I am executing my test_function as below
select test_function(t.dt::date)
from generate_series(date '2018-01-01', date '2019-12-01', interval '1 month') as t(dt);
Definition of test_function
CREATE OR REPLACE FUNCTION test_function(_snapdt date)
RETURNS integer
AS
$BODY$
declare rows integer;
BEGIN
DROP TABLE IF EXISTS temp_table;
create table temp_table AS
select col1,col2
from table1
where id=01000 and
snapshot_date=_snapdt
group by col1,col2
distributed randomly;
INSERT INTO standard_table
( snap_date,
col1,
col2
)
SELECT
_snapdt as snap_date,
a.col1,
a.col2,
FROM temp_table a;
GET DIAGNOSTICS rows=ROW_COUNT;
DROP TABLE IF EXISTS temp_table;
return rows;
END;
$BODY$
LANGUAGE plpgsql;
It executes for each month from 2018 to 2019.
I am curious how do we delete records based on a specific condition at the end of each month run.
ex: after completing executing 2018-01-01 it should run below statement
delete from my_table where col4 is NULL;
Same ,after completing executing 2018-02-01 it should run below statement
delete from my_table where col4 is NULL;
and so on for all the months from 2018 to 2019 (statement is same for all the months)
Related
The query runs slow in Oracle PL/SQL. It takes about 8 minutes in PL/SQL whereas it takes only 4 seconds when run in SQL Editor or SQL Plus.
Please let me know what is causing this. Is it possible that a different execution plan is picked up by SQL and PL/SQL ?
----SQL Editor query---takes 4 seconds---400 row count--
SELECT count(*) FROM
(
SELECT col1, col2
FROM
my_tab1 t1, my_tab2 t2
WHERE
t1.pk_col1=t2.pk_col1
and t1.created_date < t2.created_date
)
--PL/SQL Code--takes about 8 minutes---400 row rount--
DECLARE
v_cnt PLS_INTEGER:=0;
BEGIN
SELECT count(*) INTO v_cnt
FROM
(
SELECT col1, col2
FROM
my_tab1 t1, my_tab2 t2
WHERE
t1.pk_col1=t2.pk_col1
and t1.created_date < t2.created_date
)
END;
/
The easiest way to capture the execution plan within an anonymous block is to call dbms_xplan.display_cursor in a loop and print each line of output:
declare
v_cnt pls_integer;
begin
execute immediate 'alter session set statistics_level = ALL';
select count(*) into v_cnt
from
(
select col1, col2
from my_tab1 t1, my_tab2 t2
where t1.pk_col1 = t2.pk_col1
and t1.created_date < t2.created_date
);
for r in (
select p.plan_table_output
from table(dbms_xplan.display_cursor(null,null,'ALLSTATS LAST -OUTLINE +NOTE +PREDICATE +IOSTATS +REPORT')) p
)
loop
dbms_output.put_line(r.plan_table_output);
end loop;
end;
You can make the same call from a SQL*Plus command prompt immediately after executing a SQL statement, but you first have to disable dbms_output, as otherwise the SQL statement is not the 'last' statement you made. (You can also specify the sql_id if you know it.) For more details see the dbms_xplan documentation.
set serverout off
alter session set statistics_level = ALL;
select count(*)
from
(
select col1, col2
from my_tab1 t1, my_tab2 t2
where t1.pk_col1 = t2.pk_col1
and t1.created_date < t2.created_date
);
select p.plan_table_output
from table(dbms_xplan.display_cursor(null,null,'ALLSTATS LAST +OUTLINE +ADAPTIVE +PARTITION +NOTE')) p;
For a handy script to call this in one line, see www.williamrobertson.net/documents/xplanx.html. Then it's just
select count(*)
from
(
select col1, col2
from my_tab1 t1, my_tab2 t2
where t1.pk_col1 = t2.pk_col1
and t1.created_date < t2.created_date
)
#xplanx
I have a function test_function where I am passing _snapdt as parameter. I want to run this function for several dates (_snapdt) individually in a loop.
Can someone help me with this?
For example I want to run this function for several dates like "2018-01-30" , 2018-02-30" and so on ..
Currently I am running it manually like this:
select * from test_function('2018-01-30') ;
select * from test_function('2018-02-30') ;
Here is the function
CREATE OR REPLACE FUNCTION test_function(_snapdt date)
RETURNS integer
AS
$BODY$
declare rows integer;
BEGIN
DROP TABLE IF EXISTS temp_table;
create table temp_table AS
select col1,col2
from table1
where id=01000 and
snapshot_date=_snapdt
group by col1,col2
distributed randomly;
INSERT INTO standard_table
( snap_date,
col1,
col2
)
SELECT
_snapdt as snap_date,
a.col1,
a.col2,
FROM temp_table a;
GET DIAGNOSTICS rows=ROW_COUNT;
DROP TABLE IF EXISTS temp_table;
return rows;
END;
$BODY$
LANGUAGE plpgsql;
use generate_series()
select test_function(t.dt::date)
from generate_series(date '2018-01-30', date '2018-02-28') as t(dt);
If you want to call the function once for each month, change the interval:
select test_function(t.dt::date)
from generate_series(date '2018-01-01', date '2019-12-01', interval '1 month') as t(dt);
Our IT team loads couple of tables every month. The new load should have more records than the previous load, with at least 2% more records.
It's a truncate and load process, I'm collecting the num of records from each table before the truncate, and I'm checking the difference in excel every month to make sure the data load is correct.
Is there anyway to automate this in Oracle.
eg:
Table_name Before_cnt After_cnt
XX_TEST1 4,606,619,326 4,983,759,822
XX_TEST2 121,973,005 123,161,581
You can apply the steps just like below :
SQL> create table XX_TEST1( id int primary key );
SQL> insert into XX_TEST1 select level from dual connect by level <= 100;
SQL> begin -- if table exists, then drop it!
for c in (select table_name from cat where table_name = 'XX_TEST1_OLD' )
loop
execute immediate 'drop table '||c.table_name;
end loop;
end;
/
SQL> create table XX_TEST1_old as select count(*) as cnt from XX_TEST1;
SQL> begin
execute immediate 'truncate table XX_TEST1';
end;
/
SQL> insert into XX_TEST1 select level from dual connect by level <= 103;
SQL> with xt1_new(cnt_new) as
(
select count(id) from XX_TEST1
)
select case when sign( (100 * ( cnt_new - cnt) / cnt)-2 ) = 1 then 1
else 0 end as "Rate Satisfaction"
from XX_TEST1_old
cross join xt1_new;
If this SELECT statement retuns 1, then we're successful to reach the target, else returns 0 and means we're unsuccessful.
Demo
Below trigger code(converted from MSSQL) in oracle is not working.
The two columns should not have duplicate row in the table. I'm creating a trigger for accomplishing this.
Can anyone help in updating/correcting the above code to be used in my trigger?
/*
**Unique Constraint for TestOracle - TestTinyInt.
*/
if (Update(UpdOperation) or Update(TestTinyInt)) THEN
IF Exists(
SELECT * FROM inserted i INNER LOOP JOIN TestOracle x ON
(i.TestTinyInt=x.TestTinyInt)
WHERE i.updoperation IN (0, 1) AND x.updoperation IN (0, 1) GROUP BY x.TestTinyInt
HAVING COUNT(*) > 1)
BEGIN
RAISERROR( 'Invalid attempt to enter duplicate TestTinyInt in TestOracle', 16, -1 )
ROLLBACK TRAN
RETURN
END
END
The best way is to create 2 unique index on each of columns. By doing this you are eliminating duplication in particual column(like #a_horse_with_no_name mentioned).
For other case you don't need to use triger, you need only simple where condition
where Column_A not in (select Column_B from table) and Column_B not in (Select Column_A in table).
EDIT:
It if have to be done in trigger THEN :
create or replace trigger ... instead of insert or update on ...
Declare
dummy number;
Begin
select 1 into dummy from dual where :new.Column_A in (select Column_B from table) or new:.Column_B in (Select Column_A in table);
if dummy <> 1 THEN
INSERT
END IF;
END;
EDIT2: IF you don't want unique index and tirgger here is solution :
create or replace trigger ... instead of insert or update on ...
Declare
dummy number;
Begin
select count(*) into dummy from(
SELECT COL1 FROM (
(select :new.Column_A col1 from dual
UNION
select :new.Column_B from dual))
INTERSECT
SELECT COL2 FROM (
( SELECT COLUMN_A COL2 from table
UNION
SELECT COLUMN_B from table));
if dummy = 0 THEN
INSERT
END IF;
END;
I am stuck at a place.
There is a procedure that checks for something and inserts into an table type upon successful determination of that condition.
But i can insert only once in the table type. Is there a way to insert again and again into the table type.
PROCEDURE "hello"."helloWorld.db::sampleException" (OUT TRACE_RECORD "hello"."LogTrace" )
LANGUAGE SQLSCRIPT AS
BEGIN
DECLARE i int;
select count(*) into i from "hello"."REGION";
IF :i > 1 then
TRACE_RECORD = SELECT '1' AS "LogID", '1' AS "TraceID" FROM DUMMY;
end if;
IF :i > 2 then
TRACE_RECORD = SELECT '2' AS "LogID", '2' AS "TraceID" FROM DUMMY;
end if;
END;
What i get on executing the procedure is only the last record "2,2".
How can i insert both the records 1,1 and 2,2.
Note: I do not want to use Temporary Tables.
Any help on this..
Thanks.!
Editing the Question a bit:
-I have to use Table TYPE (till the time there is no optimal way better than it)
-I have to insert more than 20-30 records in the table type.
Do you have to write this as a procedure? A table-valued function seems more suitable:
CREATE FUNCTION f_tables4 (in_id INTEGER)
RETURNS TABLE (
"LogID" VARCHAR(400),
"TraceID" VARCHAR(400)
)
LANGUAGE SQLSCRIPT
AS
BEGIN
RETURN
SELECT t."LogID", t."TraceID"
FROM (
SELECT 1 AS i, '1' AS "LogID", '1' AS "TraceID" FROM DUMMY
UNION ALL
SELECT 2 AS i, '2' AS "LogID", '2' AS "TraceID" FROM DUMMY
) t
JOIN (SELECT count(*) AS cnt FROM "hello"."REGION") c
ON c.cnt > t.i
END