Friday 26 August 2011

Semi-colons and SQL

Question:
Dear PL/SQL Product Manager:
The PL/SQL User Guide and Reference section entitled "Guidelines for Dynamic SQL" tells us that "When building up a single SQL statement in a string, do not include any semi-colon at the end. When building up a PL/SQL anonymous block, include the semi-colon at the end of each PL/SQL statement and at the end of the anonymous block."
This is precisely the advice I have given to my students over the years, but I am now puzzled by the following block of code:
BEGIN
EXECUTE IMMEDIATE
'CREATE OR REPLACE PROCEDURE x IS BEGIN NULL; END;';
END;
/
I am executing a single SQL statement in a string, yet I terminate that string with a semi-colon. And if I leave off the semi-colon, I encounter the following problem when I execute the block:
SQL> BEGIN
2 EXECUTE IMMEDIATE
3 'CREATE OR REPLACE PROCEDURE x IS BEGIN NULL; END';
4 END;
5 /
ERROR:
ORA-24344: success with compilation error
ORA-06512: at line 2

Warning: PL/SQL compilation errors.

SQL> sho err procedure x
Errors for PROCEDURE X:

LINE/COL ERROR
-------- -----------------------------------------------------------------
1/30 PLS-00103: Encountered the symbol "end-of-file" when expecting
one of the following:
;
delete exists prior
The symbol ";" was substituted for "end-of-file" to continue.
I'm confused. Does the documentation need to be fixed? When exactly do I need to attach a semi-colon, and what is the logic behind this?
Answer:
PL/SQL Product Manager Bryn Llewellyn replies:
Steven, the simple rule that you should keep in mind is that a SQL statement does not have a terminator. It's easy to see why: you can't issue more than one at a time. But, anyway, the rules are the rules.
SQL*Plus adds its own dimension because it needs some way to know you've stopped typing the SQL and want to run it. I prefer to do this using a single slash as the first and only character on a line. I enforce this discipline like this:
SET SQLTERMINATOR OFF
Actually, SQL*Plus allows you to chose any punctuation character. Try this:
SET SQLTERMINATOR .
drop table tbl.
create table tbl (n number).
insert into tbl(n) values (1).
select * from tbl.
By default SQL*Plus runs with SET SQLTERMINATOR ; -- and Oracle's install scripts rely on this.
Any script that runs OK in a SET SQLTERMINATOR OFF env will run OK in a SET SQLTERMINATOR ; env -- but not vice versa.
The result is that lots of people (including some third-party authors who will go unnamed in this column) don't have a clear mental model of what the SQL actually is, and tend to think that drop table tbl; with the trailing semicolon is the legal SQL rather than a SQL*Plus-ism.
Execute immediate simply needs a legal SQL statement -- and if it weren't for SQL*Plus there'd never be any confusion. I believe the section "When Do I Use or Omit the Semicolon with Dynamic SQL?" was put into the doc because the author had once been confused and hoped to save others the problem.
So what about your example -- which must be written like this:
BEGIN
EXECUTE IMMEDIATE
'CREATE OR REPLACE PROCEDURE x IS BEGIN NULL; END;';
END;
/

Well, the SQL statement is this:

create or replace
with no trailing semicolon. It's just that <PL/SQL unit> (of course) uses semicolons to separate its statements -- and requires that the final one has a trailing semicolon too. I hope you can see how a begin... end; block fits into this.
SQL*Plus -- as a final twist -- does not recognize the semicolon that follows the last statement in a PL/SQL command as the terminator when you are in SET SQLTERMINATOR ; mode. So for PL/SQL you anyway have to add the slash that I always prefer.
However, beware the trap that I've seen some people fall into. When in SET SQLTERMINATOR ; mode they write something like this in their SQL*Plus script:
alter procedure P compile reuse settings;
/
This runs with out error and so might go unnoticed. But it actually does the recompilation twice!
By the way, you often see statements like this:
set serveroutput on;
Actually, SQL*PLus commands (as this one is) don't require a semicolon because each is always no more than one line and so is terminated by the end-of-line.
The trailing semicolon is forgiven in SET SQLTERMINATOR ; mode but not in SET SQLTERMINATOR OFF mode. So another reason to use the latter is that it forces you to realize the difference between SQL*Plus commands and SQL

No comments:

Post a Comment