How do I check if a temporary table exists in SQL Anywhere? - sql

I want to write a SQL IF statement that checks whether or not a local temporary table exists, but those kinds of tables are not recorded in the SQL Anywhere system catalog.

Note that you can do this in 11.0.1 and higher:
DROP TABLE IF EXISTS t;

If you're asking the question, "How do I drop a local temporary table without raising an error if it doesn't exist?" then the answer's simple: just DROP it and ignore any error:
BEGIN
DROP TABLE t;
EXCEPTION WHEN OTHERS THEN
END;
If you really need to know the answer to the question "Does table t exist?" you can query the table and analyze the resulting SQLSTATE. The following function makes use of several features:
ON EXCEPTION RESUME ignores any exception raised by the SELECT and passes control to the IF statement.
EXECUTE IMMEDIATE lets you write a query where the table name is in a string variable.
TOP 1 makes sure that only one row is selected even if the table contains a million rows.
ORDER BY 1 lets you meet the requirement that TOP can only be used when the result set is ordered.
SELECT 1 relieves you of the burden of specifying a column name.
INTO #dummy means the SELECT (and consequently the EXECUTE IMMEDIATE) doesn't return a result set.
If the SELECT works, it's either going to set SQLSTATE to '00000' for success or '02000' for row not found. Any other SQLSTATE means there's some serious problem with the table... like it doesn't exist.
CREATE FUNCTION f_table_is_ok
( IN #table_name VARCHAR ( 128 ) )
RETURNS INTEGER
ON EXCEPTION RESUME
BEGIN
DECLARE #dummy INTEGER;
EXECUTE IMMEDIATE STRING (
'SELECT TOP 1 1 INTO #dummy FROM ',
#table_name,
' ORDER BY 1' );
IF SQLSTATE IN ( '00000', '02000' ) THEN
RETURN 1
ELSE
RETURN 0
END IF;
END;
Here's some test code:
BEGIN
DECLARE LOCAL TEMPORARY TABLE tt ( c INTEGER );
DECLARE LOCAL TEMPORARY TABLE "t t" ( c INTEGER );
SELECT f_table_is_ok ( 'asdf' );
SELECT f_table_is_ok ( 'tt' );
SELECT f_table_is_ok ( '"t t"' );
SELECT f_table_is_ok ( '"SYS"."SYSTABLE"' );
END;

just try to drop it anyways and ignore the error...
BEGIN
DROP TABLE table;
EXCEPTION WHEN OTHERS THEN
END;

Related

Executing prepared SQL in function without loop returning a table

I have this source here:
CREATE OR REPLACE FUNCTION SWITCHTEST (CHOICE VARCHAR(10))
RETURNS TABLE ( R_COL1 VARCHAR(1024) ,R_COL2 VARCHAR(1024) )
LANGUAGE SQL
MODIFIES SQL DATA
BEGIN
DECLARE SQLSTATE CHAR(5);
DECLARE SELECT1 VARCHAR(1024);
DECLARE L_COL1 VARCHAR(1024);
DECLARE L_COL2 VARCHAR(1024);
SET SELECT1 = 'SELECT TEST, DESCR FROM TESTTAB FETCH FIRST 10 ROWS ONLY';
SET SELECT2 = 'SELECT DESCR, COLOUR FROM TESTTAB FETCH FIRST 10 ROWS ONLY';
IF CHOICE = 'FIRST' THEN
PREPARE S1 FROM SELECT1;
ELSEIF CHOICE = 'SECOND' THEN
PREPARE S1 FROM SELECT2;
ELSE
END IF;
RETURN EXEC(S1);
END#
Calling it like
SELECT * FROM TABLE (SWITCHTEST('FIRST')) #
It should just execute the SQL in the S1 prepared statement and I don't want to use a loop in the function.
I am running DB2 Windows 10.5
Any ideas on how to fix this?
I know that the EXEC(S1) is wrong but I can't find anything on the IBM page that shows how to make this work.
Thank you for your help.
Viking
Using intermediate CREATED GLOBAL TEMPORARY TABLE.
--#SET TERMINATOR #
CREATE GLOBAL TEMPORARY TABLE SWITCHTEST ( R_COL1 VARCHAR(1024) ,R_COL2 VARCHAR(1024)) ON COMMIT PRESERVE ROWS NOT LOGGED#
CREATE OR REPLACE FUNCTION SWITCHTEST (CHOICE VARCHAR(10))
RETURNS TABLE (R_COL1 VARCHAR(1024), R_COL2 VARCHAR(1024) )
LANGUAGE SQL
MODIFIES SQL DATA
BEGIN ATOMIC
DELETE FROM SWITCHTEST;
IF CHOICE='FIRST' THEN
INSERT INTO SWITCHTEST SELECT TEST, DESCR FROM TESTTAB FETCH FIRST 10 ROWS ONLY;
ELSEIF CHOICE='SECOND' THEN
INSERT INTO SWITCHTEST SELECT DESCR, COLOUR FROM TESTTAB FETCH FIRST 10 ROWS ONLY;
END IF;
RETURN SELECT R_COL1, R_COL2 FROM SWITCHTEST;
END#
Not sure if it fits your needs, but you can use a case expression to choose columns for the result table. I don't think fetch first is supported in functions, but you can add that in the call:
CREATE OR REPLACE FUNCTION SWITCHTEST (CHOICE VARCHAR(10))
RETURNS TABLE ( R_COL1 VARCHAR(1024) ,R_COL2 VARCHAR(1024) )
LANGUAGE SQL
READS SQL DATA
RETURN
SELECT CASE CHOICE WHEN 'FIRST' THEN TEST ELSE DESCR END
, CASE CHOICE WHEN 'FIRST' THEN DESCR ELSE COLOUR END
FROM TESTTAB ;
SELECT * FROM TABLE (SWITCHTEST('FIRST'))
FETCH FIRST 10 ROWS ONLY;

How to write a procedure to display the contents of a table in sql

I have a created a procedure as
create or replace procedure availability(num in number) as
begin
delete from vehicle_count;
insert into vehicle_count from select engine_no,count(engine_no)
from vehicle
where engine_no = num
group by engine_no;
end;
/
The procedure was created successfully but now i have to write a separate query to view the contents of vehicle_count as
select * from vehicle_count;
I tried inserting the select statement into the procedure after insertion but it showed a error stating "an INTO clause is expected in the select statement".
How can I create procedure to select the required contents and display it in a single execute statement?
Table schema
vehicle(vehicle_no,engine_no,offence_count,license_status,owner_id);
vehicle_count(engine_no,engine_count);
Check this (MS SQL SERVER)-
create or alter procedure availability(#num as int) as
begin
delete from vehicle_count;
insert into vehicle_count
output inserted.engine_no,inserted.count_engine_no
select engine_no,count(engine_no) as count_engine_no
from vehicle
where engine_no=#num
group by engine_no;
end;
If you want to use a SELECT into a PL/SQL block you should use either a SELECT INTO or a loop (if you want to print more rows).
You could use something like this:
BEGIN
SELECT engine_no, engine_count
INTO v_engine, v_count
FROM vehicle_count
WHERE engine_no = num;
EXCEPTION
WHEN NO_DATA_FOUND THEN
v_engine := NULL;
v_count := NULL;
END;
v_engine and v_count are two variables. You can declare them in your procedure, and they will contain the values you want to print.
You said that the procedure you wrote (actually, you posted here) compiled successfully. Well, sorry to inform you - that's not true. This is not a valid syntax:
insert into vehicle_count from select engine_no,count(engine_no)
----
from? Here?
Consider posting true information.
As of your question (if we suppose that that INSERT actually inserted something into a table):
at the beginning, you delete everything from the table
as SELECT counts number of rows that share the same ENGINE_NO (which is equal to the parameter NUM value), INSERT inserts none (if there's no such NUM value in the table) or maximum 1 row (because of aggregation)
therefore, if you want to display what's in the table, all you need is a single SELECT ... INTO statement whose result is displayed with a simple DBMS_OUTPUT.PUT_LINE which will be OK if you're doing it interactively (in SQL*Plus, SQL Developer, TOAD and smilar tools). Regarding table description, I'd say that ENGINE_NO should be a primary key (i.e. that not more than a single row with that ENGINE_NO value can exist in a table).
create or replace procedure availability (num in number) as
l_engine_no vehicle_count.engine_no%type;
l_engine_count vehicle_count.engine_count%type;
begin
delete from vehicle_count;
insert into vehicle_count (engine_no, engine_count)
select engine_no, count(engine_no)
from vehicle
where engine_no = num
group by engine_no;
-- This query shouldn't return TOO-MANY-ROWS if ENGINE_NO is a primary key.
-- However, it might return NO-DATA-FOUND if there's no such NUM there, so you should handle it
select engine_no, engine_count
into l_engine_no, l_engine_count
from vehicle_count
where engine_no = num;
dbms_output.put_line(l_engine_no ||': '|| l_engine_count);
exception
when no_data_found then
dbms_output.put_line('Nothing found for ENGINE_NO = ' || num);
end;
/
There are numerous alternatives to that (people who posted their answers/comments before this one mentioned some of those), and the final result you'd be satisfied with depends on where you want to display that information.

Drop table if it exists with DB2/400 SQL

My goal is pretty straightforward - if table has rows, drop it.
Despite the fact that currently there are several similar answers none of them worked for me.
DB2 Drop table if exists equivalent
Suggested solution:
IF EXISTS (SELECT name FROM sysibm.systables WHERE name = 'mylib.mytable') THEN
DROP TABLE mylib.mytable;END IF;
Result:
SQL State: 42601 Vendor Code: -199 Message: [SQL0199] Keyword IF not expected.
Valid tokens: ( CL END GET SET CALL DROP FREE HOLD LOCK OPEN WITH ALTER BEGIN
Drop DB2 table if exists
Suggested solution:
--#SET TERMINATOR #
begin
declare statement varchar(128);
declare continue handle for sqlstate '42710' BEGIN END;
SET STATEMENT = 'DROP TABLE MYLIB.MYTABLE';
EXECUTE IMMEDIATE STATEMENT;
end #
Result:
Message: [SQL0104] Token HANDLE was not invalid. Valid tokens: HANDLER or, if replace handle with handler:
Message: [SQL0199] Keyword STATEMENT not expected. Valid tokens: SQL PATH RESULT SCHEMA CURRENT CONNECTION DESCRIPTOR.
From answer about views
Suggested solution:
DROP TABLE MY_TABLE ONLY IF EXISTS source.
Result:
Message: [SQL0104] Token ONLY was not invalid. Valid tokens: RESTRICT CASCADE
So, I wonder if an alternate solution exists. CL solution is also interesting.
I'm assuming you may want to do this more than once, so a procedure might be in order.
CREATE or replace PROCEDURE DROP_LIVE_TABLE
(in #table varchar(10)
,in #library varchar(10)
)
BEGIN
declare #stmt varchar(100);
declare #cnt int;
IF exists( select * from systables
where sys_dname = #library
and sys_tname = #table
and table_type in ('P','T')
) THEN
SELECT int(sum(number_rows))
INTO #cnt
from SYSTABLESTAT
where sys_dname = #library
and sys_tname = #table
;
IF #cnt > 0 THEN
set #stmt = 'DROP TABLE '||#library||'.'||#table||' CASCADE';
execute immediate #stmt;
END IF;
END IF;
RETURN;
END;
The CASCADE keyword causes any dependent objects such as indexes, logical files, views, or such to be deleted as well.
Here is a CL answer to this question:
PGM PARM(&FILENAME)
DCL VAR(&FILENAME) TYPE(*CHAR) LEN(10)
DCL VAR(&NUMRECS) TYPE(*DEC) LEN(10 0)
RTVMBRD FILE(&FILENAME) NBRCURRCD(&NUMRECS)
IF COND(&NUMRECS > 0) THEN(DLTF +
FILE(&FILENAME))
OUT: ENDPGM
This solution would have trouble if the physical file has dependencies such as indexes or logical files. Those dependencies would have to be deleted first.
The solution by #danny117 on the other hand does not work in all environments. For example I was unable to coerce it to work in SQuirreL client. But it does work in i Navigator. It also works in RUNSQLSTM, but I was unable to determine how to make it work with unqualified table references. If the tables are unqualified, RUNSQLSTM uses the default collection from DFTRDBCOL. The CURRENT_SCHEMA special register does not return the value from DFTRDBCOL.
Here is the if table has rows drop it solution using a compound statement:
begin
if( exists(
select 1 from qsys2.systables
where table_schema = 'MYLIB'
and table_name = 'MYTABLE'
)) then
if( exists(
select 1 from mylib.mytable
)) then
drop table mylib.mytable;
end if;
end if;
end;
I am guessing at the reason you would want to do this, but if it is to allow creation of a new table, then best way may be with a CREATE OR REPLACE TABLE if you are at IBM i v7.2 or greater.
If all you want to do is make sure you have an empty table, TRUNCATE (v7.2+) or DELETE may be better options.
Drop table if exists using atomic statement.
BEGIN ATOMIC
IF( EXISTS(
SELECT 1 FROM TABLES
WHERE TABLE_SCHEMA = 'MYLIB'
AND TABLE_NAME = 'MYTABLE'
)) THEN
DROP TABLE MYLIB/MYTABLE;
END IF;
END;
try this:
BEGIN
IF EXISTS (SELECT NAME FROM QSYS2.SYSTABLES WHERE TABLE_SCHEMA = 'YOURLIBINUPPER' AND TABLE_NAME = 'YOURTABLEINUPPER') THEN
DROP TABLE YOURLIB.YOURTABLE;
END IF;
END ;

Select from table that does not exist

I have a question regarding ORACLE, I wrote a PLSQL CODE that checks if a table exists, if it exists then I select something from this table..pseudocode is like:
if (table exists)
Select from table where....
the problem is that I always get an error if the table does not exist, even if the if condition is never met and the select statement is never executed.
I think it is because my code is checked at compile time: "select from.." and then it prints an error if the table does not exist. How can I solve such an issue?.. here is how my code looks like (I used generic names):
DECLARE
v_table_exists NUMBER;
BEGIN
SELECT NVL(MAX(1), 0)
INTO v_table_exists
FROM ALL_TABLES
WHERE TABLE_NAME = 'TABLE_TEST';
IF v_table_exists = 1 THEN
INSERT INTO MY_TABLE(COLUMN1, COLUMN2, COLUMN3, COLUMN4)
SELECT 1234,
5678,
T.COLUMN_TEST1,
T.COLUMN_TEST2
FROM TABLE_TEST T
WHERE T.FLAG = 1;
END IF;
END;
The issue is exactly in the fact that your procedure con not be compiled as it refers to a non existing object; you may need some dynamic SQL for this; for example:
create or replace procedure checkTable is
vCheckExists number;
vNum number;
begin
-- check if the table exists
select count(1)
into vCheckExists
from user_tables
where table_name = 'NON_EXISTING_TABLE';
--
if vCheckExists = 1 then
-- query the table with dynamic SQL
execute immediate 'select count(1) from NON_EXISTING_TABLE'
into vNum;
else
vNum := -1;
end if;
dbms_output.put_line(vNum);
end;
The procedure compiles even if the table does not exist; if you call it now, you get:
SQL> select count(1) from NON_EXISTING_TABLE;
select count(1) from NON_EXISTING_TABLE
*
ERROR at line 1:
ORA-00942: table or view does not exist
SQL> exec checkTable;
-1
PL/SQL procedure successfully completed.
Then, if you create the table and call the procedure again:
SQL> create table NON_EXISTING_TABLE(a) as select 1 from dual;
Table created.
SQL> exec checkTable;
1
PL/SQL procedure successfully completed.
The same way I showed a SELECT, you can do an UPDATE or whatever SQL query you need; if you do something different from a SELECT, the INTO clause has to be removed.
For example, say you need to insert into a different table, the above code should be edited this way:
if vCheckExists = 1 then
execute immediate 'insert into target(a, b, c) select a, 1, 100 from NON_EXISTING_TABLE';
end if;
Everything will need to be done in Dynamic SQL (DBMS_SQL) or EXECUTE_IMMEDIATE otherwise your code will never compile (or package will be invalided) if table does not exists.
DBMS_SQL Example
EXECUTE_IMMEDIATE Example
According to this article, in Oracle Database Server static SQL is indeed checked at compile time to ensure referenced objects exist.
So I advise you to use dynamic SQL instead of static SQL, through a varchar for example.

value limitation in an IN clause Oracle

I work for a company that has a DW - ETL setup. I need to write a query that looks for over 2500+ values in an WHEN - IN clause and also over 1000+ values in a WHERE - IN clause. Basically it would look like the following:
SELECT
,user_id
,CASE WHEN user_id IN ('user_n', +2500 user_[n+1] ) THEN 1
ELSE 0
,item_id
FROM user_table
WHERE item_id IN ('item_n', +1000 item_[n+1] );
As you probably already know PL/SQL allows a maximum of 1000 values in an IN clause, so I tried adding OR - IN clauses (as suggested in other stackoverflow threads):
SELECT
,user_id
,CASE WHEN user_id IN ('user_n', +999 user_[n+1] )
OR user_id IN ('user_n', +999 user_[n+1] )
OR user_id IN ('user_n', +999 user_[n+1] ) THEN 1
ELSE 0 END AS user_group
,item_id
FROM user_table
WHERE item_id IN ('item_n', +999 item_[n+1] )
OR item_id IN ('item_n', +999 item_[n+1] );
NOTE: i know the math is erroneous in the examples above, but you get the point
The problem is that queries have a maximum executing time of 120 minutes and the job is being automatically killed. So I googled what solutions I could find and it seems Temporary Tables could be the solution I'm looking for, but with all honesty none of the examples I found is clear enough on how to include the values I want in the table and also how to use this table in my original query. Not even the ORACLE documentation was of much help.
Another potential problem is that I have limited rights and I've seen other people mention that in their companies they don't have the rights to create temporary tables.
Some of the info I found in my research:
ORACLE documentation
StackOverflow thread
[StackOverflow thread 2]
Another solution I found was using tuples instead, as mentioned in THIS thread (which I haven't tried) because as another user mentions performance seems greatly affected.
Any guidance on how to use a Temporary Table or if anyone has another way of dealing with this limitation would be greatly appreciated.
Create a global temporary table so no undo logs are created
CREATE GLOBAL TEMPORARY TABLE <table_name> (
<column_name> <column_data_type>,
<column_name> <column_data_type>,
<column_name> <column_data_type>)
ON COMMIT DELETE ROWS;
then depending on how the user list arrives import the data into a holding table and then run
select 'INSERT INTO global_temporary_table <column> values '
|| holding_table.column
||';'
FROM holding_table.column;
This gives you insert statements as output which you run to insert the data.
then
SELECT <some_column>
FROM <some_table>
WHERE <some_value> IN
(SELECT <some_column> from <global_temporary_table>
Use a collection:
CREATE TYPE Ints_Table AS TABLE OF INT;
CREATE TYPE IDs_Table AS TABLE OF CHAR(5);
Something like this:
SELECT user_id,
CASE WHEN user_id MEMBER OF Ints_Table( 1, 2, 3, /* ... */ 2500 )
THEN 1
ELSE 0
END
,item_id
FROM user_table
WHERE item_id MEMBER OF IDs_table( 'ABSC2', 'DITO9', 'KMKM9', /* ... */ 'QD3R5' );
Or you can use PL/SQL to populate a collection:
VARIABLE cur REFCURSOR;
DECLARE
t_users Ints_Table;
t_items IDs_Table;
f UTL_FILE.FILE_TYPE;
line VARCHAR2(4000);
BEGIN
t_users.EXTEND( 2500 );
FOR i = 1 .. 2500 LOOP
t_users( t_users.COUNT ) := i;
END LOOP;
// load data from a file
f := UTL_FILE.FOPEN('DIRECTORY_HANDLE','datafile.txt','R');
IF UTL_FILE.IS_OPEN(f) THEN
LOOP
UTL_FILE.GET_LINE(f,line);
IF line IS NULL THEN EXIT; END IF;
t_items.EXTEND;
t_items( t_items.COUNT ) := line;
END LOOP;
OPEN :cur FOR
SELECT user_id,
CASE WHEN user_id MEMBER OF t_users
THEN 1
ELSE 0
END
,item_id
FROM user_table
WHERE item_id MEMBER OF t_items;
END;
/
PRINT cur;
Or if you are using another language to call the query then you could pass the collections as a bind value (as shown here).
In PL/SQL you could use a collection type. You could create your own like this:
create type string_table is table of varchar2(100);
Or use an existing type such as SYS.DBMS_DEBUG_VC2COLL which is a table of VARCHAR2(1000).
Now you can declare a collection of this type for each of your lists, populate it, and use it in the query - something like this:
declare
strings1 SYS.DBMS_DEBUG_VC2COLL := SYS.DBMS_DEBUG_VC2COLL();
strings2 SYS.DBMS_DEBUG_VC2COLL := SYS.DBMS_DEBUG_VC2COLL();
procedure add_string1 (p_string varchar2) is
begin
strings1.extend();
strings1(strings.count) := p_string;
end;
procedure add_string2 (p_string varchar2) is
begin
strings2.extend();
strings2(strings2.count) := p_string;
end;
begin
add_string1('1');
add_string1('2');
add_string1('3');
-- and so on...
add_string1('2500');
add_string2('1');
add_string2('2');
add_string2('3');
-- and so on...
add_string2('1400');
for r in (
select user_id
, case when user_id in table(strings2) then 1 else 0 end as indicator
, item_id
from user_table
where item_id in table(strings1)
)
loop
dbms_output.put_Line(r.user_id||' '||r.indicator);
end loop;
end;
/
You can use below example to understand Global temporary tables and the type of GTT.
CREATE GLOBAL TEMPORARY TABLE GTT_PRESERVE_ROWS (ID NUMBER) ON COMMIT PRESERVE ROWS;
INSERT INTO GTT_PRESERVE_ROWS VALUES (1);
COMMIT;
SELECT * FROM GTT_PRESERVE_ROWS;
DELETE FROM GTT_PRESERVE_ROWS;
COMMIT;
TRUNCATE TABLE GTT_PRESERVE_ROWS;
DROP TABLE GTT_PRESERVE_ROWS;--WONT WORK IF YOU DIDNOT TRUNCATE THE TABLE OR THE TABLE IS BEING USED IN SOME OTHER SESSION
CREATE GLOBAL TEMPORARY TABLE GTT_DELETE_ROWS (ID NUMBER) ON COMMIT DELETE ROWS;
INSERT INTO GTT_DELETE_ROWS VALUES (1);
SELECT * FROM GTT_DELETE_ROWS;
COMMIT;
SELECT * FROM GTT_DELETE_ROWS;
DROP TABLE GTT_DELETE_ROWS;
However as you mentioned you receive the input in an excel file so you can simply create a table and load data in that table. Once the data is loaded you can use the data in IN clause of your query.
select * from employee where empid in (select empid from temptable);
create temporary table userids (userid int);
insert into userids(...)
then a join or in subquery
select ...
where user_id in (select userid from userids);
drop temporary table userids;