I am writing a schema upgrade script for a product that depends on an Oracle database. In one area, I need to create an index on a table - if that index does not already exist. Is there an easy way to check for the existence of an index that I know the name of in an Oracle script?
It would be similar to this in SQL Server:
IF NOT EXISTS (SELECT * FROM SYSINDEXES WHERE NAME = 'myIndex')
// Then create my myIndex
select count(*) from user_indexes where index_name = 'myIndex'
sqlplus won't support IF..., though, so you'll have to use anonymous PL/SQL blocks, which means EXECUTE IMMEDIATE to do DDL.
DECLARE
i INTEGER;
BEGIN
SELECT COUNT(*) INTO i FROM user_indexes WHERE index_name = 'MYINDEX';
IF i = 0 THEN
EXECUTE IMMEDIATE 'CREATE INDEX myIndex ...';
END IF;
END;
/
Edit: as pointed out, Oracle stores unquoted object names in all uppercase.
Related
I am creating a temp table
CREATE TABLE TMP_PRTimeSum AS
SELECT DISTINCT p.employee,
SUM(p.wage_amount) AS wage_sum,
SUM(p.hours) AS hour_sum
I then want to do a select from that temp table and show the results. I want to do it all in one query. It works if I run them as two separate queries. Is there any way to do this in one query?
CREATE GLOBAL TEMPORARY TABLE a
ON COMMIT PRESERVE ROWS
AS
select * from b;
(add where 1=0 too if you didn't want to initially populate it for the current session with all the data from b).
Is there any way to do this in one query?
No, you need to use one DDL statement (CREATE TABLE) to create the table and one DML statement (SELECT) to query the newly created table.
If you want to be really pedantic then you could do it in a single PL/SQL statement:
DECLARE
cur SYS_REFCURSOR;
v_employee VARCHAR2(20);
v_wage NUMBER;
v_hours NUMBER;
BEGIN
EXECUTE IMMEDIATE
'CREATE TABLE TMP_PRTimeSum (employee, wage_sum, hour_sum) AS
SELECT P.EMPLOYEE,
SUM(P.WAGE_AMOUNT),
SUM(P.HOURS)
FROM table_name p
GROUP BY p.Employee';
OPEN cur FOR 'SELECT * FROM TMP_PRTimeSum';
LOOP
FETCH cur INTO v_employee, v_wage, v_hours;
EXIT WHEN cur%NOTFOUND;
DBMS_OUTPUT.PUT_LINE(v_employee || ', ' || v_wage || ', ' || v_hours);
END LOOP;
END;
/
db<>fiddle here
However, you are then wrapping the two SQL statements in a PL/SQL anonymous block so, depending on how you want to count it then it is either one composite statement, two SQL statements or three (2 SQL and 1 PL/SQL) statements.
i need to create a script that do this thing:
check if the table exists, if exists truncate(or drop and create) table else create that table. i try to search on internet but some code work for half or not work at all.
this is one of script i found on internet
DECLARE
val INTEGER := 0;
BEGIN
SELECT COUNT(*) INTO val FROM user_tables WHERE table_name = 'tabella';
IF val = 1 THEN
EXECUTE IMMEDIATE ('TRUNCATE TABLE tabella');
ELSE
EXECUTE IMMEDIATE ('CREATE TABLE tabella(idTabella INTEGER NOT NULL, campo VARCHAR(50)');
END IF;
END;
You don't need to check if the table exists; just try to truncate the table and if the table does not exist then catch the exception and create the table:
DECLARE
table_not_exists EXCEPTION;
PRAGMA EXCEPTION_INIT( table_not_exists, -942 );
BEGIN
EXECUTE IMMEDIATE 'TRUNCATE TABLE tabella';
DBMS_OUTPUT.PUT_LINE( 'truncated' );
EXCEPTION
WHEN table_not_exists THEN
EXECUTE IMMEDIATE 'CREATE TABLE tabella ( idTabella INTEGER NOT NULL, campo VARCHAR(50) )';
DBMS_OUTPUT.PUT_LINE( 'created' );
END;
/
db<>fiddle
Since table names are saved as upper case strings, you should check them the same case. There are to ways you can do this:
You can write your table name in upper case like:
SELECT COUNT(*) INTO val FROM user_tables WHERE table_name = 'TABELLA';
You can lower case your table_name column so you can input it lower case all the time:
SELECT COUNT(*) FROM user_tables WHERE LOWER(table_name) = 'tabella';
Hope this helps.
The database defaults all names to UPPER_CASE. When you execute
CREATE TABLE tabella(...
the database actually creates the table with a name of TABELLA. This is fine - unless you quote the name of an object, by surrounding it in double-quotes, the database will automatically convert all names to upper case, and everything will work just fine. The only time you need to remember to use UPPER_CASE names is when you're accessing system tables or views, such as USER_TABLES. If you change the query in your script to
SELECT COUNT(*) INTO val FROM user_tables WHERE table_name = 'TABELLA';
I think you'll find it works just fine.
I would need some help, I am trying to build a procedure in ORACLE PL/SQL, that would get the list of unstable indexes from table dba_indexes and this select statement would also build up the statement to fix/alter the index.
So I have SQL statement that generates the alter statements.
SELECT 'alter index '||owner||'.'||index_name||' rebuild online; '
FROM dba_indexes
WHERE status = 'N/A';
And the output is:
alter index OWNER.INDEX_NAME rebuild online;
Which is perfect, and this is also the end result then I just have to get the procedure to work correctly and execute alter statements if there is any if not then just finish the procedure.
Now my question would be how can I save data from SELECT statement to 1 variable ? After that I will just check if variable is NULL or not, if null I finish procedure if not null the I would just execute this variable ?
Is that possible and how ?
Many thanks for help.
You don't necessarily need a variable; you can use a loop over your query:
begin
for s in (
SELECT 'alter index '||owner||'.'||index_name||' rebuild online' as statement
FROM dba_indexes
WHERE status = 'N/A'
)
loop
dbms_output.put_line(s.statement);
execute immediate s.statement;
end loop;
end;
This will simply do the job for every index in your query, or do nothing if the query returns no indexes.
I have a problem trying to use an Execute Immediate statement containing a CREATE TABLE statement and a user defined Table Type. I get error ORA-22905 on Oracle 11g.
Is there any workaround to solve this issue?
CREATE TYPE MY_TABLE_TYPE AS TABLE OF VARCHAR2(30);
/
DECLARE
MT MY_TABLE_TYPE;
BEGIN
SELECT * BULK COLLECT INTO MT FROM DUAL;
-- Two steps
EXECUTE IMMEDIATE 'CREATE TABLE MY_TABLE1 (A VARCHAR2(30))';
EXECUTE IMMEDIATE 'INSERT INTO MY_TABLE1 SELECT * FROM TABLE(:T)' USING MT; -- OK
-- One step
EXECUTE IMMEDIATE 'CREATE TABLE MY_TABLE2 AS SELECT * FROM TABLE(:T)' USING MT; -- ERROR ORA-22905
END;
The real code for the SELECT * FROM TABLE(:T) is dynamic (main table name is temporary) and slow. That's why I try to avoid creating the table in two steps (as done with MY_TABLE1). Also with two steps I can't use SELECT * but I have to specify all the columns (variable amount and over 100 columns).
There is likely a way to completely avoid this issue. Skip the bulk collect and use a simple CREATE TABLE MY_TABLE AS SELECT * FROM DUAL; That may be an over-simplification of the real logic to gather the data. But there is almost always a way to bypass a bulk collect and store the data directly in an object with just SQL.
If a PL/SQL solution is truly needed, the error ORA-22905: cannot access rows from a non-nested table item can be avoided by creating an object type and creating the table based on that type. This may not solve the performance issue, but at least this avoids the need to re-specify all the columns in the table DDL.
CREATE TYPE MY_TABLE_OBJECT IS OBJECT
(
A VARCHAR2(30)
);
CREATE TYPE MY_TABLE_TYPE2 AS TABLE OF VARCHAR2(30);
DECLARE
MT MY_TABLE_TYPE2;
BEGIN
SELECT * BULK COLLECT INTO MT FROM DUAL;
EXECUTE IMMEDIATE 'CREATE TABLE MY_TABLE2 OF MY_TABLE_OBJECT';
EXECUTE IMMEDIATE 'INSERT INTO MY_TABLE2 SELECT * FROM TABLE(:T)' USING MT;
END;
/
I have an assignment for uni now where I need to write a database change script and then a rollback script. I should also do some simple checks whether the changes has been done or not. I have spent enormous time by writing the scripts because I am not skilled in plsql. The prodcut is here:
-- the check could be more extensive, e.g. checking the type of the column
declare
titleExists number;
begin
select count(*) into titleExists
from user_tab_columns
where table_name = 'TITLE'
and column_name = 'TITLE';
if titleExists > 0 then
execute immediate 'alter table title rename column title to name';
end if;
end;
/
declare
typeExists number;
begin
select count(*) into typeExists
from user_tab_columns
where table_name = 'TITLE'
and column_name = 'TYPE'
and data_type = 'CHAR';
if typeExists > 0 then
execute immediate 'alter table title add (new_type varchar2(12) check (new_type in (''business'', ''mod_cook'', ''psychology'', ''popular_comp'', ''trad_cook'')))';
execute immediate 'update title set new_type = trim(type)';
execute immediate 'alter table title modify (new_type not null)';
execute immediate 'alter table title drop column type';
execute immediate 'alter table title rename column new_type to type';
end if;
end;
/
The first part renames a column and the second part changes columns type and adds a check, basically turns a char column into an enum.
I would really like to know whehter I need to put every alteration in execute immediate block. Is there a simpler way of writing this?
There are a number of issues with the script, but keeping myself limited to the question whether to use execute immediate:
There is not really a simpler way of writing this. The PL/SQL is modifying the database it runs on, so adding for instance the update title occur as a hard-coded section in the PL/SQL is not possible (it would not compile).
Also, sometimes it is possible to merge multiple statements executed through execute immediate in one big PL/SQL being send over. But PL/SQL itself does not support the DDL statements, so you need some way of dynamic SQL.
Note also that each DDL does an implicit commit, so your update is always executed and committed by the following alter table statement.
I think you have done great being this your first PL/SQL assignment.