I am trying to write a stored procedure which works dynamically by concatenating the parameters which are passed.
CREATE OR REPLACE PROCEDURE E_Enquiry
(IN SourceQueue1 VARCHAR(30),
IN PayloadMsgId1 VARCHAR(100),
IN EventSource1 VARCHAR(100)
)
DYNAMIC RESULT SETS 1
LANGUAGE SQL
RETURNRT: BEGIN
DECLARE query_String VARCHAR(32000);
SET query_String = 'SELECT PayloadMsgId,ExceptionId,EventSource,E_Message.InterfaceId,CreationTime,SourceProtocol,ErrorMessage,Severity,InterfaceName
from E_Message,E_Config where' ;
if SourceQueue1 <> NULL THEN
SET query_String = query_String + 'SourceQueue='+SourceQueue1+'and';
end if;
if PayloadMsgId1 <> NULL THEN
SET query_String = query_String + 'PayloadMsgId='+PayloadMsgId1+'and';
end if;
if EventSource1 <> NULL THEN
SET query_String = query_String + 'EventSource='+EventSource1;
end if;
RETURNCR: BEGIN
DECLARE C1 CURSOR WITH RETURN TO CALLER FOR
query_String;
open C1;
END RETURNCR;
END RETURNRT
I am not sure whether the query_String holds the query when called in cursor. This stored procedure is not returning any values. Could someone help to overcome this?
Related
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.
In the following query I am using a stored procedure to create a table output_tbl_name from a source_tbl. The name of the output_tbl_name is timestamped with today's date.
DECLARE
creation_date STRING := to_varchar(current_date(), 'YYYYMMDD');
output_tbl_name STRING := concat('my_database.my_schema.', 'output_', :creation_date);
QUERY STRING;
BEGIN
QUERY:= REPLACE(
'create or replace table <output_tbl_name>(col1 varchar, col2 varchar) as
select * from source_tbl;'
,'<output_tbl_name>', :output_tbl_name); ;
EXECUTE IMMEDIATE :QUERY;
RETURN :QUERY;
END;
However, I would like to set a dynamic criteria for the data that is copied over. E.g., the source_tbl has a date column, and I want to only copy over records for dates > 6 months ago:
declare start_date date := add_months(current_date(), -6)
create or replace output_tbl_name as select * from source_tbl where date > <start_date>
How can I incorporate this into my above query? Placing it directly into my QUERY in my BEGIN statement isn't working. Thanks for your help!
create a source data table:
CREATE OR REPLACE TABLE tmp_table(col1 string);
DECLARE
creation_date STRING := to_varchar(current_date(), 'YYYYMMDD');
output_tbl_name STRING := concat('my_database.my_schema.', 'output_', :creation_date);
QUERY STRING;
source_tbl STRING := 'TMP_TABLE';
BEGIN
LET date_present := false;
SELECT true INTO :date_present FROM INFORMATION_SCHEMA.columns WHERE table_name = :source_tbl AND column_name = 'DATE';
if (date_present = true) then
QUERY := 'create or replace table <output_tbl_name>(col1 varchar, col2 varchar) as select * from source_tbl where date > <start_date>;';
ELSE
QUERY:= 'create or replace table <output_tbl_name>(col1 varchar, col2 varchar) as select * from source_tbl;';
END IF;
QUERY:= REPLACE( QUERY, '<output_tbl_name>', :output_tbl_name);
--EXECUTE IMMEDIATE :QUERY;
RETURN :QUERY;
END;
begin
let count := true;
if (count = true) then
return 'negative value';
elseif (count = 0) then
return 'zero';
else
return 'positive value';
end if;
end;
anonymous block
create or replace table my_database.my_schema.output_20220307(col1 varchar, col2 varchar) as select * from source_tbl;
put in a date column:
CREATE OR REPLACE TABLE tmp_table(col1 string, date timestamp);
anonymous block
create or replace table my_database.my_schema.output_20220307(col1 varchar, col2 varchar) as select * from source_tbl where date > <start_date>;
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;
/
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
i got this sp:
DROP TABLE IF EXISTS SplitValuesDump;
CREATE TABLE SplitValuesDump (
value VARCHAR(1000) NOT NULL PRIMARY KEY
);
DELIMITER $$
DROP PROCEDURE IF EXISTS `ChangeSitesRedirects`$$
CREATE PROCEDURE `ChangeSitesRedirects`(
prodimainAddress varchar(255),
subdomainMainAddress varchar(255)
)
SQL SECURITY INVOKER
BEGIN
DECLARE tdomain varchar(1000);
DECLARE tvalue varchar(1000);
DECLARE prepValue varchar(1000);
DECLARE subdomainFullAddress varchar(1000);
DECLARE totalDomain int;
DECLARE tclientid int;
DECLARE sitedone INT DEFAULT 0;
DECLARE splitdone INT DEFAULT 0;
DECLARE lastDomain varchar(1000);
DECLARE curlSites CURSOR FOR (SELECT domain,clientid from sites where redirectsubdomain = 'N');
DECLARE CONTINUE HANDLER FOR NOT FOUND SET sitedone = 1;
set sitedone := 0;
OPEN curlSites;
Scan_Sites:WHILE (sitedone = 0) DO
IF sitedone = 1 THEN
BEGIN
LEAVE Scan_Sites;
END;
ELSE
BEGIN
DECLARE curlStringDump CURSOR FOR (SELECT `value` from SplitValuesDump);
DECLARE CONTINUE HANDLER FOR NOT FOUND SET splitdone = 1;
FETCH curlSites INTO tdomain,tclientid;
CALL split_string(tdomain,';');
OPEN curlStringDump;
SET splitdone:=0;
ScanDump: WHILE (splitdone = 0) DO
IF splitdone = 1 THEN
BEGIN
LEAVE ScanDump;
END;
ELSE
BEGIN
FETCH curlStringDump INTO tvalue;
SET subdomainFullAddress:= subdomainMainAddress;
IF tvalue <> "" THEN
BEGIN
IF tvalue like prodimainAddress OR tvalue like subdomainMainAddress THEN
BEGIN
set totalDomain := totalDomain + 1;
IF tvalue like subdomainMainAddress THEN
BEGIN
SET subdomainFullAddress := tvalue;
END;
END IF;
END;
ELSE
BEGIN
set totalDomain := totalDomain + 1;
set lastDomain := tvalue;
END;
END IF;
END;
END IF;
END;
END IF;
END WHILE ScanDump;
CLOSE curlStringDump;
SET splitdone :=0;
SET prepValue:='N';
IF lastDomain = '' AND totalDomain = 2 THEN
BEGIN
set prepValue := subdomainFullAddress || CHAR(2) || prodimainAddress;
INSERT INTO sites_tmp SELECT * FROM sites where clientid = tclientid limit 1;
UPDATE sites_tmp SET redirectsubdomain = prepValue WHERE clientid = tclientid limit 1;
END;
ELSE
BEGIN
set prepValue := prodimainAddress || CHAR(2) || lastDomain || CHAR(1) ||subdomainFullAddress || CHAR(2) || lastDomain;
INSERT INTO sites_tmp SELECT * FROM sites where clientid = tclientid limit 1;
UPDATE sites_tmp SET redirectsubdomain = prepValue WHERE clientid = tclientid limit 1;
END;
END IF;
END;
END IF;
END WHILE Scan_Sites;
CLOSE curlSites;
SET sitedone :=0;
END$$
i try in the get few info from column data split his data and bring some data ion there.
for each recored on table sites
and then update table sites_tmp.
i got issue that i not know how i can debug at or make at faster?
what ur recommend here?
as well why its so slow???
and in the end its not passed the all the records?