Why did this the SQL parser execute this illegal syntax? - sql

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.

Related

MS sql 2008 try catch still throws exception

I worried all day.
My label is very big - '20317302009001'.
Zlecenie is int column - so sql generates error when compare zlecenie=#label.
I tried to catch it, but still get message:
Msg 248, Level 16, State 1, Procedure label_check, Line 9
The conversion of the varchar value '20317302009001' overflowed an int column.
Who knows the answer?
Thank you!
begin TRY
if (#komponent is null) and ISNUMERIC(#label)=1
begin
set #komponent=null
if exists(select * from Rejestr_zuzycia_tkaniny where zlecenie=#label)
begin
declare #program int;
select #program=program from Rejestr_zuzycia_tkaniny where zlecenie=#label
select #komponent=komponent from Komponenty_programu where program=#program
end;
end;
end TRY
begin CATCH
set #komponent=null
end CATCH
From your code, it looks like you don't actually use zlecenie as a number, so you might want to compare by casting it as a varchar first like so:
if exists(select * from Rejestr_zuzycia_tkaniny where cast(zlecenie as varchar(20))=#label)
However, if you do need to process zlecenie as a number later on e.g. add it to something, then you might want to make it a bigint instead of int to accommodate large values.
MSDN has this to say about TRY...CATCH in T-SQL:
The following types of errors are not handled by a CATCH block when they occur at the same level of execution as the TRY…CATCH construct:
Compile errors, such as syntax errors, that prevent a batch from running.
Errors that occur during statement-level recompilation, such as object name resolution errors that occur after compilation because of deferred name resolution.
I believe that the arithmetic overflow error might belong to the second scenario, which would explain why the CATCH block does not handle it. However, I have not been able to get any corroboration elsewhere so I suggest you do not just take my word for it.

Oracle SQL procedure to check input length

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

Selecting large sequence value into global variable not working in PL/SQL code

I have a script where I would like to have a global variable to store a transaction number for later use. The code is working fine on one schema where the sequence value that I'm fetching is relatively low. It is not working on another schema with a higher sequence value where I get a "Numeric Overflow". If I change that sequence value to a lower number it is working as well but that is not an option.
VAR TRANSACTIONNR NUMBER;
BEGIN
--Works with NEXTVAL being around 946713241
--Doesn't work with NEXTVAL being around 2961725541
SELECT MY_SEQUENCE.NEXTVAL INTO :TRANSACTIONNR FROM DUAL;
MY_PACKAGE.STARTTRANSACTION(:TRANSACTIONNR);
END;
/
-- SQL Statements
BEGIN
MY_PACKAGE.ENDTRANSACTION;
MY_PACKAGE.DO_SOMETHING(:TRANSACTIONNR);
END;
/
What is also working is selecting the sequence into a variable declared in the DECLARE block:
DECLARE
TRANSACTIONNR NUMBER;
BEGIN
SELECT MY_SEQUENCE.NEXTVAL INTO TRANSACTIONNR FROM DUAL;
MY_PACKAGE.STARTTRANSACTION(TRANSACTIONNR);
END;
/
But that means I won't be able to reuse it in the block at the end. Setting the size of the number is not possible.
VAR TRANSACTIONNR NUMBER(15)
is not valid.
Any ideas what I could try or other ways to store global state?
On further investigation this looks like it might be a SQL Developer bug (making assumptions about what you're doing again, of course...). I can get the same error with:
VAR TRANSACTIONNR NUMBER;
BEGIN
SELECT 2961725541 INTO :TRANSACTIONNR FROM DUAL;
END;
/
It appears that SQL Developer's NUMBER is limited to 2^31, which isn't the case normally for Oracle.
A possibly workaround is to use BINARY_FLOAT to store the value, but you'll run into precision problems eventually (not sure where, but looks OK up to 2^53-ish), and you'll need to cast() it back to NUMBER when using it.
VAR TRANSACTIONNR BINARY_DOUBLE;
BEGIN
SELECT 2961725541 INTO :TRANSACTIONNR FROM DUAL;
-- dbms_output.put_line(cast(:TRANSACTIONNR as NUMBER)); -- null for some reason
END;
/
...
BEGIN
dbms_output.put_line(cast(:TRANSACTIONNR as NUMBER));
END;
/
For some reason I don't seem to be able to refer to the bind variable again in the anonymous block I set it in - it's null in the commented-out code - which seems to be another SQL Developer quirk, regardless of the var type; but as you were doing so in your code, I may again have assumed too much...
Original answer for posterity, as it may still be relevant in other circumstances...
Presumably you're doing something to end the current transaction, e.g. a commit in endtransaction; otherwise you could just refer to my_sequence.currval in the do_something call. A number variable is fine for this size of number though, it won't have a problem with a sequence that size, and it won't make any difference that it is from a sequence rather than manually assigned. I don't think the problem is with the storage or the sequence.
It seems rather more likely that the error is coming from one of the package procedures you're calling, though I can't quite imagine what you might be doing with it; something like this will cause the same error though:
create sequence my_sequence start with 2961725541;
create package my_package as
procedure starttransaction(v_num number);
procedure endtransaction;
procedure do_something(v_num number);
end my_package;
/
create package body my_package as
procedure starttransaction(v_num number) is
begin
dbms_output.put_line('starttransaction(): ' || v_num);
for i in 1..v_num loop
null;
end loop;
end starttransaction;
procedure endtransaction is
begin
dbms_output.put_line('endtransaction()');
end endtransaction;
procedure do_something(v_num number) is
begin
dbms_output.put_line('do_something(): ' || v_num);
end do_something;
end my_package;
/
When your code is run against that it throws your error:
BEGIN
*
ERROR at line 1:
ORA-01426: numeric overflow
ORA-06512: at "STACKOVERFLOW.MY_PACKAGE", line 6
ORA-06512: at line 5
endtransaction()
do_something():
Note the error is reported against line 6 in the package, which is the for ... loop line, not from the assignment in your anonymous block.
Looping like that would be an odd thing to do of course, but there are probably other ways to generate that error. The breakpoint for it working is if the nextval is above 2^31. If I start the sequence with 2147483647 it works, with 2147483648 it errors.
I'm assuming you are actually getting an ORA-01426 from the original question; if it's actually a ORA-1438 or ORA-06502 then it's easier to reproduce, by trying to assign the value to a number(9) column or variable. 'Numeric overflow' is pretty specific though.

Common Table Expression Error

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.

BREAK description in BOL

SQL Server 2005 BOL says about BREAK:
Exits the innermost loop in a WHILE or IF…ELSE statement.
I don't understand this part about IF…ELSE. It looks like you can put BREAK inside IF. Actually BREAK works only inside WHILE, and control flow pass after loop's END. BREAK not in a WHILE context fires error, and that's completely OK.
My question is - why documentation mention IF...ELSE?
BTW, SQL Server 2000 BOL says only this:
Exits the innermost WHILE loop.
SQL 2008 BOL says:
Exits the innermost loop in a WHILE
statement or an IF…ELSE statement
inside a WHILE loop.
which is probably what the SQL 2005 documentation should have said.
The SQL2008 explanation still seems somewhat confusing however. To me it implies that break in an IF…ELSE statement inside a WHILE loop would just exit the IF…ELSE. This is not the case.
DECLARE #i INT = 0
WHILE #i<10
BEGIN
IF(#i=3)
BEGIN
PRINT 'i=3'
BREAK
END
ELSE
BEGIN
PRINT 'i<>3'
END
SET #i = #i+1
END
PRINT 'out of while'
Prints
i<>3
i<>3
i<>3
i=3
out of while