Best approach to insert delta records from view into a table - sql

I have a requirement where I do a daily load from a view to a table. After the initial load, there may be scenarios where the original records get deleted from the view's source table. There are also scenarios where these records are updated.
When the stored procedure is run, the table that is loaded should pick up delta records. This means only new inserts. Also, it should mark deleted lines as D. In addition to this, any updates in source data must also updated in this table and marked as U.
Please refer to the attached image which shows in case 1 , 2 inserts on the initial load and then an update and then a delete.
Left side represents the view and right side represents the table I am trying to load.
Thanks!
Shyam

If you prefer to use triggers on HANA database tables you can use following samples on a column table, if you are working with row tables then you can prefer statement based approach
create trigger Salary_A_DEL after DELETE on Salary
REFERENCING OLD ROW myoldrow
FOR EACH ROW
begin
INSERT INTO SalaryLog (
Employee,
Salary,
Operation,
DateTime
) VALUES (
:myoldrow.Employee,
:myoldrow.Salary,
'D',
CURRENT_DATE
);
end;
create trigger Salary_A_UPD after UPDATE on Salary
REFERENCING NEW ROW mynewrow, OLD ROW myoldrow
FOR EACH ROW
begin
INSERT INTO SalaryLog (
Employee,
Salary,
Operation,
DateTime
) VALUES (
:mynewrow.Employee,
:mynewrow.Salary,
'U',
CURRENT_DATE
);
end;
create trigger Salary_A_INS after INSERT on Salary
REFERENCING NEW ROW mynewrow
FOR EACH ROW
begin
INSERT INTO SalaryLog (
Employee,
Salary,
Operation,
DateTime
) VALUES (
:mynewrow.Employee,
:mynewrow.Salary,
'I',
CURRENT_DATE
);
end;

Related

From keyword not found where expected error in oracle

Select firstname as name, time as asof, salary as bal into temp employee
from people.person p
where p.id =1;
Need to create a temporary table employee by inserting values from already created table person which belongs to people database but getting error from keyword not found where expected
You'd then use CTAS (Create Table As Select), not an invalid INTO clause; it is used for different purposes.
create table temp_employee as
select firstname as name,
time as asof,
salary as bal
from people.person p
where p.id = 1;
Based on comment you posted, there are several options you might want to consider.
One is to create a permanent table (just like the above example shows). If you'll reuse it, then - in your procedure - first delete its contents (or truncate the table as it is way faster), and then re-populate it:
delete from temp_employee;
-- or truncate table temp_employee;
insert into temp_employee
select firstname, time, salary from people.person
where id = 1;
Another option is to create a true temporary table, e.g.
create global temporary table temp_employee
(name varchar2(30),
time date,
bal number
)
on commit preserve rows;
Whenever you need data in it, just insert it:
insert into temp_employee (name, time, bal)
select firstname as name,
time as asof,
salary as bal
from people.person p
where p.id = 1;
Doing so, its contents will be visible only to you (and nobody else), while table's contents will be kept during the transaction or session (it depends on how you created it - see the on commit preserve/delete rows clause).
What you should not do is to create the table, drop it, then create it again, and so on - in Oracle, we create table once and use it many times.

Oracle and mutating table with simple exercise

I'm in trouble with the implementation of a trigger.
Assuming that I have two types:
CREATE TYPE customer_t AS OBJECT(
code INTEGER,
name VARCHAR(20),
surname VARCHAR(20),
age INTEGER);
and the type
CREATE TYPE ticket_t AS OBJECT (
price INTEGER,
cust REF customer_t
)
And then I have the associate tables:
CREATE TABLE customers OF TYPE customer_t
CREATE TABLE tickets OF TYPE ticket_t
I have to do an exercise so I have to create a trigger for ensure that a customer won't buy more than 10 tickets but, if I use command like "select count(*)" I get an error because I can't access to mutating table.
Please can anyone help me with this trigger?
EDIT:
I populated the tables as follows:
INSERT INTO custs (code, name, surname, age) values (123, 'Paolo', 'Past', 32);
and repeating the following operation ten times:
INSERT INTO tickets (price, cust) values
(4, (SELECT * FROM (SELECT REF(T) FROM custs T WHERE name = 'Paolo' AND surname = 'Past') WHERE rownum < 2))
The trigger implemented is:
create or replace
trigger check_num_ticket after insert on tickets
for each row
declare
num_ticket number;
begin
SELECT count(*) INTO num_ticket FROM tickets WHERE :new.cust = cust;
if (num_ticket >= 10) then
raise_application_error('-20099', 'no ticket available');
end if;
end;
And I get this error:
A trigger (or a user defined plsql function that is referenced in
this statement) attempted to look at (or modify) a table that was
in the middle of being modified by the statement which fired it.
You are getting the mutating table error, because you are inserting in the same table where you want to get the row count for. Imagine your insert statement inserts two rows. There is no rule which row to insert first and which last, but your trigger fires on one inserted row and wants to know how many rows are already in the table. The DBMS tells you this is undefined, as the table is currently mutating.
You need an after statement trigger instead of a before row trigger. So when the insert statement's inserts are done, you look at the table to see whether there are suddenly customers with too many rows in it.
(A great alternative is a compound trigger. It combines row and statement triggers. So in the after row section you'd remember the customers in some array/collection and in the after statement section you'd look up the table for only the remembered customers.)

SQL trigger function to UPDATE daily moving average upon INSERT

I am trying to create a SQL trigger function which should UPDATE a column when data is INSERTed INTO the table. The update is based on the values present in the values being INSERTed.
I have the following table to store daily OHLC data of a stock.
CREATE TABLE daily_ohlc (
cdate date,
open numeric(8,2),
high numeric(8,2),
low numeric(8,2),
close numeric(8,2),
sma8 numeric(8,2)
);
INSERT command:
INSERT INTO daily_ohlc (cdate, open, high, low, close)
values ('SYMBOL', 101, 110, 95, 108);
When this command is executed I would like to update the 'sma8' column based on the present values being INSERTed and the values already available in the table.
As of now, I am using the following SQL query to calculate the values for every row and then use the result to update the 'sma8' column using python.
SELECT sec.date, AVG(sec.close)
OVER(ORDER BY sec.date ROWS BETWEEN 7 PRECEDING AND CURRENT ROW) AS
simple_mov_avg FROM daily_ohlc sec;
The above query calculates Simple Moving Average over the last 8 records (including the present row).
Using this procedure I update every row of data in the 'sma8' column every time I insert data. I would like to update only the last row (i.e row being INSERTed) by using a trigger. How to do this?
You may do an UPDATE FROM your select query using appropriate joins in your Trigger.
create or replace function update_sma8() RETURNS TRIGGER AS
$$
BEGIN
UPDATE daily_ohlc d SET sma8 = s.simple_mov_avg
FROM
(
SELECT sec.cdate,AVG(sec.close)
OVER(ORDER BY sec.cdate ROWS BETWEEN 7 PRECEDING AND CURRENT ROW) AS
simple_mov_avg FROM daily_ohlc sec
)s where s.cdate = NEW.cdate --The newly inserted cdate
AND d.cdate = s.cdate;
RETURN NULL;
END $$ language plpgsql;
Demo
The only caveat of using this method is that if someone deletes a row or updates close column, then the values have to be recalculated, which won't happen for existing rows. Only the inserted row will see the right re-calculated value.
Instead, you may simply create View to calculate the sma8 column from the main table for all rows when requested.
Can't you just do something along those lines?
INSERT INTO daily_ohlc
SELECT current_date, 101, 110, 95, 108, (COUNT(*)*AVG(close)+108)/(1+Count(*))
FROM daily_ohlc
WHERE cDate >= ANY (
SELECT MIN(cdate)
FROM (SELECT CDate, ROW_NUMBER() OVER (ORDER BY CDate DESC) as RowNum FROM daily_ohlc) a
WHERE RowNum <= 7
)
I know very well it could appear complicated compared to a trigger.
However, I am trying to avoid a case where you successfully create the ON INSERT trigger and next want to handle updates in the table. Updating a table within a procedure triggered by an update in the same table is not the best idea.

SQL Server trigger : update query new vs old data

I have two tables. One has employee info in it. The other I want to add records to based on this employee table.
What I want to happen is whenever there are salary adjustments made to the employee table (by an UDPATE query), the extra table will have a row added to it containing a new event ID and the amount by which salaries have been adjusted (so if 5 people's salaries are increased by £1000, then the row will have the adjustment at £5000).
I've created the trigger and it adds the row with each update. However what it doesn't do is only bring in the additional salary. I can't think how to do that. I have this code so far;
Create trigger Salaryupdate
On TBL_Employees
For update
As
Insert TBL_audit (notes,Delta,AdjDate)
Select 'Salary update', sum(salary), getdate()
From TBL_Employees
I know the sum bit is wrong as I only want the change in salary value, not the total sum.
How can I find the difference between new and old values for the changed rows (or other method)?
I'm using SQL Server 2008.
You should be using the deleted and inserted tables in a trigger. So, I think:
Create trigger Salaryupdate
On TBL_Employees
For update
As
Insert TBL_audit(notes, Delta, AdjDate)
Select 'Salary update',
coalesce(newsalary, 0) - coalesce(oldsalary, 0),
getdate()
From (select sum(salary) as newsalary from inserted) i cross join
(select sum(salary) as oldsalary from deleted) d;
Also, in SQL Server you can set AdjDate to have a default value of getdate() -- that way, the database takes care of setting the value when you insert another row.

Rolling rows in SQL table

I'd like to create an SQL table that has no more than n rows of data. When a new row is inserted, I'd like the oldest row removed to make space for the new one.
Is there a typical way of handling this within SQLite?
Should manage it with some outside (third-party) code?
Expanding on Alex' answer, and assuming you have an incrementing, non-repeating serial column on table t named serial which can be used to determine the relative age of rows:
CREATE TRIGGER ten_rows_only AFTER INSERT ON t
BEGIN
DELETE FROM t WHERE serial <= (SELECT serial FROM t ORDER BY serial DESC LIMIT 10, 1);
END;
This will do nothing when you have fewer than ten rows, and will DELETE the lowest serial when an INSERT would push you to eleven rows.
UPDATE
Here's a slightly more complicated case, where your table records "age" of row in a column which may contain duplicates, as for example a TIMESTAMP column tracking the insert times.
sqlite> .schema t
CREATE TABLE t (id VARCHAR(1) NOT NULL PRIMARY KEY, ts TIMESTAMP NOT NULL);
CREATE TRIGGER ten_rows_only AFTER INSERT ON t
BEGIN
DELETE FROM t WHERE id IN (SELECT id FROM t ORDER BY ts DESC LIMIT 10, -1);
END;
Here we take for granted that we cannot use id to determine relative age, so we delete everything after the first 10 rows ordered by timestamp. (SQLite imposes an arbitrary order on rows sharing the same ts).
Seems SQLite's support for triggers can suffice: http://www.sqlite.org/lang_createtrigger.html
article on fixed queues in sql: http://www.xaprb.com/blog/2007/01/11/how-to-implement-a-queue-in-sql
should be able to use the same technique to implement "rolling rows"
This would be something like how you would do it. This assumes that my_id_column is auto-incrementing and is the ordering column for the table.
-- handle rolls forward
-- deletes the oldest row
create trigger rollfwd after insert on my_table when (select count() from my_table) > max_table_size
begin
delete from my_table where my_id_column = (select min(my_id_column) from my_table);
end;
-- handle rolls back
-- inserts an empty row at the position before oldest entry
-- assumes all columns option or defaulted
create trigger rollbk after delete on my_table when (select count() from my_table) < max_table_size
begin
insert into my_table (my_id_column) values ((select min(my_id_column) from my_table) - 1);
end;