calling a procedure from another procedure in sqlserver - stored-procedures

i have 2 procedures procgetid,insertmarks.procgetid returns a int value which will be used in insertmarks procedure
code in procgetid procedure:
ALTER procedure [dbo].[procgetid](#sid int output)
as
begin
select #sid=(select isnull(max(id),0)+100 from test2);
return #sid
end
code in insertmarks procedure:
ALTER procedure [dbo].[insertmarks](#sub1 int,#sub2 int,#sub3 int)
as
declare #student_id int
begin
exec #student_id = procgetid
insert into marks values(#sub1,#sub2,#sub3,#student_id)
end
while executing insertmarks procedure i got an error
"Cannot insert the value NULL into column 'student_id',column does not allow nulls. INSERT fails."

ALTER procedure [dbo].[insertmarks](#sub1 int,#sub2 int,#sub3 int)
as
declare #student_id int
begin
set #student_id = exec [schemaname].procgetid
insert into marks values(#sub1,#sub2,#sub3,#student_id)
end

Related

Teradata, insert in table the result of a Stored Procedure

I want to insert the result of a stored procedure into a table
Something like :
CREATE PROCEDURE MyStoredProc2()
DYNAMIC RESULT SETS 1
BEGIN
INSERT INTO MyTable
SELECT * FROM (CALL MyStoredProc1())
END;
Definition of MyStoredProc1 :
CREATE PROCEDURE MyStoredProc1 ()
DYNAMIC RESULT SETS 1
BEGIN
DECLARE CURSOR_1 CURSOR WITH RETURN ONLY FOR
SELECT * FROM Table;
OPEN CURSOR_1;
END;
Thanks for your help

How to pass multiple values in a single parameter if number of values is not predefined

I select some rows in dbgrid and then have to pass set of values in parameter of stored procedure or query. I use Firebird 3. How to pass multiple values in a single parameter if number of values is not predefined? For example, for 3 values of good_id I get error:
conversion error from string "7802 ,8403 ,11461"
create or alter procedure sp_goods (id varchar(60))
returns (
good varchar(50),
good_id integer)
as
begin
for select good_id, good from goods where good_id in (:id)
into :good_id, :good
do suspend;
end
procedure Button1Click(Sender: TObject);
var
str : String;
i : Integer;
begin
Query1.Close;
Query1.SQL.Text := 'select * from sp_goods(:id) ';
with DBGridGoods do
begin
if SelectedRows.Count > 0 then
begin
str := '';
With DataSource.DataSet do
for i := 0 to SelectedRows.Count - 1 do
begin
GotoBookmark(SelectedRows.Items[i]) ;
str := str + FieldByName('good_id').asString + ', ';
end;
str := copy( str, 1, length( str ) - 2 );
end;
end;
Query1.Params[0].AsString:=str;
Query1.Open;
end;
If I call stored procedure in IBExpert
select * from sp_goods('8403')
It works, but
select * from sp_goods('8403','7802')
returns error
Input parameter mismatch for procedure sp_goods.
The same error occurs if I use query instead of stored procedure.
I tried to use array for values, but get empty dataset:
procedure Button1Click(Sender: TObject);
var
a: array of integer;
begin
Query1.Close;
Query1.SQL.Text := 'select * from sp_goods(:id) ';
setlength(a, 2);
a[0]:= 7802;
a[1]:=8403;
Query1.Params[0].Value:= a;
Query1.Open;
end;
There is no way to pass set of values into single parameter in Firebird.
In your example whole stored procedure is meaningless and it is simpler and faster to select all values at once into original grid using join. If you wish to get goods for selected items only and to put them into a separate grid the best way is to perform the query in your loop instead of gathering list of ids. If you prepare the query once (it is a common mistake to do prepare() call inside of the loop) it will be quite fast.
I have achieved this in two ways in the past. One is by using Dynamic Queries, which is not what you want to do, unless there is no other option.
The other way is by using this procedure. I am dragging this from my archives, and there will be other ways to achieve this more efficiently. I am providing it to show how to do it.
create or alter procedure "Split_Line"
( IP_NOTE VARCHAR (16000),
IP_SEP CHAR (1))
returns (
"Index" INTEGER,
"Line" VARCHAR (16000))
as
declare variable lLines varchar (16000);
declare variable lMax integer;
declare variable lPos integer;
begin
lMax = 16000;
lLines = ip_Note;
"Index" = 0;
while (lLines is not null)
do begin
"Line" = null;
lPos = null;
select "Result" from "Pos" (:Ip_Sep, :lLines) into :lPos;
if (lPos is null or lPos = 0)
then begin
/* Last line with no separator */
"Line" = lLines;
lLines = null;
end
else if (lPos = 1 and lLines = Ip_Sep)
then begin
/* Last char is a separator */
"Line" = '';
lLines = null;
end
else begin
/* Normal Case */
// "Line" = "SubStr" (:lLines, 1, :lPos-1);
// lLines = "SubStr" (:lLines, :lPos+1, :lMax);
"Line" = substring (:lLines from 1 for :lPos-1);
lLines = substring (:lLines from :lPos+1 for :lMax);
end
"Index" = "Index" + 1;
suspend;
end
end
You call it with a comma separated values in a string and the separator character (comma in this case). And it returns a table that you use.
Example of usage
select * from "Split_Line" ('x,a,cat', ',')
will return
Index
Line
1
x
2
a
3
cat
And you can use it in your case
create or alter procedure sp_goods (id varchar(60))
returns (
good varchar(50),
good_id integer)
as
begin
for select good_id, good from goods
where good_id in (select cast("Line" as numeric (18, 0))
from "Split_Line" (:id, ','))
into :good_id, :good
do suspend;
end
Supporting procedure to compile before Split_String
create or alter procedure "Pos"
( SUBSTR VARCHAR (100),
STR VARCHAR (16000))
returns ( "Result" INTEGER)
as
DECLARE VARIABLE SubStr2 VARCHAR(16256); /* 1 + SubStr-lenght + Str-length */
DECLARE VARIABLE Tmp VARCHAR(255);
BEGIN
IF (SubStr IS NULL OR Str IS NULL)
THEN BEGIN
"Result" = NULL;
suspend;
EXIT;
END
IF (SubStr = '' OR Str = '')
THEN BEGIN
"Result" = 0;
suspend;
EXIT;
END
SubStr2 = SubStr || '%';
Tmp = '';
"Result" = 1;
WHILE (Str NOT LIKE SubStr2 AND Str NOT LIKE Tmp)
DO BEGIN
SubStr2 = '_' || SubStr2;
Tmp = Tmp || '_';
"Result" = "Result" + 1;
END
IF (Str LIKE Tmp)
THEN "Result" = 0;
suspend;
END
And I have replaced my substr (from my user defined function library) to use the firebird substring in the Split_Line procedure.
Apologies for the quoted identifiers, always use Dialect 3. And the odd capitalisation was to support Crystal Reports which at that time would only work with uppercase procedures.
The other way to do it is to use Dynamic Queries.
create or alter procedure sp_goods (id varchar(60))
returns (
good varchar(50),
good_id integer)
as
declare lsql varchar (5000);
begin
lsql = 'select good_id, good from goods where good_id in (' || :id || ')';
for execute statement lsql
into :good_id, :good
do suspend;
end
Disadvantages
Normally, when a procedure is compiled, the queries are prepared at that time. So, execution is faster. With Dynamic Queries or Dynamic Sql, the query has to be prepared every time the procedure is executed.
Normally, when a procedure is compiled, the engine validates the table and fields etc. In this case the validation happens at execution time. So you have to be really careful how you construct your query.
Note - I havent had time to test it with a real table, but it compiles. (Its 3am, so I might check that tomorrow).
I wouldn't normally recommend this, but everything has a place.

generate a number increases with a specific format ex 'PRT-00000'

I want to create auto numbering to my access database in delphi
example :
I have a database with part names , i want to create an auto id that counts number of these records and generates a name with number as this 'PRT-00000' and increase it with one each time i add a record and keeps this format of five digits , like this 'PRT-00001'
help me please and thanks a lot .
sorry for my poor english
Let's assume your Access table is named 'Parts' and has an AutoNumber
field named 'ID' and a Short Text field named 'PartNumber'. One way of generating the
PartNumber value would be to get Access to calculate it for you, but since you have asked about Delphi, I'm going to explain a way to do it in Delphi.
Please start a new, very simple project with just the following items on the main form:
A TAdoConnection configured to connect to your database;
A TAdoQuery configured to use the TAdoConnection with its SQL.Text property set
to 'select * from Parts'
A TDataSource and TDBGrid configured to display the contents of the TAdoQuery.
A TButton
Then, add the following code to the form's unit:
procedure TForm2.Button1Click(Sender: TObject);
begin
NewPart;
end;
procedure TForm2.NewPart;
const
sSelect = 'select * from Parts';
sPrefix = 'PRT-';
iDigits = 5;
var
PartNumber : String;
ID : Integer;
begin
qryParts.Insert;
try
// First, set the new record's PartNumber field to a temporary value
qryParts.FieldByName('PartNumber').AsString := 'xxxx';
// save the record so that we can then read the ID value Access has allocated to the record
qryParts.Post;
// read the ID value
ID := qryParts.FieldByName('ID').AsInteger;
// next, construct the desired value for the PartNumber field based on the ID
PartNumber := qryParts.FieldByName('ID').AsString;
// left-pad the PartNumber with zeroes
while Length(PartNumber) < iDigits do
PartNumber := '0' + PartNumber;
// pre-pend the PRT- prefix
PartNumber := sPrefix + PartNumber;
// put qryParts into its dsEdit state
qryParts.Edit;
qryParts.FieldByName('PartNumber').AsString := PartNumber;
finally
// post the record back to the Parts table
qryParts.Post;
end;
end;
procedure TForm2.FormCreate(Sender: TObject);
begin
qryParts.Open;
end;
Update I've managed to get the new Part's autonumber ID in advance of the new Part being posted to the db. To use this, add the NewAutoNumber function and modify the NewPart method as shown below.
function TForm2.NewAutoNumber(ATable, AColumn: String): Integer;
var
vCat : OleVariant;
vTable : OleVariant;
vColumn : OleVariant;
begin
vCat := CreateOleObject('ADOX.Catalog');
vCat.ActiveConnection := AdoConnection1.ConnectionString;
vTable := vCat.Tables[ATable];
vColumn := vTable.Columns[AColumn];
Result := vColumn.Properties['Seed'].Value;
end;
procedure TForm2.NewPart;
const
sSelect = 'select * from Parts';
sPrefix = 'PRT-';
iDigits = 5;
var
PrvSql : String;
PartNumber : String;
ID : Integer;
begin
ID := NewAutoNumber('Parts', 'ID');
try
qryParts.Insert;
qryParts.FieldByName('PartNumber').AsString := 'xxxx';
qryParts.Post;
if not qryParts.Locate('ID', ID, []) then begin
raise exception.CreateFmt('Failed to create new Parts record with ID = %d', [ID]);
end;
PartNumber := qryParts.FieldByName('ID').AsString;
while Length(PartNumber) < iDigits do
PartNumber := '0' + PartNumber;
PartNumber := sPrefix + PartNumber;
qryParts.Edit;
qryParts.FieldByName('PartNumber').AsString := PartNumber;
finally
qryParts.Post;
end;
end;
Update #2 As an alternative to getting the ID value for a newly-added Parts record using
either of the methods above, it can be obtained by using the 'select ##identity' method. The simplest
way to do this is to add another TAdoQuery, qryAutoNumber to the form and to add this function to
get the AutoNumber value:
function TForm2.NewAutoNumberFromIdentity : Integer;
begin
if qryAutoNumber.Active then
qryAutoNumber.Close;
qryAutoNumber.SQL.Text := 'select ##identity';
qryAutoNumber.Open;
Result := qryAutoNumber.Fields[0].AsInteger;
end;
Note that to obtain the correct ID value, this function should be called immediately after calling qryParts.Post. However, I have included this
only for completeness but as far as I can see, it is largely pointless
because once the new Parts record has been posted, the ID AutoNumber value
can be read directly from the ID field of qryParts.

PLSQL stored procedure return the result of dynamic query

I have the following code but this does not return the resulting rows.
When I don't use dynamic sql, I was able to return the result with the help of the cursor, but not it doesn't return anything.
CREATE OR REPLACE PROCEDURE my_db.sp_test
(
my_clause in VARCHAR2
)
AS
query1 VARCHAR2(5000) DEFAULT 'SELECT my_table.* FROM my_table WHERE 1=1 ';
BEGIN
if like_clause is not null then
query1 := query1 || my_clause;
else
query1 := query1 || my_clause;
end if;
EXECUTE IMMEDIATE query1;
END;
/
for example: my_clause parameter is 'AND my_table.ID IN (10693192,10687172,10630960)'
EDIT:
The following code returns the resulting rows. And I am able to read them from my .NET application (and also from Toad). However, the above query returns nothing.
And I will need t ouse dynamic sql so, I would like to return the same result using the above SP.
CREATE OR REPLACE PROCEDURE PHX_EPI.sp_test2
(
cursor_ OUT SYS_REFCURSOR
)
AS
BEGIN
OPEN cursor_ FOR
SELECT my_table.* FROM my_table;
END;
/

What happens if I call ParamByName for a parameter that has multiple locations in a query? [duplicate]

I am using ADOQuery in Delphi 7 and Oracle. I am getting error while passing parameters to ADOQuery. I have used following line. Please help me to identify error.
ADOQuery.Sql.text:= 'select * from temp_table '+
'where column1 in (select column from table2 where id=:id) and id=:id';
ADOQuery.Parameters.ParamByValue('id').value= 'abc';
ADOQuery.open;
when I open the query i will get following error:
Parameter object is improperly defined. Inconsistent or incomplete information is provided.
We have the same problem, we ended "masking" the class TParameters like this:
Declaration:
TMyParameter = class(TParameter)
private
function GetAsValue: variant;
Procedure SetAsValue(const Value: variant);
public
property Value: variant read GetAsValue write SetAsValue;
end;
Implementation:
procedure TMyParameter.SetAsValue(const Value: variant);
var
iPar: Integer;
begin
for iPar:= 0 to Collection.Count - 1 do
if (Name = TParameter(Collection.Items[iPar]).Name) then
TParameter(Collection.Items[iPar]).Value:= Value;
end;
function TMyParameter.GetAsValue: variant;
begin
Result:= inherited Value;
end;
And how to use:
TMyParameter(ADOQuery.Parameters.ParamByName('id')).AsValue:= 'abc';
I hope it helps.
for i:=0 to ADOQuery.Parameters.Count-1 do
begin
if ADOQuery.Parameters.Items[i].Name = 'id' then
ADOQuery.Parameters.Items[i].Value := 'abc';
end;
You need to distinguish between the two id;s:
ADOQuery.Sql.text:= 'select * from temp_table a where column1 in (select column from table2 b where b.id=:id) and a.id=:id';
ADOQuery.Parameters.ParamByValue('id').value= 'abc';
ADOQuery.open;
In the SQL code declare a variable of the necessary type, assign to that variable the parameter; you will be able to use that variable as many times as necessary:
ADOQuery.Sql.text:= 'declare #param varchar(50); set #param = :id; '+
'select * from temp_table '+
'where column1 in (select column from table2 where id=#param) and id=#param';
ADOQuery.Parameters.ParamByValue('id').value= 'abc';
ADOQuery.open;
Regards

Resources