compare i and i+1 element oracle - sql

I have table
CREATE table table_x
(
id NUMBER, -- ID
NUMBER_history NUMBER, -- consecutive number
start_date date, -- start_date of next id=end_date of previous
end_date date -- should be >=start_date
);
INSERT INTO table_x VALUES (14 , 1, '13-NOV-92' ,'14-NOV-92' );
INSERT INTO table_x VALUES (15 , 2, '14-NOV-92' ,'16-NOV-92' );
INSERT INTO table_x VALUES (19 , 3, '16-NOV-92' ,'18-NOV-92' );
INSERT INTO table_x VALUES (11 , 4, '18-NOV-92' ,'14-NOV-93' );
INSERT INTO table_x VALUES (17 , 5, '15-NOV-93' ,'16-NOV-93' );
INSERT INTO table_x VALUES (151 , 6, '16-NOV-93' ,'17-NOV-93' );
INSERT INTO table_x VALUES (139 , 7, '17-NOV-93' ,'18-NOV-93' );
INSERT INTO table_x VALUES (121 , 8, '19-NOV-93' ,'20-DEC-93' );
INSERT INTO table_x VALUES (822 , 9, '20-DEC-93' ,'20-DEC-93' );
I want write query where I can find where start_date of next row > end_date of previous. they must be equal.
I try to do something like that using NUMBER_history as counter. C-way where I organize cycle by variable i and compare i and i+1 (NUMBER_history and NUMBER_history+1)
select * INTO row_tblx from table_x where NUMBER_history=NUMBER_history and end_date<(select start_date from table_x where NUMBER_history=NUMBER_history+1);
but I must organize loop by n_counter from 1 to last NUMBER_history value and fetch data into multiple row.
How can I do it?
I try
set serveroutput on
DECLARE
CURSOR cur IS
SELECT * FROM table_x;
TYPE row_tblx_type
IS
TABLE OF cur%ROWTYPE;
row_tblx row_tblx_type;
rowx cur%ROWTYPE;
nh_count NUMBER;
BEGIN
FOR NUMBER_history IN cur LOOP
select * INTO row_tblx from table_x where NUMBER_history=NUMBER_history and end_date<(select start_date from table_x where NUMBER_history=NUMBER_history+1);
DBMS_OUTPUT.PUT_LINE (row_tblx(NUMBER_history).id);
END LOOP;
END;
/
How can I do it using for or another loop, multiple records or table of records , cursor, an table row as counter (NUMBER_history)?
How can I do it without cursor?

You don't need PL/SQL or a loop for that:
select *
from (
select id, number_history, start_date, end_date,
lead(start_date) over (order by number_history) as next_start
from table_x
) t
where next_start > end_date;

For example, here's a solution that does not require PL/SQL or LEAD/LAG function
select a.*, b.*
from table_x a
join table_x b
on a.number_history+1 = b.number_history
where b.start_date > a.end_date

Related

Conditionally insert into another table if id exists in another table else insert into both both tables in oracle

If customer id exists in table A, insert order in table B.
if customer id does not exist in table A, insert customer id in table A and then order in table B.
I have been trying to achieve this with if/else and merge but keep running into
invalid sql statement.
IF EXISTS (SELECT CustomerID FROM Customer_T WHERE CustomerID = 18)
Insert into Order_T
values(79,18,to_date('09/28/2021','mm/dd/yyyy'),to_date('10/01/2021','mm/dd/yyyy'),1,3)
ELSE
insert INTO Customer_T VALUES (18,'Capitol Industries Ltd', '999 Fifth Avenue', 'New York', 'NY','10015')
insert into Order_T values (79,18,to_date('09/28/2021','mm/dd/yyyy'),to_date('10/01/2021','mm/dd/yyyy'),1,3)
END IF;
The IF THEN ELSE logic is not needed for this case. Instead make use of the database built-in functionality. customerid should be your primary key, so if you try to insert and it already exists that will raise the DUP_VAL_ON_INDEX exception.
Check the following example:
-- create tables
create table customers (
id number generated by default on null as identity
constraint customers_id_pk primary key,
name varchar2(255 char)
)
;
create table orders (
id number generated by default on null as identity
constraint orders_id_pk primary key,
customer_id number
constraint orders_customer_id_fk
references customers on delete cascade,
product varchar2(100 char)
)
;
BEGIN
BEGIN
insert INTO customers VALUES (2,'Capitol Industries Ltd');
EXCEPTION WHEN DUP_VAL_ON_INDEX THEN
NULL;
END;
insert into orders (customer_id,product) values (2,'a book');
END;
/
run the above block a couple of times. Only the first time it will insert a customer.
Suppose you have 2 very simple tables.
Tables
create table T1( num_ )
as
select 1 from dual ;
create table T2( num_ )
as
select 200 from dual ;
An anonymous block, containing IF .. ELSE .. END IF and EXISTS() similar to the code in your question, causes an error:
function or pseudo-column 'EXISTS' may be used inside a SQL statement
only
begin
if exists( select num_ from T1 where num_ = 2 ) then
insert into T2( num_ ) values( 2 ) ;
dbms_output.put_line( 'if' ) ;
else
insert into T1( num_ ) values( 2 ) ;
insert into T2( num_ ) values( 2 ) ;
dbms_output.put_line( 'else' ) ;
end if ;
end ;
/
-- error:
... function or pseudo-column 'EXISTS' may be used inside a SQL statement only
One solution may be to do the following (see asktom.oracle.com - Equivalent for EXISTS() in an IF statement)
begin
for x in ( select count(*) cnt
from dual
where exists ( select num_ from T1 where num_ = 2 )
) loop
if ( x.cnt = 1 ) then -- found
insert into T2( num_ ) values( 2 ) ;
dbms_output.put_line( 'if' ) ;
else -- not found
insert into T1( num_ ) values( 2 ) ;
insert into T2( num_ ) values( 2 ) ;
dbms_output.put_line( 'else' ) ;
end if;
end loop;
end;
/
-- output:
1 rows affected
dbms_output:
else
After the first execution of the anonymous block, the tables contain the following rows:
select num_, '<- T1' as table_ from T1
union all
select num_, '<- T2' from T2 ;
-- result
NUM_ TABLE_
1 <- T1
2 <- T1
200 <- T2
2 <- T2
Execute the anonymous block again, and you get ...
1 rows affected
dbms_output:
if
-- tables
NUM_ TABLE_
1 <- T1
2 <- T1
200 <- T2
2 <- T2
2 <- T2
DBfiddle here.
You may use multitable insert and use the fact that aggregate function without group by always returns a row with null in case of absent (= not satisfying where condition) row.
The code is below:
insert into customers(id, name, company, state)
values (1, 'Some name', 'Some company', 'NY')
1 rows affected
insert all
when cust_exists = 0
then
into customers (id, name, company, state)
values (cust_id, cust_name, company, state)
when 1 = 1
then
into orders (id, customer_id, order_date, due_date, some_id)
values(order_id, cust_id, order_date, due_date, some_id)
select
1 as order_id,
1 as cust_id,
'Some other name' as cust_name,
'Company' as company,
'NY' as state,
date '2021-09-28' as order_date,
date '2021-10-03' as due_date,
100 as some_id,
nvl(max(1), 0) as cust_exists
from customers
where id = 1
1 rows affected
insert all
when cust_exists = 0
then
into customers (id, name, company, state)
values (cust_id, cust_name, company, state)
when 1 = 1
then
into orders (id, customer_id, order_date, due_date, some_id)
values(order_id, cust_id, order_date, due_date, some_id)
select
2 as order_id,
2 as cust_id,
'Some other name' as cust_name,
'Company' as company,
'NY' as state,
date '2021-09-28' as order_date,
date '2021-10-03' as due_date,
100 as some_id,
nvl(max(1), 0) as cust_exists
from customers
where id = 2
2 rows affected
You may also use two inserts with documented ignore_row_on_dupkey_index hint, which does what its name implies.
insert /*+
ignore_row_on_dupkey_index(customers(id))
*/
into customers (id, name, company, state)
values (2, 'Name', 'Comp', 'NY')
✓
insert into orders (id, customer_id, order_date, due_date, some_id)
values (3, 2, date '2021-09-30', date '2021-10-07', 5)
1 rows affected
select *
from customers
ID
NAME
COMPANY
STATE
1
Some name
Some company
NY
2
Some other name
Company
NY
db<>fiddle here

How to store fetched bulk values into single variable and insert all values it on one variable using pl/sql

Note
Somebody Tell me how to store Multiples rows value for a single column store into a single variable and behalf of that variable returned values store in table on one single click how to do such scenario using pl/sql.
Like Such Query Return single column with Multi rows i want to store it into some var and insert all values on single click process.It will generate error image of error has attached
DECLARE
l_respondent_id VARCHAR(100);
BEGIN
FOR c IN (
SELECT
s.gr_number
FROM
student s
LEFT JOIN class_time ct ON ct.class_id = s.class_id
AND instr(s.class_time, ct.class_time) > 0
WHERE
upper(TRIM(ct.class_id)) = upper(TRIM(:app_user))
AND s.gr_number IS NOT NULL
AND is_active_flg = 'Y'
) LOOP
l_respondent_id := c.gr_number;
BEGIN
SELECT
s.gr_number
INTO l_respondent_id
FROM
student s
LEFT JOIN class_time ct ON ct.class_id = s.class_id
AND instr(s.class_time, ct.class_time) > 0
WHERE
upper(TRIM(ct.class_id)) = upper(TRIM(:app_user))
AND s.gr_number IS NOT NULL
AND is_active_flg = 'Y'
AND s.gr_number = c.gr_number;
EXCEPTION
WHEN no_data_found THEN
l_respondent_id := NULL;
END;
IF l_respondent_id IS NULL THEN
INSERT INTO student_class_attend ( gr_number ) VALUES ( c.gr_number ) RETURNING gr_number INTO l_respondent_id;
END IF;
END LOOP;
END;
You can probably use a nested table for this, together with a BULK COLLECT and a FORALL. Simplified example:
Tables and data
create table students( id primary key, fname, lname )
as
select 1, 'fname_1', 'lname_1' from dual union all
select 2, 'fname_2', 'lname_2' from dual union all
select 3, 'fname_3', 'lname_3' from dual union all
select 4, 'fname_4', 'lname_4' from dual union all
select 5, 'fname_5', 'lname_5' from dual union all
select 6, 'fname_6', 'lname_6' from dual ;
-- empty
create table attendance( studentid number references students, when_ date ) ;
Anonymous block
declare
-- This will allow you to store several studentIDs:
type studentid_t is table of students.id%type index by pls_integer ;
-- The current attendance list. Notice the TYPE.
attendancelist studentid_t ;
begin
-- Your query (including all the necessary joins and conditions) here.
-- In this case, we will pick up 3 of the 6 IDs.
select id bulk collect into attendancelist
from students
where mod( id, 2 ) = 0 ;
-- Write a _single_ DML statement after FORALL (which is not a loop - see docs)
forall i in attendancelist.first .. attendancelist.last
insert into attendance( studentid, when_ )
values( attendancelist( i ) , sysdate ) ; -- use whatever you need here
end ;
/
Result (after executing the anonymous block)
SQL> select * from attendance ;
STUDENTID WHEN_
---------- ---------
2 07-JUN-20
4 07-JUN-20
6 07-JUN-20
DBfiddle here.

how to check Date is between 2 dates in SQL?

i want retrieve rows based on Date that within 2 dates or
it will be greater than date or less than date
where id =5
AND ( :fromDate is null or trunc ( :fromDate ) >= trun(TABLE.DEPOSIT_DATE ))
AND ( :toDate is null or trunc ( :toDate )<= trunc (TABLE.DEPOSIT_DATE ))
when i pass paramters to :fromDate and :ToDate at this format (7/25/2018 1:21:55 PM) for example
the query return 0 rows
Try using "BETWEEN" operator as below example
SELECT * FROM employee where joining_date BETWEEN '2006-02-14 04:34:33' AND '2016-02-15 04:34:12';
If you are using sql server
then first set the date format
like : - set daqteformat dmy
and date condition u can use between
like : Entrydate between '01/01/2018' and '31/01/2018'
Why you cannot use between ? check below example using plsql block statement to use bind variable the same as your example
SET serveroutput ON size 2000
/
declare
T_DATE date;
F_DATE DATE;
C_ID number(2);
begin
T_DATE := to_date('01/07/2018','dd/mm/yyyy');
F_DATE := to_date('01/06/2018','dd/mm/yyyy');
delete from ex_employee;
insert into ex_employee (id, X_NAME, T_ADDED) values (1, 'aa', sysdate);
insert into ex_employee
(id, X_NAME, T_ADDED)
values
(2, 'bb', sysdate - 1);
insert into ex_employee
(id, X_NAME, T_ADDED)
values
(3, 'cc', to_date('20/06/2018','dd/mm/yyyy'));
commit;
select id
into C_ID
from ex_employee
where T_ADDED between F_DATE and T_DATE or F_DATE is null or T_DATE is null;
dbms_output.put_line(c_id);
end;
/
PL/SQL procedure successfully completed
3

How to secure table for avoid duplicate data

I cant resolve the problem how secure my table to avoid duplicate combination of attributes_positions. The best way to show you what I mean is the following image
column id_combination represents number of combination. Combination consists of attributes_positions. So Combination is sequence of attributes_positions.
And now I would secure table from insert exaclty the same sequence of attributes_positions.
Of course if already inserted combination contains one additional attributes_positions or one less than inserting combination is ok
image I show the different bettwen duplicate and not duplicate combination.
Is there a some way how I can do that?? Meaby something like 'before update'. But how to implement for this example. I`m not so pretty good with advanced sql.
The database where I trying to secure table is postgresql 9.4
I will be grateful for help
-- The data
CREATE TABLE theset (
set_id INTEGER NOT NULL PRIMARY KEY
, set_name text UNIQUE
);
INSERT INTO theset(set_id, set_name) VALUES
( 1, 'one'), ( 2, 'two'), ( 3, 'three'), ( 4, 'four');
CREATE TABLE theitem (
item_id integer NOT NULL PRIMARY KEY
, item_name text UNIQUE
);
INSERT INTO theitem(item_id, item_name) VALUES
( 1, 'one'), ( 2, 'two'), ( 3, 'three'), ( 4, 'four'), ( 5, 'five');
CREATE TABLE set_item (
set_id integer NOT NULL REFERENCES theset (set_id)
, item_id integer NOT NULL REFERENCES theitem(item_id)
, PRIMARY KEY (set_id,item_id)
);
-- swapped index is indicated for junction tables
CREATE UNIQUE INDEX ON set_item(item_id, set_id);
INSERT INTO set_item(set_id,item_id) VALUES
(1,1), (1,2), (1,3), (1,4),
(2,1), (2,2), (2,3), -- (2,4),
(3,1), (3,2), (3,3), (3,4), (3,5),
(4,1), (4,2), (4,4);
CREATE FUNCTION set_item_unique_set( ) RETURNS TRIGGER AS
$func$
BEGIN
IF EXISTS ( -- other set
SELECT * FROM theset oth
-- WHERE oth.set_id <> NEW.set_id -- only for insert/update
WHERE TG_OP = 'DELETE' AND oth.set_id <> OLD.set_id
OR TG_OP <> 'DELETE' AND oth.set_id <> NEW.set_id
-- count (common) members in the two sets
-- items not in common will have count=1
AND NOT EXISTS (
SELECT item_id FROM set_item x1
WHERE (x1.set_id = NEW.set_id OR x1.set_id = oth.set_id )
GROUP BY item_id
HAVING COUNT(*) = 1
)
) THEN
RAISE EXCEPTION 'Not unique set';
RETURN NULL;
ELSE
RETURN NEW;
END IF;
END;
$func$ LANGUAGE 'plpgsql'
;
CREATE CONSTRAINT TRIGGER check_item_set_unique
AFTER UPDATE OR INSERT OR DELETE
-- BEFORE UPDATE OR INSERT
ON set_item
FOR EACH ROW
EXECUTE PROCEDURE set_item_unique_set()
;
-- Test it
INSERT INTO set_item(set_id,item_id) VALUES(4,5); -- success
INSERT INTO set_item(set_id,item_id) VALUES(2,4); -- failure
DELETE FROM set_item WHERE set_id=1 AND item_id= 4; -- failure
Note: There should also be a trigger for the DELETE case.
UPDATE: added handling of DELETE
(the handling of deletes is not perfect; imagine the case where the last element from a set is removed)
My answer assumes that the target is without dupes, and that we want to insert a new set - which happens to be a duplicate. I choose the group of 4 with the id_comb of 1.
You would have to put the group of 4 into a staging table. Then, you have to pivot both staging and target horizontally - so that you get 5 columns named attr_pos1 to attr_pos5 (the biggest group in your example is 5). To pivot, you need a sequence number, which we get by using ROW_NUMBER(). That's for both tables, staging and target. Then, you pivot both. Then, you try to join pivoted staging and target on all 5 attr_pos# columns, and count the rows. If you get 0, you have no duplicates. If you get 1, you have duplicates.
Here's the whole scenario:
WITH
-- input section: a) target table, no dupes
target(id_comb,attr_pos) AS (
SELECT 2,1
UNION ALL SELECT 2,2
UNION ALL SELECT 2,3
UNION ALL SELECT 2,4
UNION ALL SELECT 3,1
UNION ALL SELECT 3,2\
UNION ALL SELECT 3,3
UNION ALL SELECT 3,4
UNION ALL SELECT 3,5
UNION ALL SELECT 4,1
UNION ALL SELECT 4,2
UNION ALL SELECT 4,3
)
,
-- input section: b) staging, input, would be a dupe
staging(id_comb,attr_pos) AS (
SELECT 1,1
UNION ALL SELECT 1,2
UNION ALL SELECT 1,3
UNION ALL SELECT 1,4
)
,
-- query section:
-- add sequence numbers to stage and target
target_s AS (
SELECT
ROW_NUMBER() OVER(PARTITION BY id_comb ORDER BY attr_pos) AS seq
, *
FROM target
)
,
staging_s AS (
SELECT
ROW_NUMBER() OVER(PARTITION BY id_comb ORDER BY attr_pos) AS seq
, *
FROM staging
)
,
-- horizontally pivot target, NULLS as -1 for later join
target_h AS (
SELECT
id_comb
, IFNULL(MAX(CASE seq WHEN 1 THEN attr_pos END),-1) AS attr_pos1
, IFNULL(MAX(CASE seq WHEN 2 THEN attr_pos END),-1) AS attr_pos2
, IFNULL(MAX(CASE seq WHEN 3 THEN attr_pos END),-1) AS attr_pos3
, IFNULL(MAX(CASE seq WHEN 4 THEN attr_pos END),-1) AS attr_pos4
, IFNULL(MAX(CASE seq WHEN 5 THEN attr_pos END),-1) AS attr_pos5
FROM target_s
GROUP BY id_comb ORDER BY id_comb
)
,
-- horizontally pivot staging, NULLS as -1 for later join
staging_h AS (
SELECT
id_comb
, IFNULL(MAX(CASE seq WHEN 1 THEN attr_pos END),-1) AS attr_pos1
, IFNULL(MAX(CASE seq WHEN 2 THEN attr_pos END),-1) AS attr_pos2
, IFNULL(MAX(CASE seq WHEN 3 THEN attr_pos END),-1) AS attr_pos3
, IFNULL(MAX(CASE seq WHEN 4 THEN attr_pos END),-1) AS attr_pos4
, IFNULL(MAX(CASE seq WHEN 5 THEN attr_pos END),-1) AS attr_pos5
FROM staging_s
GROUP BY id_comb ORDER BY id_comb
)
SELECT
COUNT(*)
FROM target_h
JOIN staging_h USING (
attr_pos1
, attr_pos2
, attr_pos3
, attr_pos4
, attr_pos5
);
Hope this helps ----
Marco
Interesting but not very useful solution by #wildplasser. I create script to insert sample data:
WITH param AS (
SELECT 8 AS max
), maxarray AS (
SELECT array_agg(i) as ma FROM (SELECT generate_series(1, max) as i FROM param) as i
), pre AS (
SELECT
*
FROM (
SELECT
*, CASE WHEN (id >> mbit) & 1 = 1 THEN ma[mbit + 1] END AS item_id
FROM (
SELECT *,
generate_series(0, array_upper(ma, 1) - 1) as mbit
FROM (
SELECT *,
generate_series(1,(2^max - 1)::int8) AS id
FROM param, maxarray
) AS pre1
) AS pre2
) AS pre3
WHERE item_id IS NOT NULL
), ins_item AS (
INSERT INTO theitem (item_id, item_name) SELECT i, i::text FROM generate_series(1, (SELECT max FROM param)) as i RETURNING *
), ins_set AS (
INSERT INTO theset (set_id, set_name)
SELECT id, id::text FROM generate_series(1, (SELECT 2^max - 1 FROM param)::int8) as id
RETURNING *
), ins_set_item AS (
INSERT INTO set_item (set_id, item_id)
SELECT id, item_id FROM pre WHERE (SELECT count(*) FROM ins_item) > 0 AND (SELECT count(*) FROM ins_set) > 0
RETURNING *
)
SELECT
'sets', count(*)
FROM ins_set
UNION ALL
SELECT
'items', count(*)
FROM ins_item
UNION ALL
SELECT
'sets_items', count(*)
FROM ins_set_item
;
When I call it with 8 (1024 - 2^8 rows for set_item) it run 21 seconds. It is very bad. When I off trigger it took less then 1 milliseconds.
My proposal
It is very interesting to use arrays in this case. Unfortunatelly PostgreSQL does not support foreighn key for arrays, but it may be done by TRIGGERs. I remove set_item table and add items int[] field for theset:
-- The data
CREATE TABLE theitem (
item_id integer NOT NULL PRIMARY KEY
, item_name text UNIQUE
);
CREATE TABLE theset (
set_id INTEGER NOT NULL PRIMARY KEY
, set_name text UNIQUE
, items integer[] UNIQUE NOT NULL
);
CREATE INDEX i1 ON theset USING gin (items);
CREATE OR REPLACE FUNCTION check_item_CU() RETURNS TRIGGER AS $sql$
BEGIN
IF (SELECT count(*) > 0 FROM unnest(NEW.items) AS u LEFT JOIN theitem ON (item_id = u) WHERE item_id IS NULL) THEN
RETURN NULL;
END IF;
NEW.items = ARRAY(SELECT unnest(NEW.items) ORDER BY 1);
RETURN NEW;
END;
$sql$ LANGUAGE plpgsql;
CREATE TRIGGER check_item_CU BEFORE INSERT OR UPDATE ON theset FOR EACH ROW EXECUTE PROCEDURE check_item_CU();
CREATE OR REPLACE FUNCTION check_item_UD() RETURNS TRIGGER AS $sql$
BEGIN
IF (TG_OP = 'DELETE' OR TG_OP = 'UPDATE' AND NEW.item_id != OLD.item_id) AND (SELECT count(*) > 0 FROM theset WHERE OLD.item_id = ANY(items)) THEN
RAISE EXCEPTION 'item_id % still used', OLD.item_id;
RETURN NULL;
END IF;
RETURN NEW;
END;
$sql$ LANGUAGE plpgsql;
CREATE TRIGGER check_item_UD BEFORE DELETE OR UPDATE ON theitem FOR EACH ROW EXECUTE PROCEDURE check_item_UD();
WITH param AS (
SELECT 10 AS max
), maxarray AS (
SELECT array_agg(i) as ma FROM (SELECT generate_series(1, max) as i FROM param) as i
), pre AS (
SELECT
*
FROM (
SELECT
*, CASE WHEN (id >> mbit) & 1 = 1 THEN ma[mbit + 1] END AS item_id
FROM (
SELECT *,
generate_series(0, array_upper(ma, 1) - 1) as mbit
FROM (
SELECT *,
generate_series(1,(2^max - 1)::int8) AS id
FROM param, maxarray
) AS pre1
) AS pre2
) AS pre3
WHERE item_id IS NOT NULL
), pre_arr AS (
SELECT id, array_agg(item_id) AS items
FROM pre
GROUP BY 1
), ins_item AS (
INSERT INTO theitem (item_id, item_name) SELECT i, i::text FROM generate_series(1, (SELECT max FROM param)) as i RETURNING *
), ins_set AS (
INSERT INTO theset (set_id, set_name, items)
SELECT id, id::text, items FROM pre_arr WHERE (SELECT count(*) FROM ins_item) > 0
RETURNING *
)
SELECT
'sets', count(*)
FROM ins_set
UNION ALL
SELECT
'items', count(*)
FROM ins_item
;
This variant run less than 1ms

Delete Oracle rows based on size

I have this Oracle table which I want to clean from time to time when I reach 2000 rows of data:
CREATE TABLE AGENT_HISTORY(
EVENT_ID INTEGER NOT NULL,
AGENT_ID INTEGER NOT NULL,
EVENT_DATE DATE NOT NULL
)
/
How I can delete the oldest row from the table when the table reaches 2000 rows?
You can delete all but the newest 2000 rows with the following query:
DELETE FROM agent_history a
WHERE 2000 < ( SELECT COUNT(1) cnt FROM agent_history b WHERE b.event_date < a.event_date )
The query checks every row in the table (a) to see how many rows have an event_date LESS than that row. If there are more than 2000 rows less than it, then it will delete that row.
Let me know if this doesn't work.
Create a DBMS_JOB or DBMS_SCHEDULER, that kicks off after certain interval and call a procedure. In that procedure check the count and delete the rows based on event_date.
Sorry, I didn't see your comment until now. Here is the code you were looking for. Make sure you have the grants to create scheduler program and jobs. This code assumes that the event_id is a sequence of #s and keeps up with the event_date. Otherwise change the rank based on both time and id or of your choice. Also you can change time interval. Check DBMS_SCHEDULER package documentation for any errors and corrections.
create or replace procedure proc_house_keeping is
begin
delete
from (
select rank() over (order by event_id desc) rnk
from agent_history
)
where rnk > 2000;
commit;
end;
/
begin
dbms_scheduler.create_program(
program_name => 'PROG_HOUSE_KEEPING',
program_type => 'STORED_PROCEDURE',
program_action => 'PROC_HOUSE_KEEPING',
number_of_arguments => 0,
enabled => FALSE,
comments => 'Procedure to delete rows greater than 2000');
end;
/
begin
dbms_scheduler.create_job(
job_name => 'table_house_keeping',
program_name => 'PROG_HOUSE_KEEPING',
start_date => dbms_scheduler.stime,
repeat_interval => 'FREQ=MINUTELY;INTERVAL=1',
end_date => dbms_scheduler.stime+1,
enabled => false,
auto_drop => false,
comments => 'table house keeping, runs every minute');
end;
/
An approach may be adding a trigger to your table, so that it checks and deletes the oldest rows at every INSERT statement; for example, assuming not more than 3 rows:
CREATE OR REPLACE TRIGGER DELETE_3
AFTER INSERT ON AGENT_HISTORY
DECLARE
vNum number;
minDate date;
BEGIN
delete AGENT_HISTORY
where (event_id, agent_id, event_date) in
( select event_id, agent_id, event_date
from (
select event_id, agent_id, event_date, row_number() over (order by event_date desc) num
from AGENT_HISTORY
)
where num > 3 /* MAX NUMBER OF ROWS = 3*/
);
END;
Say we insert 5 rows:
SQL> begin
2 insert into AGENT_HISTORY(EVENT_ID , AGENT_ID, EVENT_DATE) values ( 1, 1, sysdate);
3 dbms_lock.sleep(1);
4 insert into AGENT_HISTORY(EVENT_ID , AGENT_ID, EVENT_DATE) values ( 2, 2, sysdate);
5 dbms_lock.sleep(1);
6 insert into AGENT_HISTORY(EVENT_ID , AGENT_ID, EVENT_DATE) values ( 3, 3, sysdate);
7 dbms_lock.sleep(1);
8 insert into AGENT_HISTORY(EVENT_ID , AGENT_ID, EVENT_DATE) values ( 4, 4, sysdate);
9 dbms_lock.sleep(1);
10 insert into AGENT_HISTORY(EVENT_ID , AGENT_ID, EVENT_DATE) values ( 5, 5, sysdate);
11 commit;
12 end;
13 /
PL/SQL procedure successfully completed.
we only have the newest 3:
SQL> select * from AGENT_HISTORY;
EVENT_ID AGENT_ID EVENT_DATE
---------- ---------- ---------------------------------------------------------------------------
3 3 18-FEB-16 17:05:24,000000
4 4 18-FEB-16 17:05:25,000000
5 5 18-FEB-16 17:05:26,000000