Looking for a utility that converts a SQL statement into a dynamic SQL statement - plsql

I am looking for a utility that will convert Oracle SQL to a string that can executed dynamically.
Edit:
Yes, consider this simple SQL
SELECT * FROM TABLE
WHERE COLUMN_NAME = 'VALUE'
I have a utility which for T-SQL which converts the above SQL to a synamic SQL as follows:
BEGIN
DECLARE #Exe_String VarChar(2000)
DECLARE #Qt Char(1)
DECLARE #Cr Char(1)
SET #Qt = Char(39)
SET #Cr = Char(10)
SET #Exe_String = 'SELECT * FROM TABLE ' + #Cr
SET #Exe_String = #Exe_String + 'WHERE COLUMN_NAME = ' + #Qt + 'VALUE' + #Qt + '' + #Cr
PRINT #Exe_String
--Execute (#Exe_String)
END
Granted that the code generated good probably be better, yo get the idea, I hope.
I'm looking for the same type of conversion for Oracle SQL.

Here is a tool that I have used a couple of times. You will have to change the output a little to get it to run but it sure beats having to figure out how to escape all the single ticks.
Sql Tuning
After you click on the link it will take you right to the site and a page with sample SQL. Click the "Static SQL to Dynamic SQL" button and you can see how it works. Then input your own sql you want converted and click the button again. Remove the extra tick (') marks in the end and beginning of each line with the exception of the first and last line and pipes (|) don't need to be there either. Hope this helps.

As a raw translation of your T-SQL to PL/SQL
DECLARE
Exe_String VarChar(2000);
Qt CONSTANT Char(1) := CHR(39);
Cr CONSTANT Char(1) := CHR(10);
BEGIN
exe_string := 'SELECT * FROM TABLE '||Cr;
exe_string := exe_string ||
'WHERE COLUMN_NAME = ' || Qt || 'VALUE' ||Qt || '' ||Cr;
dbms_output.put_line(exe_string);
--
EXECUTE IMMEDIATE exe_string;
END;
The obvious difference is that in Oracle the concatenation operator for strings is || rather than +.
Personally, I have a little string manipluation package (let's call it pstring) that I'd use in a case like this - includes functions like enquote(string), standard constants for newline,tab,etc and the ability to do C-style text replacement.
exe_string :=
pstring.substitute_text('SELECT * FROM %s \n WHERE %s = %s',
table_name,column_name,pstring.enquote(value));
Have you considered using bind variables - i.e. :value - rather than dealing with escaping all the internal quotes? It's a good defence against SQL injection.
Obviously there's some difficulty if you have varying numbers of variables (you need to use DBMS_SQL to link them to the statement rather than a simple EXECUTE IMMEDIATE) but for your simple case it would look like this.
PROCEDURE (table_name IN VARCHAR2, column_name IN VARCHAR2)
IS
Exe_String VarChar(2000);
BEGIN
exe_string :=
pstring.substitute_text('SELECT * FROM %s \n WHERE %s = :value',
table_name,column_name);
dbms_output.put_line(exe_string);
--
EXECUTE IMMEDIATE exe_string USING pstring.enquote(value);
END;
Although of course you have to do something with the results of your SQL.
EXECUTE IMMEDIATE exe_string INTO lresult USING pstring.enquote(value);
Which is difficult when the shape of the table may differ - again, you have to look at Type 4 dynamic SQL (DBMS_SQL).

Related

Converting Syntax from sqllite to postgresql

I have a sqlLite database which contain in some field sql statement, this sql statement are needed to create some chart in an IOS Application.
Now I need to display this chart in a web application which use Postgresql
I need to find a script who convert from sqllite syntax to postgresql syntax
things like printf, current date etc...
I am asking if there is some ready to use script for this kind of conversion
PS : I am using Symfony for the back-end so PHP
Example:
SELECT
r.agent AS gebiet,
r.invoiceno AS rechnung,
r.infotext AS auftrag,
c.companyno AS kundenr,
c.companyname AS kunde,
r.itemno AS artikelnr,
r.itemtext AS artikel,
SUM(r.qty) || ' ' || r.unit AS menge,
printf('%.2f', SUM(r.turnover) / SUM(r.qty)) AS preis,
printf('%.2f', SUM(r.turnover)) || ' ' || r.currency AS gesamt,
'2' AS 'sys_align9',
'2' AS 'sys_align10',
'2' AS 'sys_align11'
FROM
invoices r
INNER JOIN
company c
ON
r.companyno = c.companyno
WHERE
r.agent = ?
Disclaimer: I don't know SQLite ;)
The query structure itself looks good, but two things will not work with Postgres:
I assume printf() formats the output to two decimals, so to_char() is probably what you are looking for
identifiers need to be enclosed in double quotes, not single quotes. So AS 'sys_align11' should be `AS "sys_align11" but the quotes are not required to begin with.
SELECT
r.agent AS gebiet,
r.invoiceno AS rechnung,
r.infotext AS auftrag,
c.companyno AS kundenr,
c.companyname AS kunde,
r.itemno AS artikelnr,
r.itemtext AS artikel,
SUM(r.qty) || ' ' || r.unit AS menge,
to_char(SUM(r.turnover) / SUM(r.qty), '0.00') AS preis,
to_char(SUM(r.turnover),'0.00') || ' ' || r.currency AS gesamt,
'2' AS sys_align9,
'2' AS sys_align10,
'2' AS sys_align11
FROM
invoices r
INNER JOIN
company c
ON
r.companyno = c.companyno
WHERE
r.agent = ?
If turnover is an integer (or bigin) you need to cast it to numeric, otherwise the division is done as an integer division, e.g. SUM(r.turnover)::numeric
As you are using an aggregat function you will need some kind of group by in Postgres - otherwise the result wouldn't be deterministic.
If invoiceno is the primary key of the invoices table, a `group by r.invoiceno' should be enough.

Create a table as, where 'date condition' in dynamic PL/SQL

I got assigned the following task.
Assume we have a table A structured with an id column and a date column.
Write a procedure in PL/SQL that: takes as parameters the table name (in our case A) and a date D, creates a backup table named A_bck containing only the records of A with dates < D and removes from the table A all the records inserted in A_bck.
Here there is my code.
Unluckily I get this error:
Error report -
ORA-00904: "MAY": invalid identifier
ORA-06512: at line 41
ORA-06512: at line 80
00904. 00000 - "%s: invalid identifier"
If I try to achieve the same result using a where condition on the id column instead that on the date one, I have no problems.
Where is the mistake? Am I implementing it completely in the wrong way?
The problem you have is that as you're executing dynamic sql you're query is built up as a string. Oracle does not know that the date you've given is actually a date, it is simply being treated as part of the string. To solve this you should be able to do the following:
my_query := 'CREATE TABLE ' || table_name_backup || ' AS (SELECT * FROM ' || table_name || ' WHERE table_date < to_date(''' || backup_date || '''))';
This should sort out your issue for you. As a side note, you will probably want to change your "table_exists" query, as table names are all stored in upper case, e.g.
SELECT COUNT(*) INTO table_exists FROM USER_TABLES WHERE TABLE_NAME = upper(my_table);
Edit: Further explanation following comment
To explain why you don't have the above problem when using integers, it is important to remember that using execute immediate simply executes the given string as an SQL query.
For example:
declare
x INTEGER := 1;
i integer;
my_query VARCHAR2(256);
begin
my_query := 'select 1 from dual where 1 = ' || x;
EXECUTE IMMEDIATE my_query INTO i;
end;
my_query in the above example would be executed as:
select 1 from dual where 1 = 1
which is perfectly valid sql. In your example however, you were ending up with something like this:
CREATE TABLE abaco_bck AS (SELECT * FROM abaco WHERE table_date < 27-MAY-17)
As it isn't wrapped in quotes, or explicitly converted to a date, the SQL engine is trying to subtract "MAY" from 27, but it doesn't know what "MAY" is.
One other thing to mention, is that for some operations you could use bind variables instead of quotes (although you can't for DDL) e.g.
declare
lToday DATE := SYSDATE;
i INTEGER;
my_query VARCHAR2(256);
begin
my_query := 'select 1 from dual where sysdate = :1';
EXECUTE IMMEDIATE my_query INTO i USING lToday;
end;

PL/SQL accept input with single quotes.

I am trying to use the PL/SQL accept functionality to take in user input. However I want the user input to be able to accept single and or double quotes. The double quotes don't seem to be a problem, however the single quote is a problem. I have my code posted below, but I keep getting errors because it doesn't like the single quote. While I know in a practical situation, the user input would be taken in via a web front end and passed to the database, unfortunately this homework assignment is asking for PL/SQL only. Any help is greatly appreciated, thanks!
Accept p_1 prompt 'Please enter the region name (CHILD_NAME):'
DECLARE
--pragma execption_init
user_input REGION.CHILD_NAME%type := '&p_1';
v_res number :=0;
BEGIN
SYS.DBMS_OUTPUT.PUT_LINE('USER INPUT:');
SYS.DBMS_OUTPUT.PUT_LINE(user_input);
SYS.DBMS_OUTPUT.PUT_LINE('');
SYS.DBMS_OUTPUT.PUT_LINE('MY OUTPUT:');
/* select count(*)
into v_res
from REGION where upper(CHILD_NAME) like upper(user_input);
if v_res = 0 then
SYS.DBMS_OUTPUT.PUT_LINE(user_input ||' is not in the table.');
else
SYS.DBMS_OUTPUT.PUT_LINE('Need to build my logic here.');
end if;
SYS.DBMS_OUTPUT.PUT_LINE('');*/
END;
Use alternative quoting mechanism q:
set serveroutput on
accept p_1 prompt 'Say something: '
declare
v_something varchar2(32767) := q'[&p_1]';
begin
dbms_output.put_line('(v_something = ' || v_something || ')');
end;
/
Example run
SQL> #so52
Say something: 'foo [is] 'bar''
old 2: v_something varchar2(32767) := q'[&p_1]';
new 2: v_something varchar2(32767) := q'['foo [is] 'bar'']';
(v_something = 'foo [is] 'bar'')
PL/SQL procedure successfully completed.
SQL>

Assign value to a field of rowtype where `field name` is a string

I want to assign a value to a rowtype's field but I don't know how to do it.
Suppose that I have a table X inside my database.
Suppose also that I have the following variables
a ( X%ROWTYPE ), representing a row of the table X
b ( VARCHAR2 ), containing a column name of the table X
c ( VARCHAR2 ), containing what I want to store inside a.b
What I want to do : something like a.b := c.
I've come up with something like this :
EXECUTE IMMEDIATE 'SELECT '|| c || ' INTO a.' || b || ' FROM DUAL';
Apparently, this isn't the right way to go. I get a ORA-0095: missing keyword error.
Can anyone help me with this ?
Here is the complete code :
DECLARE
tRow MyTable%ROWTYPE;
col_name VARCHAR(10) := 'Length';
nValue NUMBER(12,4) := 0.001;
dynamic_request VARCHAR(300);
BEGIN
dynamic_request := 'SELECT '|| nValue || ' INTO tRow.' || col_name || ' FROM DUAL';
EXECUTE IMMEDIATE dynamic_request;
END;
Ok, I solved it !
Short answer : Using a global variable does the trick
Answer Development
Let us consider two facts about dynamic PL/SQL blocks (i.e., PL/SQL blocks written as strings, to be executed trough an EXECUTE IMMEDIATE statement)
[1] There is no such thing as variable scope when you create a dynamic PLSQL block. What I mean by that is, if you do something like this :
CREATE OR REPLACE PROCEDURE DynamicVariableAssignment(
theString IN VARCHAR2
)
IS
BEGIN
EXECUTE IMMEDIATE 'BEGIN theString := ''test''; END; ';
END;
it will simply not work because the scope of theString is not transfered to the dynamic PL/SQL block. In other words, the dynamic PL/SQL block doesn't "inherit" of any variable, wherever it is executed.
[2] You might say "OK, no panic, I can give input/output arguments to my dynamic PL/SQL block, right ?". Sure you can, but guess what : you can only give SQL types as in/out ! True PL/SQL types on the other hand, such as a myTable%rowtype, are not accepted as an input for a dynamic PL/SQL block. So the answer of hmmftg won't work either :
-- I've reduced the code to the interesting part
dynamic_request := 'BEGIN :t_row.' || col_name || ':= 0.001; END;';
EXECUTE IMMEDIATE dynamic_request USING IN OUT tRow;
-- (where tRow is of type myTable%ROWTYPE)
since tRow is of MyTable%ROWTYPE, it is not a valid SQL type and is therefore not valid as an input to the dynamic PL/SQL block.
The Solution Who would have thought that global variables would come and save the day ? As we said in [1], we have no reference to any variable outside the dynamic PL/SQL block. BUT we can still access global variables defined in package headers !
Let us assume that I have a package kingPackage in which I define the following :
tempVariable myTable%ROWTYPE;
Then I can do this :
FINAL CODE (body only)
-- Copy tRow into temp variable
kingPackage.tempVariable := tRow;
-- We modify the column of the temp variable
vString := 'BEGIN kingPackage.tempVariable.' || col_val || ' := ' || TO_CHAR(vNumber) ||'; END;';
EXECUTE IMMEDIATE vString;
-- The column value has been updated \o/
tRow := kingPackage.tempVariable;
There you go, fellas !
Have a nice day
try this:
CREATE OR REPLACE PROCEDURE ROW_CHANGER(
tRow IN MyTable%ROWTYPE,
col_name IN VARCHAR,
nValue IN NUMBER)
AS
dynamic_request VARCHAR(300);
BEGIN
dynamic_request := 'BEGIN :t_row.'||COL_NAME ||':= :n_value; END;';
EXECUTE IMMEDIATE dynamic_request
USING IN OUT TROW, IN nValue;
END;
this is because in your EXECUTE IMMEDIATE the tRow MyTable%ROWTYPE is not defined,
so we defined it with using statement.

Using nvarchar(MAX) to build query, but conversion fails in where clause

I have a stored procedure that uses a variable called #Command (nvarchar(MAX)). I then add parameters accordingly based on given input.
declare #Command nvarchar(max)
if(#CaseFileID IS NOT NULL)
BEGIN
select #Command='
select [ServerCredentialsID],[CaseFileID],EIKSLT.[LocationType],EPT.PaymentType,[TaskID],[DateActive]
,[LengthOfPurchase],[Username],[Password],[IPDomain],[Port],[DES],[Website],[AmountPaid],[Latitude]
,[Longitude],[HasAttachments],[TimeStamp],[CaseElement],[Temporary],[StatusID]
FROM Element17a_IKSServerCredentials EIKSSC
JOIN ElementsIKSLocationTypes EIKSLT ON EIKSSC.LocationBeingUsedID= EIKSLT.IKSLocationBeingUsedID
JOIN ElementsPaymentTypes EPT ON EIKSSC.PaymentMethodID=EPT.PaymentTypeID
where EIKSSC.CaseFileID='''+cast(#CaseFileID as nvarchar(MAX))+''' '
#CaseFileID is declared as an int, and in the table it is an int. When I try
where EIKSSC.CaseFileID = ' + #CaseFileID + ' '
then the value doesn't even show (in the error it looks like "EIKSSC.CaseFileID= '" )
I just don't get it.
NOTE: SQL Server 2008 Management Studio
It's because #CaseFileID is VARCHAR even though you don't show it.
Your IF should be
if(#CaseFileID > '')
And if even that doesn't work, then you need to swap to LEFT joins because INNER JOINs will remove records that cannot be matched in the other 2 tables.
Finally, because CaseFileID is an int, you don't need the quotes. Even though SQL Server will implicitly cast '9' to the integer 9 in the WHERE clause, it's just not necessary.
declare #Command nvarchar(max)
if(#CaseFileID > '')
BEGIN
select #Command='
select [ServerCredentialsID],[CaseFileID],EIKSLT.[LocationType],EPT.PaymentType,[TaskID],[DateActive]
,[LengthOfPurchase],[Username],[Password],[IPDomain],[Port],[DES],[Website],[AmountPaid],[Latitude]
,[Longitude],[HasAttachments],[TimeStamp],[CaseElement],[Temporary],[StatusID]
FROM Element17a_IKSServerCredentials EIKSSC
LEFT JOIN ElementsIKSLocationTypes EIKSLT ON EIKSSC.LocationBeingUsedID= EIKSLT.IKSLocationBeingUsedID
LEFT JOIN ElementsPaymentTypes EPT ON EIKSSC.PaymentMethodID=EPT.PaymentTypeID
where EIKSSC.CaseFileID='+cast(#CaseFileID as nvarchar(MAX))

Resources