When I run this query
DECLARE
num NUMBER;
BEGIN
SELECT COUNT(*) INTO num FROM user_all_tables WHERE TABLE_NAME=upper('DatabaseScriptLog')
;
IF num < 1 THEN
CREATE TABLE DatabaseScriptLog
(ScriptIdentifier VARCHAR(100) NOT NULL,
ScriptType VARCHAR(50),
StartDate TIMESTAMP,
EndDate TIMESTAMP,
PRIMARY KEY (ScriptIdentifier)
);
END IF;
END;
When execute the above, I got the following:
PLS-00103: Encountered the symbol
"CREATE" when expecting one of the
following:
begin case declare exit for goto if
loop mod null pragma raise return
select update while with << close current delete
fetch lock insert open rollback
savepoint set sql execute commit
forall merge pipe
06550. 00000 - "line %s, column %s:\n%s"
*Cause: Usually a PL/SQL compilation error.
You cannot run DDL statements like that. You need to use dynamic SQL (EXECUTE IMMEDIATE).
IF num < 1 THEN
EXECUTE IMMEDIATE 'CREATE TABLE DatabaseScriptLog (ScriptIdentifier VARCHAR(100) NOT NULL, ScriptType VARCHAR(50), StartDate TIMESTAMP, EndDate TIMESTAMP, PRIMARY KEY (ScriptIdentifier))'
END IF;
You cannot do this like you could in SQLServer. You need to execute the create code through a stored procedure that is already in the proper schema. You pass the create code as a parameter and the stored procedure that has the correct privileges does it for you.
I use a version script that updates the schema to the latest by running schema altering operations separated by if-then clauses to check what version the db is at. After altering it increments the version so that the next if statements test passes and so on. If you are up to date and run the script the ifs skip all altering code. If your db is at version 46 and you run the script which has all changes up to 50, you execute only the blocks that represent versions 47-50.
You could execute immediate but would need elevated privileges which I would not recommend.
Hope this helps.
Related
I'd like to use the max value to feed the creation of a sequence. For instance, I am getting the max id value and I'd like to use this value:
DO $$
DECLARE
min_value Integer;
BEGIN
SELECT MAX(tmp.id)+1 into min_value FROM tmp;
-- raise notice 'Value: %', min_value;
CREATE SEQUENCE id_seq_tmp
INCREMENT 1
START :tmp.min_value --Getting error: syntax error at or near ":"
MINVALUE :tmp.min_value;
END $$;
How do I refer to the max value and pass to the sequence creation? I am using psql (PostgreSQL) 13.3.
The fundamental roadblock is that DDL commands ("utility statements") do not allow parameter substitution at all. You need to build the query string and execute it.
Generic SQL
I suggest format() for the task:
DO
$do$
BEGIN
EXECUTE format('
CREATE SEQUENCE id_seq_tmp
INCREMENT 1
START %1$s
MINVALUE %1$s'
, (SELECT max(tmp.id)+1 FROM tmp));
END
$do$;
The manual:
Another restriction on parameter symbols is that they only work in
SELECT, INSERT, UPDATE, and DELETE commands. In other statement types
(generically called utility statements), you must insert values
textually even if they are just data values.
See:
“ERROR: there is no parameter $1” in “EXECUTE .. USING ..;” statement in plpgsql
Creating user with password from variables in anonymous block
Operating from psql
While working from psql you can use \gexec to execute a the dynamically built DDL statement directly:
SELECT format('CREATE SEQUENCE id_seq_tmp INCREMENT 1 START %1$s MINVALUE %1$s', max(big_id)+1) FROM b2\gexec
See:
How to pass variable to PL/pgSQL code from the command line?
Filter column names from existing table for SQL DDL statement
(Either way, if the table tmp can be empty, you need to do more, as the SELECT query comes up empty.)
I am trying to use v('APP_USER') as default value for a column. I get null when I use it in select like
select v('APP_USER') from dual;
But when I use it as default in column, like below, I am getting error.
create table test_table (col_1 varchar2(50) default NVL(v('APP_USER'), SYS_CONTEXT('USERENV','OS_USER')));
Error
create table test_table (col_1 varchar2(50) default NVL(v('APP_USER'), SYS_CONTEXT('USERENV','OS_USER')))
Error report -
ORA-04044: procedure, function, package, or type is not allowed here
04044. 00000 - "procedure, function, package, or type is not allowed here"
*Cause: A procedure, function, or package was specified in an
inappropriate place in a statement.
*Action: Make sure the name is correct or remove it.
Can anyone explain this or have a turnaround for this ??
There are other options than V('APP_USER'). Since Apex 5, the APP_USER is stored in the sys_context and that is a lot more performant than the V() function. It is available as SYS_CONTEXT('APEX$SESSION','APP_USER').
It also works as a default value for tables:
create table test_table
(col_1 VARCHAR2(100) DEFAULT SYS_CONTEXT('APEX$SESSION','APP_USER'));
Table TEST_TABLE created.
That being said, the best practice for audit columns is a trigger that populates the the 4 audit columns (as #Littlefoot suggested). Have a look at quicksql (under SQL Workshop > Utilities or on livesql.oracle.com). You can have it generate the triggers for you if you set "include Audit columns" and "Apex Enabled". An example of such a generated trigger is:
create or replace trigger employees_biu
before insert or update
on employees
for each row
begin
if inserting then
:new.created := sysdate;
:new.created_by := nvl(sys_context('APEX$SESSION','APP_USER'),user);
end if;
:new.updated := sysdate;
:new.updated_by := nvl(sys_context('APEX$SESSION','APP_USER'),user);
end employees_biu;
/
One option is to use a database trigger, e.g.
CREATE OR REPLACE TRIGGER trg_biu_test
BEFORE INSERT OR UPDATE ON test
FOR EACH ROW
BEGIN
:new.col1 := v ('APP_USER');
END;
/
We want to run a simple SQL statement (dropping and creating an index), but only if the old index has not already been deleted. After looking up the syntax for IF in DB2, I came up with this:
IF EXISTS (SELECT indname FROM SYSCAT.INDEXES WHERE INDNAME = 'TEST_CREATE_INDEX_OLD')
THEN
DROP INDEX TEST_CREATE_INDEX_OLD;
create index TEST_CREATE_INDEX_NEW on example_table
(
id,
another_field
);
END IF;
When run with either SQuirrel (already setup to run with db2) or via command line, this script results in an error:
An unexpected token "IF EXISTS (SELECT indname FROM SYSCAT.INDEX" was
found following "BEGIN-OF-STATEMENT". Expected tokens may include:
"".. SQLCODE=-104, SQLSTATE=42601, DRIVER=4.23.42 SQL Code:
-104, SQL State: 42601
So - what am I doing wrong? Am I missing something, or is there another way to achieve my goal (check for $thing in database, execute appropriate query) that so far has not occured to me?
If IF statement is valid only in a compound-SQL block (i.e. inside a stored-procedure/routine/function/anonymous-block).
It's not valid standalone as your question shows, and that is why Db2 throws the -104 error.
You can look up the explanation of the sqlcode -104 by looking up SQL0104N in the free online Db2 Knowledge Center at this link.
To be able to use compound-SQL in your Squirrel-SQL tool, you need to configure squirrel to use an alternative statement terminator. Google that. In the examples below, I show a statement terminator of # (to delimit the block).
Here are two different ways to do what you want with Db2-Linux/Unix/Windows, each uses an anonymous block. Other approaches are possible.
In this example the drop-index will only run if the index-name exists in the current schema:
begin
declare v_index_exists integer default 0;
select 1 into v_index_exists
from syscat.indexes
where indname = 'TEST_CREATE_INDEX_OLD';
if v_index_exists = 1
then
execute immediate('drop index TEST_CREATE_INDEX_OLD');
execute immediate('create index TEST_CREATE_INDEX_NEW on example_table ( id, another_field)');
end if;
end#
In this example the drop-index will always run, but the block won't abort if the index does'nt exist (i.e. it will continue and not throw any error).
begin
declare v_no_such_index integer default 0;
declare not_exists condition for sqlstate '42704';
declare continue handler for not_exists set v_no_such_index=1;
execute immediate('drop index TEST_CREATE_INDEX_OLD');
execute immediate('create index TEST_CREATE_INDEX_NEW on example_table ( id, another_field)');
end#
You must use another statement delimiter, if you want to use db2 compound statement.
In Squirrel: Session -> Session Properties -> SQL -> Statement Separator = #.
Indexes in Db2 are fully qualified by 2 SYSCAT.INDEXES columns: INDSCHEMA and INDNAME. So, it's advisable to use both these fields in a SELECT statement on SYSCAT.INDEXEX as in example.
You can't use static DDL statements in a compound statement. Use EXECUTE IMMEDIATE statements instead.
Below is an example emulating UPDATE INDEX for the index in a schema equal to CURRENT SCHEMA special registry set in the session.
BEGIN
IF EXISTS (SELECT indname FROM SYSCAT.INDEXES WHERE INDSCHEMA = CURRENT SCHEMA AND INDNAME = 'TEST_CREATE_INDEX_OLD')
THEN
EXECUTE IMMEDIATE 'DROP INDEX TEST_CREATE_INDEX_OLD';
EXECUTE IMMEDIATE'
create index TEST_CREATE_INDEX_NEW on example_table
(
id,
another_field
)
';
END IF;
END
#
I have written a oracle stored procedure and creating an error log table if that doesn't exist.
SELECT COUNT(*)
INTO v_count
FROM all_tables
WHERE TABLE_NAME = 'ERROR_LOG';
IF v_count =0 THEN
cr_table := 'CREATE TABLE ERROR_LOG ( ERROR_LOG_ID NUMBER GENERATED BY DEFAULT ON NULL AS IDENTITY, IDENTIFIER VARCHAR2(100), ERROR_MESSAGE VARCHAR2(1000),created_by varchar2(100 ), created_date TIMESTAMP DEFAULT systimestamp )';
execute immediate cr_table;
then issuing an insert statement below in the code as
INSERT
INTO error_log
(
identifier,
error_message,
created_by
)
VALUES
(
v_identifier,
'Success',
v_user
);
But the SP is throwing compilation error with
PL/SQL: ORA-00942: table or view does not exist
If I create the table manually, offline, and compile then it works.
any help?
if you use execute immediate to create a table, it exists in the PL/SQL context, not in SQL context, you have to execute the INSERT by using execute immedation too.
Or you have to make the insert inside an other BEGIN.. END block.
Before using the stored procedure, please create a view or table where you are going to insert the values using the SP. So it will avoid the compiler error in the run time.
This is too long for a comment.
The table doesn't exist when the code is compiled. Hence, you are getting a compile-time error. At compilation time, Oracle doesn't know that the table will exist when executed.
One solution is to use dynamic SQL for the insert as well.
A better solution is to set up the database with the appropriate tables before the code can ever be executed. Creating permanent tables dynamically in conditional code is usually a sign of a poorly designed application.
IF I create this trigger, then the error is raised when drop or truncate is used on tables, but there is nothing inserted into logTable, but if I delete RAISE_APPLICATION_ERROR... then the values are inserted into logTable, but the drop/truncate are executed too. Why? How can I avoid drop/truncate on Schema (If I use instead of trigger, it is fired only if owner of the schema is dropping/truncating something).
CREATE OR REPLACE TRIGGER trigger_name
BEFORE DROP OR TRUNCATE ON DATABASE
DECLARE
username varchar2(100);
BEGIN
IF ora_dict_obj_owner = 'MySchema' THEN
select user INTO username from dual;
INSERT INTO logTable VALUES(username , SYSDATE);
RAISE_APPLICATION_ERROR (-20001,'ERROR, YOU CAN NOT DELETE THIS!!');
END IF;
END;
According to the documentation:
Statement-Level Atomicity
Oracle Database supports statement-level atomicity, which means that a SQL statement is an atomic unit of work
and either completely succeeds or completely fails.
A successful statement is different from a committed transaction. A
single SQL statement executes successfully if the database parses and
runs it without error as an atomic unit, as when all rows are changed
in a multirow update.
If a SQL statement causes an error during execution, then it is not
successful and so all effects of the statement are rolled back. This
operation is a statement-level rollback.
The procedure is a PL/SQL statement, it is atomic, if you raise an error within the procedure, then the whole procedure fails and Oracle performs a rollback of all the changes done by this procedure.
But you can create a procedure with AUTONOMOUS_TRANSACTION Pragma in order to bypass this behaviour, in this way:
CREATE TABLE logtable(
username varchar2(200),
log_date date
);
CREATE OR REPLACE PROCEDURE log_message( username varchar2 ) IS
PRAGMA AUTONOMOUS_TRANSACTION;
BEGIN
INSERT INTO logtable( username, log_date ) VALUES ( username, sysdate );
COMMIT;
END;
/
CREATE OR REPLACE TRIGGER trigger_name
BEFORE DROP OR TRUNCATE ON DATABASE
DECLARE
username varchar2(100);
BEGIN
IF ora_dict_obj_owner = 'TEST' THEN
log_message( user );
RAISE_APPLICATION_ERROR (-20001,'ERROR, YOU CAN NOT DELETE THIS!!');
END IF;
END;
And now:
drop table table1;
ORA-00604: error occurred at recursive SQL level 1
ORA-20001: ERROR, YOU CAN NOT DELETE THIS!!
ORA-06512: at line 6
00604. 00000 - "error occurred at recursive SQL level %s"
*Cause: An error occurred while processing a recursive SQL statement
(a statement applying to internal dictionary tables).
*Action: If the situation described in the next error on the stack
can be corrected, do so; otherwise contact Oracle Support.
select * from logtable;
USERNAME LOG_DATE
-------- -------------------
TEST 2018-04-27 00:16:34