I have defined a variable
define myStrings = "'abc','def'"
which I later need to use inside a procedure block and convert into a table of varchars
declare
type varcharListType is table of varchar(200);
myList varcharListType;
begin
myList := varcharListType(&myStrings);
.
.
.
end;
/
I am attempting to use either the variable or the table inside an IN clause in a create query within the procedure block
execute immediate 'create table tmp_foo '
|| 'as select * from bar '
|| 'where bar_val in (&myStrings) ';
I have tried using the REPLACE function also
myNewStrings := replace(&myStrings, '''' , '''''');
but I get an exception related to abc and def not being defined.
ISSUE:
I am getting a syntax exception because the quotes around abc and def in myString are not escaped. The value "'abc','def'" must be 'defined' rather then 'declared' so it is substituted later.
QUESTION:
Is it possible to 'define' a variable in such a way that I can use it both as table type values and also a string in the execute immediate statement?
TO REPRODUCE:
Create
create table bar (bar_id number not null, bar_val varchar2(20),
constraint bar_pk primary key (bar_id)
enable
);
Insert
insert into bar (bar_id, bar_val)
values (1, 'abc'),
(2, 'def'),
(3, 'ghi');
SAMPLE PROCEDURE
set verify off;
set serveroutput on;
define myStrings = "'abc','def'"
declare
type varcharListType is table of varchar(20);
myList varcharListType;
begin
myList := varcharListType(&myStrings);
execute immediate 'create table tmp_foo '
|| 'as select * from bar '
|| 'where bar_val in (&myStrings) ';
for i in myList.FIRST..myList.LAST loop
dbms_output.put_line('VALUE: ' || myList(i));
end loop;
end;
/
set serveroutput off;
set verify on;
The below is the approch I would take, Note the use of tablen in the loop, this is because the DBMS_UTILITY.COMMA_TO_TABLE procedure adds a null value at the end of the table.
Hope you find this helpfull
declare
myStrings varchar2(100) := '''abc'',''def''';
myList dbms_utility.uncl_array;
tablen number :=0;
begin
DBMS_UTILITY.COMMA_TO_TABLE ( replace(myStrings, '''', ''), tablen, myList);
execute immediate 'create table tmp_foo '
|| 'as select * from bar '
|| 'where bar_val in (' ||myStrings||')';
for i in myList.FIRST..tablen loop
dbms_output.put_line('VALUE: ' || myList(i));
end loop;
end;
/
Thanks to #ShaunPeterson for inspiring the solution to this issue. While it solve the issue directly it provided the correct approach so all +1s should go to him.
Where his answer fell short was that he 'declared' myStrings rather then 'defining' it.
declare
myStrings varchar2(100) := '''abc'',''def''';
NOT
define myStrings = "'abc','def'"
Herein lay the crux of the issue. In PL/SQL variables that are 'declared' for a procedure block like myStringsVar below are not substituted like 'defined' variables are. As per the OP the requirement was that 'myStrings' was first 'defined' then later transformed for use in a procedure block.
Therefore the resulting solution looks like this:
define myStrings = "''abc'',''def''"
declare
myStringsVar varchar2(100) := '&myStrings';
myList dbms_utility.uncl_array;
tablen number :=0;
begin
DBMS_UTILITY.COMMA_TO_TABLE ( replace(myStringsVar, '''', ''), tablen, myList);
execute immediate 'create table tmp_foo '
|| 'as select * from bar '
|| 'where bar_val in (' || myStringsVar||')';
for i in myList.FIRST..tablen loop
dbms_output.put_line('VALUE: ' || myList(i));
end loop;
end;
/
Related
I have a Stored Procedure with a input param, which is a multi valued parameter of Varchar.
The passed parameters are passed to the IN Clause of the query.
I am unable to figure out how to handle that in the stored procedure.
Till now , I have this (this is the snippet of the actual stored procedure) :
CREATE OR replace PROCEDURE <SCHEMA>.Some_Proc
(
IN V_INDSTRY_DESCRPTN VARCHAR (2000)
)
DYNAMIC RESULT SETS 1
BEGIN
DECLARE WHERE_CLAUSE VARCHAR(5000) DEFAULT '';
DECLARE OUTER_CLAUSE VARCHAR(2000) DEFAULT '';
DECLARE V_SQL VARCHAR(10000) DEFAULT '';
DECLARE CSR_RSLT_SET CURSOR WITH RETURN FOR S1;
IF (V_INDSTRY_DESCRPTN != 'ALL') THEN
SET WHERE_CLAUSE = WHERE_CLAUSE || 'AND industry.INDSTRY_DESCRPTN in ( '''||V_INDSTRY_DESCRPTN||''')' ;
END IF;
SET V_SQL ='<SOME QUERY>'
/*some other logic goes here*/
PREPARE S1 FROM V_SQL;
OPEN CSR_RSLT_SET;
END
I am calling the procedure like this :
CALL <SCHEMA>.Some_Proc ('industry1')
1)how do I send multiple values in the same parameter?
CALL <SCHEMA>.Some_Proc ("'industry1','industry2'") gives a compilation error
2)how do I handle the multi-valued parameter within the procedure.
Try this as is:
--#SET TERMINATOR #
CREATE OR REPLACE PROCEDURE TEST_MULTIVALUE(P_TABSCHEMAS VARCHAR(128))
DYNAMIC RESULT SETS 1
BEGIN
DECLARE L_STMT VARCHAR(200);
DECLARE C1 CURSOR WITH RETURN FOR S1;
SET L_STMT = 'SELECT TABSCHEMA, TABNAME FROM SYSCAT.TABLES WHERE TABSCHEMA IN ('||P_TABSCHEMAS||')';
CALL DBMS_OUTPUT.PUT_LINE(L_STMT);
PREPARE S1 FROM L_STMT;
OPEN C1;
END#
SET SERVEROUTPUT ON#
CALL TEST_MULTIVALUE('''SYSCAT''')#
CALL TEST_MULTIVALUE('''SYSCAT'', ''SYSSTAT''')#
For those who are afraid of sql injections
We tokenize the input parameter with strings separated by comma producing a table of strings.
CREATE OR REPLACE PROCEDURE TEST_MULTIVALUE_STATIC(P_TABSCHEMAS VARCHAR(128))
DYNAMIC RESULT SETS 1
BEGIN
DECLARE C1 CURSOR WITH RETURN FOR
SELECT TABSCHEMA, TABNAME
FROM SYSCAT.TABLES T
WHERE EXISTS
(
SELECT 1
FROM XMLTABLE
(
'for $id in tokenize($s, "\s*,\s*") return <i>{string($id)}</i>'
passing P_TABSCHEMAS as "s"
COLUMNS
TOK VARCHAR(128) PATH '.'
) P
WHERE P.TOK=T.TABSCHEMA
);
OPEN C1;
END
#
CALL TEST_MULTIVALUE_STATIC('SYSCAT')#
CALL TEST_MULTIVALUE_STATIC('SYSCAT, SYSSTAT')#
I guess your question actually is, how to escape single quotes inside a character literal (surrounded by single quotes) in the standard-compliant SQL. The answer is, by doubling them:
CALL <SCHEMA>.Some_Proc ('''industry1'',''industry2''')
I am getting an error when creating tables in MS ACCESS when using FireDAC connection (Delphi Xe6).
The A new database file is created and 3 tables are created either using TFDCommand or TFDQuery, the tables do get created, but I always get an error 'Table already exists'. Any suggestions to stop the error being raised.
I do encompass the code in a try...Except routine.
Code:
FDCommand1.CommandText.Text := 'CREATE Table [SampleData] ' +
'([SampleID] INTEGER Primary Key NOT NULL, ' +
' [RiskIDX] INTEGER NULL,' +
' [RefSampleID] Char(10) NULL,' +
' [SerialNumber] Char(60) NULL,' +
' [TestDate] DateTime NULL,' +
' [ResampleDate] DateTime NULL,' +
' [SampleDate] DateTime NULL,' +
' [SamplingPoint] Char(60) NULL,' +
' [LabTech] Char(60) NULL, SampledBy Char(60) NULL,' +
' [Status] Char(60) NULL,' +
' [Source] Char(60) NULL,' +
' [Condition] Char(60) NULL,' +
' [PlotPnt] YESNO,' +
' [Comments] Memo' +
' CONSTRAINT FKSampleId Foreign Key SerialNumber
References AssetInfo SerialNo);';
OR
FDQuery1.SQL.Text := Memo1.text; //Create table SQL command
FDQuery1.Prepare;
FDQuery1.Execute(200,0);
With some experimentation I have found that the FDCommand and FDQuery components do not work in this instance; not even if you check before hand that the tables don't exist. (I not too sure why - but it seems to me that the SQL command is sent twice hence the table exists error). However, the FDConnection.ExecSQL does work. Tried and tested (creates 3 tables with the CONSTRAINTS as I needed it to).
Regards
TomD
I didn't test this code, but it should works in your porpouse.
function tableExists(TableName:String;Connection:TFDConnection):Boolean;
var
str: TStringList;
begin
str := TStringList.Create;
try
Connection.GetTableNames('','','',str);
result := str.IndexOf(TableName) <> -1;
finally
str.Free;
end;
end;
usage:
procedure TForm2.Button1Click(Sender: TObject);
begin
if not tableExists('SampleData',FDConnection1) then
begin
//create table here
end;
end;
You are calling Execute with ATimes parameter set to 200. That means that you want execute the command in array DML mode and you have 200 rows of parameter values. And since you have no parameters, it simply executes the command that number of times which fails on second attempt when DBMS rejects to create already created table.
You can either call Execute this way (there's no need for explicit call of Prepare):
FDQuery1.SQL.Text := Memo1.Text; // Create table SQL command
FDQuery1.Execute;
Or ExecSQL which internally calls Execute the way shown above:
FDQuery1.SQL.Text := Memo1.Text; // Create table SQL command
FDQuery1.ExecSQL;
Create or replace procedure total_test_inside()
RETURNS REFTABLE(testtabl)
LANGUAGE NZPLSQL
AS
BEGIN_PROC
DECLARE
prod_id integer;
lkp_weighted_prc numeric(20,3);
rec record;
BEGIN
FOR rec IN select prod_id from weight
LOOP
select weightprice into lkp_weighted_prc from weight;
call total_amort_test(lkp_weighted_prc);
execute immediate 'insert into ' ||REFTABLENAME || ' values(' || lkp_weighted_prc || ')';
END loop;
return REFTABLE;
END;
END_PROC;
call total_test_inside();
Can someone guide on why I'm unable to loop which is taking just last prod_id from weight table in netezza.
Thanks in advance
I'm going to go out on a limb with what I think you're trying to accomplish here.
Since you are already doing a FOR loop here, there's no need to do another select to get weightprice, and in fact that will probably just give you the same first value over and over again.
FOR rec IN select prod_id from weight
LOOP
select weightprice into lkp_weighted_prc from weight;
call total_amort_test(lkp_weighted_prc);
execute immediate 'insert into ' ||REFTABLENAME || ' values(' || lkp_weighted_prc || ')';
END loop;
What I think you want is this. Here I've added weightprice to the select statement in the FOR definition, and removed the seemingly superfluous SELECT. Then I use "rec." qualifier to reference the single record that is stepped over in the FOR loop.
BEGIN
FOR rec IN select prod_id, weightprice lkp_weighted_prc from weight
LOOP
call total_amort_test(rec.lkp_weighted_prc);
execute immediate 'insert into ' ||REFTABLENAME || ' values(' || rec.lkp_weighted_prc || ')';
END loop;
I'm facing an issue executing SQL script in a TADOQuery component. The script that's being executed has goto statements along with their corresponding labels, for example:
goto MyLabel
MyLabel:
--do something else
However, the TADOQuery is seeing these : characters and treating them as parameters (which they are not parameters), and gives me an error:
Parameter object is improperly defined. Inconsistent or incomplete information was provided
How can I instruct the TADOQuery to not try to consider these as parameters?
Set AdoQuery.ParamCheck to false.
Update
The OP said in a follow-up comment that the above was sufficient for his immediate problem but wouldn't have worked if the query had contained actual :parameters. Initially, I couldn't get it to work with those either.
However, looking at the code of TParameters.ParseSQL in ADODB (D7), the author seems to have anticipated colons being embedded in the SQL (I mean, before any :paramname(s) one might enter to act as place-holders for TParameters), by treating a doubled-up colon (::) as a special case. So I think the intent was that one should double up any colon one doesn't want treated as a TParameter. To see what I mean, see Memo1's contents:
(PartialDFM)
object Memo1: TMemo
Left = 32
Top = 112
Width = 297
Height = 113
Lines.Strings = (
'declare'
' #number int'
'select'
' #number = ?'
'if #number > 0 goto positive'
'if #number < 0 goto negative'
''
'select ''zero'''
''
'positive::'
' select ''positive'''
' goto'
' exitpoint'
'negative::'
' select ''negative'''
'exitpoint::')
end
object ADOQuery1: TADOQuery
Connection = ADOConnection1
Left = 64
Top = 24
end
Then, the following works for me (displaying "positive", "negative" or "zero" in a DBGrid according to the value assigned to AdoQuery1.Parameters[0].Value)
procedure TForm1.DoQuery;
begin
if AdoQuery1.Active
then AdoQuery1.Close;
// AdoQuery1.Prepared := True;
AdoQuery1.SQL.Text := Memo1.Lines.Text;
AdoQuery1.ParamCheck := False;
ADOQuery1.Parameters.Clear;
ADOQuery1.Parameters.CreateParameter('Param1', ftInteger, pdInput, 1, Null);
AdoQuery1.Parameters[0].Value := 666;
AdoQuery1.Prepared := True;
AdoQuery1.Open;
end;
I'm currently using AdoQuery's and append post commands. But for data security I want to change my code with insert into and update table name...
But I have a lot of forms and tables...
Because of that I think maybe someone has already developed code for generating insert statements.
Actually I have found a way but I'm stuck.
I have query1. it contains the fieldlist.
I'm creating a parameter list in another query from this fieldlist.
I'm updating the parameters field by field.
This is not very convenient
Can someone give me a easy ways to do this.
Note: I prefer coding this job with only standard components. I don't want to install additional components.
Maybe not the reply you want. I think you need to raise the abstraction level. You need to skip SQL. An ORM framework can do this for you. It maybe feels like a big step for you but I promise it is also a relief to just use code like:
Person.name := 'Bob';
Invoice.customer.address.street := 'Abbey road';
Edit1.text := Invoice.customer.name;
To actually update database you need to call an update method that differ depending on framework. For a list of frameworks see here. I am also aware of TMS Aurelius. I use Bold on daily use. Bold also have features like OCL, derived attributes and links in the model, some boldaware components (it updates whenever db changes). But it has one big disadvantage. It is only available for D2006/D2007. I am working for a solution on this because I think it is the best and most mature ORM framework for Delphi. See also my blog on Bold for Delphi. Ask if you have questions!
You take the fieldlist from your query.
Create a new query with parameters.
And fill in the values.
Something like this:
const
TableNameEscapeStart = '['; //SQL server, use '`' for MySQL
TableNameEscapeEnd = ']'; //SQL server, use '`' for MySQL
FieldNameEscapeStart = '[';
FieldNameEscapeEnd = ']';
function CreateInsertStatementFromTable1ToTable2(Table1, Table2: TTable): String;
var
i: integer;
comma: string;
begin
i:= 0;
Result:= 'INSERT INTO '+TableNameEscapeStart + Table2.TableName + TableNameEscapeEnd + ' (';
comma:= ' , '
while i < Table1.FieldCount do begin
if (i = Table1.FieldCount -1) then begin comma:= ' '; end;
Result:= Result + FieldNameEscapeStart + Table1.Fields.Field[i].Name + FieldNameEscapeEnd + comma;
end;
Result:= Result +' ) VALUES ( ';
i:= 0;
comma:= ' , '
while i < Table1.FieldCount do begin
if (i = Table1.FieldCount -1) then begin comma:= ' '; end;
Result:= Result +':' + IntToStr(i+1) + comma;
end; {while}
Result:= Result + ' ); ';
end;
There are three avenues for SQL injection here.
1. The field values
2. The table name
3. The field names
The first is covered by the use of parameters.
The second and third are covered, because you're using the table and field names of the table directly.
If you don't have a trusted source of table and fields names, then you need to compare these against the table and fieldnames obtained directly from the table.
See: Delphi - prevent against SQL injection
You insert the data using ParamByName (slowly) or more efficiently using Param[i] where i starts at 0.
In MySQL it's even easier:
If table1 and table2 have the same fields, the following SQL will insert all data in table2 into table1:
INSERT INTO table1 SELECT * FROM table2;