I have to use old school ADODB (not ADO.NET) to execute a statement that contains a Common Table Expression.
I am using (have to use) the SQLOLEDB provider.
The DML statement works fine when executing from a Windows 7 / Windows Server 2008 client but not from WinXP or Win2K3 server.
I have profiled the routine and found that the old OSes send a slightly different SQL statement.
Win7 + 2008 = exec sp_executesql N'WITH source(Vsl, Cpt, SrcTyp, SrcNum, Opn, JobNum, Qty, Cst, Vry, Reg, Vnt, Sbk) AS ...'
WinXP + Win2K3 = exec sp_executesql N'exec WITH source(Vsl, Cpt, SrcTyp, SrcNum, Opn, JobNum, Qty, Cst, Vry, Reg, Vnt, Sbk) AS ...'
Notice the extra 'exec' slipped into the command text.
It appears as if the verions of SQLOLEDB.1 on the old OSs mis-treats the WITH statement and sees it as needing a prepending 'exec'.
Can anyone shed some light on this. Is there an SQLOLEDB driver update that I can apply to the old OSes? or some other workaround.
(FYI, You should really revisit some of your existing questions, as most of them seem to have helpful answers that appear to address the question; your comments even suggest this is so. If they have an answer, please accept it).
If you really need to use a CTE here (meaning you're doing something recursive and aren't just using it for convenience instead of inline-selecting or inline-joining), the simplest and fastest workaround would probably be to include your SQL within your own call to sp_executesql. You'll end up nesting calls to it (which will look silly), but it shouldn't cause any actual problems.
Wrapping the query up in an sp_executesql statement works great if you don't have parameters in the query, otherwise the parameters aren't parsed because they're in a quoted string by the time they get to ADO, and this results in a syntax error.
What I did to resolve this was to create a TADOQuery descendant, which overrides the constructor, as follows:
constructor TCPADOQuery.Create(AOwner: TComponent);
begin
inherited;
TWideStringList(SQL).OnChange := LocalQueryChanged;
end;
LocalQueryChanged then checks if the query starts with a common table expression, and inserts a dummy declaration at the beginning of the query, which the XP ADO parser does understand. A CTE must be preceded by a semicolon if it is not the first statement in the query, so we have to fix that first:
procedure TCPADOQuery.LocalQueryChanged(Sender: TObject);
var
a: WideString;
begin
if not (csLoading in ComponentState) then
Close;
a := Trim(SQL.Text);
if Uppercase(copy(a, 1, 4)) = 'WITH' then a := ';' + a;
if Uppercase(copy(a, 1, 5)) = ';WITH' then
a := 'DECLARE #DummyForADO_XP BIT'#13#10 + a;
CommandText := a;
end;
This has resolved the problem, and has saved me having to rework all of my code where I use both CTEs and parameters.
Related
i have the following delphi code:
FDQuery1.SQL.Clear;
FDQuery1.SQL.Add('SELECT * FROM :Tablename');
FDQuery1.ParamByName('Tablename').AsString := 'tasks';
ShowMessage(FDQuery1.sql.Text);
FDQuery1.Open;
(coppied from this link: http://www.delphigroups.info/2/da/237634.html)
it does not work because the parameter is not filled but stays the same.
does somebody know why it is not filled?
Because you cannot use parameters for table name substitution in SQL commands in general. You are lucky enough here though, FireDAC supports preprocessor macros to parametrize table names in SQL commands. So you can write for example this (note that if you want to see the command as you did in your code, it must be after macro preprocessing, that is e.g. after calling Prepare):
FDQuery1.SQL.Text := 'SELECT * FROM &TableName';
FDQuery1.MacroByName('TableName').AsIdentifier := 'tasks';
FDQuery1.Open;
For details about this kind of macros, see the substitution variables topic.
To make a long story short I propose to discuss the code you see below.
When running it:
Oracle 11 compiler raises
"PLS-00306: wrong number or types of arguments tips in call to 'PIPE_TABLE'"
"PLS-00642: Local Collection Types Not Allowed in SQL Statement"
Oracle 12 compiles the following package with no such warnings, but we have a surprise in runtime
when executing the anonymous block as is - everything is fine
(we may pipe some rows in the pipe_table function - it doesn't affect)
now let's uncomment the line with hello; or put there a call to any procedure, and run the changed anonumous block again
we get "ORA-22163: left hand and right hand side collections are not of same type"
And the question is:
Does Oracle 12 allow local collection types in SQL?
If yes then what's wrong with the code of PACKAGE buggy_report?
CREATE OR REPLACE PACKAGE buggy_report IS
SUBTYPE t_id IS NUMBER(10);
TYPE t_id_table IS TABLE OF t_id;
TYPE t_info_rec IS RECORD ( first NUMBER );
TYPE t_info_table IS TABLE OF t_info_rec;
TYPE t_info_cur IS REF CURSOR RETURN t_info_rec;
FUNCTION pipe_table(p t_id_table) RETURN t_info_table PIPELINED;
FUNCTION get_cursor RETURN t_info_cur;
END buggy_report;
/
CREATE OR REPLACE PACKAGE BODY buggy_report IS
FUNCTION pipe_table(p t_id_table) RETURN t_info_table PIPELINED IS
l_table t_id_table;
BEGIN
l_table := p;
END;
FUNCTION get_cursor RETURN t_info_cur IS
l_table t_id_table;
l_result t_info_cur;
BEGIN
OPEN l_result FOR SELECT * FROM TABLE (buggy_report.pipe_table(l_table));
RETURN l_result;
END;
END;
/
DECLARE
l_cur buggy_report.t_info_cur;
l_rec l_cur%ROWTYPE;
PROCEDURE hello IS BEGIN NULL; END;
BEGIN
l_cur := buggy_report.get_cursor();
-- hello;
LOOP
FETCH l_cur INTO l_rec;
EXIT WHEN l_cur%NOTFOUND;
END LOOP;
CLOSE l_cur;
dbms_output.put_line('success');
END;
/
In further experiments we found out that problems are even deeper than it's been assumed.
For example, varying elements used in the package buggy_report we can get an ORA-03113: end-of-file on communication channel
when running the script (in the question). It can be done with changing the type of t_id_table to VARRAY or TABLE .. INDEX BY ... There are a lot of ways and variations leading us to different exceptions, which are off topic to this post.
The one more interesting thing is that compilation time of buggy_report package specification can take up to 25 seconds,
when normally it takes about 0.05 seconds. I can definitely say that it depends on presence of TYPE t_id_table parameter in the pipe_table function declaration, and "long time compilation" happen in 40% of installation cases. So it seems that the problem with local collection types in SQL latently appear during the compilation.
So we see that Oracle 12.1.0.2 obviously have a bug in realization of using local collection types in SQL.
The minimal examples to get ORA-22163 and ORA-03113 are following. There we assume the same buggy_report package as in the question.
-- produces 'ORA-03113: end-of-file on communication channel'
DECLARE
l_cur buggy_report.t_info_cur;
FUNCTION get_it RETURN buggy_report.t_info_cur IS BEGIN RETURN buggy_report.get_cursor(); END;
BEGIN
l_cur := get_it();
dbms_output.put_line('');
END;
/
-- produces 'ORA-22163: left hand and right hand side collections are not of same type'
DECLARE
l_cur buggy_report.t_info_cur;
PROCEDURE hello IS BEGIN NULL; END;
BEGIN
l_cur := buggy_report.get_cursor;
-- comment `hello` and exception disappears
hello;
CLOSE l_cur;
END;
/
Yes, in Oracle 12c you are allowed to use local collection types in SQL.
Documentation Database New Features Guide says:
PL/SQL-Specific Data Types Allowed Across the PL/SQL-to-SQL Interface
The table operator can now be used in a PL/SQL program on a collection whose data type is declared in PL/SQL. This also allows the data type to be a PL/SQL associative array. (In prior releases, the collection's data type had to be declared at the schema level.)
However, I don't know why your code is not working, maybe this new feature has still a bug.
I fiddled around your example. The trick how Oracle 12c can use PL/SQL collections in SQL statements is that Oracle creates surrogate schema object types with compatible SQL type attributes and uses these surrogate types in a query. Your case looks like a bug. I traced the execution and the surrogate types are created only once if not exist. So the effective type doesn't change nor recompile (don't know if implicit recompilation are done using ALTER statement) during execution of pipelined function. And the issue only occurs if you use the p parameter in pipe_table function. If you don't call l_table := p; the code executes successfully even with enabled method call.
I am pretty new to delphi and I would like to ask how can I create a correct SQL statement / SQL string in delphi.
I have tried something like this:
sql:='use [TestovaciaDb] INSERT INTO [dbo].[client]([Meno],[Priezvisko]) VALUES('+name+','+surname+')';
I am using MS SQL server 2012
But I am getting a exception there. Thank you
EDIT:
meno and priez are variables with values from TEdit1 and TEdit2:
meno:= Edit1.Text;
priez:= Edit2.Text;
Use parameterized queries. You set the database in your ConnectionString, so you don't need to `use' it in your query.
ADOQuery1.SQL.Text := 'INSERT INTO [dbo].[client] ([Meno],[Priezvisko]) ' +
'VALUES(:Meno, :Priezvisko)';
ADOQuery1.Parameters.ParamByName('Meno').Value := Edit1.Text;
ADOQuery1.Parameters.ParamByName('Priezvisko').Value := Edit2.Text;
ADOQuery1.ExecSQL;
Remove the use [xxx] at the begining of the statement. The connection you use must be already configured to point to the correct database. Just like many others said, avoid creating your sentences by using constants, instead, use paramenters.
http://docwiki.embarcadero.com/Libraries/XE3/en/System.SysUtils.QuotedStr
Use QuotedStr function.
For example
sql:='use [TestovaciaDb] INSERT INTO [dbo].[client]([Meno],[Priezvisko]) VALUES('+QuotedStr(name)+','+QuotedStr(surname)+')';
Use QuotedStr to convert the string S to a quoted string. A single quotation mark (') is inserted at the beginning and end of S, and each single quotation mark in the string is repeated. To remove the quotation marks from a quoted string, use the AnsiDequotedStr routine.
I am trying to check the input length, to see if it's less than 7. It should show an error message, but the code below doesn't work. What's wrong with it?
CREATE OR REPLACE PROCEDURE prc_staffContact(IN_staffID IN CHAR, IN_staffContact IN VARCHAR) IS
v_staffName VARCHAR(50);
v_staffID CHAR(6);
v_staffContact VARCHAR(11);
BEGIN
SELECT s.staffName, s.staffID, s.staffContact
INTO v_staffName, v_staffID, v_staffContact
FROM staff s
WHERE staffID = IN_staffID;
IF (LENGTH(IN_staffContact) < 7 )
THEN
DBMS_OUTPUT.PUT_LINE('Error. Contact number at least 7 digits.');
ELSE
UPDATE staff
SET staffContact = IN_staffContact
WHERE staffID = IN_staffID;
DBMS_OUTPUT.PUT_LINE('=============================================================================');
DBMS_OUTPUT.PUT_LINE('The contact number of [ ' ||v_staffName || ' ] has been updated successfully.');
DBMS_OUTPUT.PUT_LINE('New contact number: [ ' ||v_staffContact || ' ].');
DBMS_OUTPUT.PUT_LINE('=============================================================================');
END IF;
END;
/
You said something that appears to be contradictory:
it shows [PL/SQL procedure successfully completed.] but doesn't checking even my input is less than 7 characters. and the data doesn't update also.
You described that you're doing as:
i save it under procedure1.sql and i start it in sql plus. that is my 1st call. after that i call the exec prc_staffContact('100001', '0000')
Together those suggest that when you say the data isn't updated, what you really mean is you don't get the contact number/new contact number message from the else branch, and I think you're assuming that means the update doesn't happen either, so it didn't execute either branch. But you must have gone into either the if or the else, by definition.
So if you didn't get either message, then you haven't done:
set serveroutput on
in SQL*Plus before calling exec. That setting is off by default, unless you have it turned on in your login.sql or glogin.sql, so you have to turn it on explicitly if you want to see dbms_output messages.
In this case, for the validation, (a) you probably want the select inside the elsef` too, partly because (b) if the passed values doesn't exist you'll get a no_data_found exception, and (c) you might want to consider throwing an exception if the length is less than 7 rather than (only) displaying a message. Someone else calling this might not have serverout on either, or might be using a different client that doesn't have that option.
You've also got v_staffID defined as char(6). Apart from wondering why that isn't a varchar2, the length you've given it means that if IN_staffID is 7 chars or more, the select into will get a 'character string buffer too small' error. I'd declare that as:
v_staffID staff.staffID%TYPE;
... to avoid issues like that, and the same for the other fields that relate to table columns.
And your 'success' message is showing the old contact number, not the new one. Not sure you need v_staffContact at all.
Look at your code carefully. May be your variable types not compatible or stored procedure parameters not compatible with other variables (or table columns, column's types). I tested your code in my database, all success. but may be error not data found in your select statement, or may be buffer too small error. Hope this help you. thanks
I have a stored procedure in this form:
ALTER PROCEDURE [dbo].[fooBar]
(
)
AS
BEGIN
-- etc
RETURN #Success
END
It had been working perfectly with BEGIN and END in it before, but after changing something and reverting it back, it refused to execute, pointing to a syntax error at the last END (removing that next pointed to a syntax error at the first IF/BEGIN/.... statement inside the procedure, and thus begins your wild goose chase).
Looking at the MSDN official documentation for the syntax, BEGIN and END used in this way to envelope a stored procedure is illegal. (Removing the BEGIN and END solved the problem)
Question: Why did this happen?
Did the compiler skip over this BEGIN and END initially and later discover it? Are there some things that the SQL compiler ignores? Is it legacy? Is it just finicky? Am I missing a hotfix?
This is SQL Server 10.50.1617
BEGIN/END are perfectly valid for a stored procedure. This is explicitly set in the grammar at the documentation page you linked to.
It is the parentheses that are not allowed.