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.
Related
Hello I have this situation
I have a table with 400 millions of records I want to migrate some data to another table for get better performance.
I know that the process could be slow and heavy and I want to execute something like this
INSERT INTO TABLLE_2 SELECT NAME,TAX_ID,PROD_CODE FROM TABLE_1;
But I want this in a procedure that iterates the table from 50000 to 50000 records.
I saw this but it DELETE the rows and the target is SQLServer and not Oracle
WHILE 1 = 1
BEGIN
DELETE TOP (50000) FROM DBO.VENTAS
OUTPUT
Deleted.AccountNumber,
Deleted.BillToAddressID,
Deleted.CustomerID,
Deleted.OrderDate,
Deleted.SalesOrderNumber,
Deleted.TotalDue
INTO DATOS_HISTORY.dbo.VENTAS
WHERE OrderDate >= '20130101'
and OrderDate < '20140101'
IF ##ROWCOUNT = 0 BREAK;
END;
If it's just for a one-time migration, don't paginate/iterate at all. Just use a pdml append insert-select:
BEGIN
EXECUTE IMMEDIATE 'alter session enable parallel dml';
INSERT /*+ parallel(8) nologging append */ INTO table_2
SELECT name,tax_id,prod_code from table_1;
COMMIT;
EXECUTE IMMEDIATE 'alter session disable parallel dml';
END;
Or if the table doesn't yet exist, CTAS it to be even simpler:
CREATE OR REPLACE TABLE table_2 PARALLEL (DEGREE 8) NOLOGGING AS
SELECT name,tax_id,prod_code FROM table_1;
If you absolutely must iterate due to other business needs you didn't mention, you can use PL/SQL for that as well:
DECLARE
CURSOR cur_test
IS
SELECT name,tax_id,prod_code
FROM table_1;
TYPE test_tabtype IS TABLE OF cur_test%ROWTYPE;
tab_test test_tabtype;
var_limit integer := 50000;
BEGIN
OPEN cur_test;
FETCH cur_test BULK COLLECT INTO tab_test LIMIT var_limit;
LOOP
-- do whatever else you need to do
FORALL i IN tab_test.FIRST .. tab_test.LAST
INSERT INTO table_2
(name,tax_id,prod_code)
VALUES (tab_test(i).name, tab_test(i).tax_id, tab_test(i).prod_code);
COMMIT;
EXIT WHEN tab_test.COUNT < var_limit;
FETCH cur_test BULK COLLECT INTO tab_test LIMIT var_limit;
END LOOP;
CLOSE cur_test;
END;
That won't be nearly as fast, though. If you need even faster, you can create a SQL object and nested table type instead of the PL/SQL types you see here and then you can do a normal INSERT /*+ APPEND */ SELECT ... statement that will use direct path. But honestly, I doubt you really need iteration at all..
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.
Based on this post: How to use partition name as a parameter in the select statement? I have tried to replicate it, but no success. I get error message "ORA-00922" Invalid or missing option. It must be something small syntax issue, but not sure where is it exactly. Here my code:
DECLARE
TYPE PROD_TAB IS TABLE OF DBA_TAB_PARTITIONS%ROWTYPE;
PRODUCTS_TAB PROD_TAB := PROD_TAB();
START_TIME NUMBER;
END_TIME NUMBER;
BEGIN
SELECT *
BULK COLLECT
INTO products_tab
FROM dba_tab_partitions t
WHERE table_name = 'LST_COLL';
FOR i in products_tab.first .. products_tab.last LOOP
EXECUTE IMMEDIATE 'CREATE TABLE test_partition_copy
PARALLEL NOLOGGING
("ID", datetime, ...)
SELECT * FROM test_partition PARTITION (||products_tab(i).partition_name||)';
COMMIT;
END LOOP;
COMMIT;
END;
You need the PL/SQL variable to be injected via string concatenation; you're missing some single quotes:
EXECUTE IMMEDIATE 'CREATE TABLE test_partition_copy
(id, datetime, ...)
PARALLEL NOLOGGING
AS SELECT id, datetime, ...
FROM test_partition PARTITION ("' || products_tab(i).partition_name || '")';
You also need the AS keyword; the PARALLEL NOLOGGING clauses come after the new table's column-name list; it's better to list the columns explicitly in both parts of the statement; and I've included double-quotes around the partition name just in case.
If you build up your dynamic statement as a string variable, you can inspect that (e.g. via dbms_output) to look for errors, or even try to run it manually to get a better idea exactly where the error is happening, depending on your client. You can then do EXECUTE IMMEDIATE <string_variable>.
You still have an issue in that you're trying to create a table with the same name - test_partition_copy - each time; the first time round the loop for the first partition you might create that table OK, but the second loop for the second partition will get an error because the table already exists.
Incidentally, DDL commits implicitly, so you don't need the explicit commit.
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.
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.