Getting the last value from a sequence in SQL and insert it into another table (Oracle) - sql

I have 2 tables, which were created by executing the following (keep in mind that every query is executed through on node.js, i.e., connection.execute('select....') ):
create table User(userid int not null, name varchar(50) not null, primary key(userid));
create table Random(userid int not null, random int, primary key (userid), foreign key (userid) references User(userid) );
I also added a sequence to auto increment the userid:
create sequence userid_seq;
create or replace trigger userid_bir
before insert on User
for each row
begin
select userid_seq.nextval
into :new.userid
from dual;
end;
/
Now, I have a process whereby after I insert a new user, that user's id is inserted immediately into Random. I browsed through Stackoverflow and came up with:
insert into User(name) values('John');
SET #last_id = LAST_INSERT_ID();
insert into Random(userid, random) values( last_id, 2);
However, I got the following error:
SP2-0735: unknown SET option beginning "#last_id"
Any ideas?
My other concern is that if there are 2 computers trying to insert 2 users (in total) at the same time, the last_id value (or MAX userid value) might mess up. That is, the server might be executing:
insert into User(name) values('John'); ## from Person A
insert into User(name) values('Brian'); ## from Person B
SET #last_id = LAST_INSERT_ID(); ## from Person A, say it's set to 1
SET #last_id = LAST_INSERT_ID(); ## from Person B, say it's set to 2
insert into Random(userid, random) values( last_id, 2); ## from Person A
insert into Random(userid, random) values( last_id, 2); ## from Person B, error: duplicate values?

That's probably MySQL syntax. In Oracle you can use RETURNING INTO clause.
declare
last_id number;
begin
insert into user(name) values('Abcd')
returning userid into last_id;
insert into Random(userid, random) values( last_id, 2);
commit;
end;
/

userid_seq.currval will return the most recent value returned for the sequence in the current session. So you can
INSERT INTO random( userid, random )
VALUES( user_id_seq.currval, 2 );
If you want to have the value in a local variable
DECLARE
l_userid user.user_id%type;
BEGIN
INSERT INTO user(name)
VALUES( 'John' )
RETURNING userid
INTO l_userid;
INSERT INTO random( userid, random )
VALUES( l_userid, 2 );
END;

Related

Oracle masking data using a trigger

Im trying to create a process that masks data. When I create the trigger I'm getting the error.
ORA-04072: invalid trigger type
I'm unsure why and was hoping someone can explain what the problem is and how to fix it.
The end result is when a user queries cards they should see the masked data and WHEN they query CARDS_TBL they should see all the data (unmasked)
Original implementation
CREATE TABLE CARDS (
CARD_ID NUMBER
GENERATED BY DEFAULT AS IDENTITY,
CARD_STR VARCHAR2(16) NOT NULL,
PRIMARY KEY (CARD_ID)
);
INSERT INTO CARDS(CARD_STR) VALUES('4024007187788590');
INSERT INTO CARDS(CARD_STR) VALUES('5432223398564536');
INSERT INTO CARDS(CARD_STR) VALUES('5430445512530934');
INSERT INTO CARDS(CARD_STR) VALUES('4020156755227854');
INSERT INTO CARDS(CARD_STR) VALUES('5431248766892318');
CREATE OR REPLACE VIEW CARDS_V AS
SELECT
CARD_ID,
REGEXP_REPLACE(CARD_STR, '(^\d{3})(.*)(\d{4}$)', '\1**********\3') AS CARD_STR
FROM CARDS;
CREATE OR REPLACE TRIGGER CARDS_TRG_INSERT INSTEAD OF
INSERT ON CARDS_V
FOR EACH ROW
BEGIN
INSERT INTO CARDS (CARD_STR) VALUES (:NEW.CARD_STR);
END;
INSERT INTO CARDS_V (CARD_STR) VALUES ('4011589733550908');
CREATE OR REPLACE TRIGGER CARDS_TRG_UPDATE INSTEAD OF
UPDATE ON CARDS_V
FOR EACH ROW
BEGIN
UPDATE CARDS
SET CARD_STR = :NEW.CARD_STR
WHERE CARD_ID = :OLD.CARD_ID;
END;
CREATE TABLE CARDS_TBL (
CARD_ID NUMBER
GENERATED BY DEFAULT AS IDENTITY,
CARD_STR VARCHAR2(16) NOT NULL,
PRIMARY KEY (CARD_ID)
);
INSERT INTO CARDS_TBL(CARD_STR) VALUES('4024007187788590');
INSERT INTO CARDS_TBL(CARD_STR) VALUES('5432223398564536');
INSERT INTO CARDS_TBL(CARD_STR) VALUES('5430445512530934');
INSERT INTO CARDS_TBL(CARD_STR) VALUES('4020156755227854');
INSERT INTO CARDS_TBL(CARD_STR) VALUES('5431248766892318');
CREATE OR REPLACE VIEW CARDS AS
SELECT
CARD_ID,
REGEXP_REPLACE(CARD_STR, '(^\d{3})(.*)(\d{4}$)', '\1**********\3') AS CARD_STR
FROM CARDS_TBL;
CREATE OR REPLACE TRIGGER CARDS_TBL_TRG_UPDATE BEFORE UPDATE ON CARDS_TBL
FOR EACH ROW
BEGIN
UPDATE CARDS_TBL
SET CARD_STR = :NEW.CARD_STR
WHERE CARD_ID = :OLD.CARD_ID;
END;
/
CREATE OR REPLACE TRIGGER CARDS_TBL_TRG_INSERT BEFORE INSERT ON CARDS
FOR EACH ROW
BEGIN
INSERT INTO CARDS_TBL (CARD_STR) VALUES (:NEW.CARD_STR);
END;
INSERT INTO CARDS_TBL (CARD_STR) VALUES ('2222333344445555');
SELECT * FROM CARDS_TBL;
UPDATE CARDS_TBL
SET CARD_STR = '2222333344445566'
WHERE CARD_ID = 6;
/
SELECT * FROM CARDS;
In this particular example, at least, the trigger CARDS_TBL_TRG_UPDATE doesn't do anything (except raise a MUTATING TABLE exception) and can be dispensed with. Get rid of it and your example runs as expected. See this db<>fiddle

INSERT + SELECT data type mismatch on similar fields

I'm running the following SQLite workaround to add a primary key to a table that did not have one. I am getting a datatype mismatch on
INSERT INTO cities
SELECT id, name FROM old_cities;
However, the fields have exactly the same type. Is it possible that his happens due to running the queries from DbBrowser for SQLite?
CREATE table cities (
id INTEGER NOT NULL,
name TEXT NOT NULL
);
INSERT INTO cities (id, name)
VALUES ('pan', 'doul');
END TRANSACTION;
PRAGMA foreign_keys=off;
BEGIN TRANSACTION;
ALTER TABLE cities RENAME TO old_cities;
--CREATE TABLE cities (
-- id INTEGER NOT NULL PRIMARY KEY,
-- name TEXT NOT NULL
--);
CREATE TABLE cities (
id INTEGER NOT NULL,
name TEXT NOT NULL,
PRIMARY KEY (id)
);
SELECT * FROM old_cities;
INSERT INTO cities
SELECT id, name FROM old_cities;
DROP TABLE old_cities;
COMMIT;
You have defined the column id of the table cities to be INTEGER, but with this:
INSERT INTO cities (id, name) VALUES ('pan', 'doul');
you insert the string 'pan' as id.
SQLite does not do any type checking in this case and allows it.
Did you mean to insert 2 rows each having the names 'pan' and 'doul'?
If so, you should do something like:
INSERT INTO cities (id, name) VALUES (1, 'pan'), (2, 'doul');
Later you rename the table cities to old_cities and you recreate cities but you do something different: you define id as INTEGER and PRIMARY KEY.
This definition is the only one that forces type checking in SQLite.
So, when you try to insert the rows from old_cities to cities you get an error because 'pan' is not allowed in the column id as it is defined now.

trigger to create parent element and retrieve id in postgresql

I have created the following tables:
CREATE TABLE IF NOT EXISTS public.teams (
id SERIAL PRIMARY KEY,
name VARCHAR(64) NOT NULL UNIQUE
) WITH (OIDS = FALSE);
CREATE TABLE IF NOT EXISTS public.submissions (
id SERIAL PRIMARY KEY,
team_id INTEGER REFERENCES public.teams NOT NULL,
records_num INTEGER NOT NULL,
timestamp TIMESTAMP NOT NULL
) WITH (OIDS = FALSE);
CREATE TABLE IF NOT EXISTS public.predictions (
id SERIAL PRIMARY KEY,
submission_id INTEGER REFERENCES public.submissions NOT NULL,
customer INTEGER REFERENCES public.real NOT NULL,
date DATE NOT NULL,
billing NUMERIC(20, 2) NOT NULL
) WITH (OIDS = FALSE);
CREATE TABLE IF NOT EXISTS public.real (
customer INTEGER PRIMARY KEY,
date DATE NOT NULL,
billing NUMERIC(20, 2) NOT NULL
) WITH (OIDS = FALSE);
The relation for submissions-predictions is one-to-many; users will submit predictions in packets of 1000 rows that should get the same submission id.
I am trying to create a trigger that runs BEFORE INSERT ON predictions that creates a submissions row. This is what I have so far:
CREATE OR REPLACE FUNCTION insert_submission() RETURNS TRIGGER AS
$$
BEGIN
INSERT INTO submissions(team_id, records_num, timestamp)
VALUES (1, 1, '2018-04-21 00:00:00'); /*example values, need to fill with dynamically assigned ones, specially for records_num and team_id*/
RETURN NULL;
END
$$ LANGUAGE plpgsql;
DROP TRIGGER trigger_submission ON public.predictions;
CREATE TRIGGER trigger_submission BEFORE INSERT ON predictions
EXECUTE PROCEDURE insert_submission();
So, my questions are:
How do I go about retrieving the newly created submissions.id for the row inserted by the trigger, in order to add it to all the rows inserted in predictions by the user? Do I have to run another trigger AFTER INSERT for this?
EDIT: to clarify following #bignose answer, the sequence of events would go like this:
User inserts 1000 rows into public.predictions:
INSERT INTO predictions(customer, date, billing)
VALUES
(1, '2018-01-05', 543.42),
(4, '2018-04-02', 553.21),
...
(423, '2019-11-18', 38.87) /* 1000th row */
He does not know which submission_id to insert in those rows and indeed, the submissions row for this packet of predictions doesn't exist yet so a trigger runs before to create a row in submissions that would execute something like this:
INSERT INTO public.submisssions(team_id, records_num, timestamp)
VALUES (
4, /* I will need something to retrieve team_id here */
1000, /* I will need something to count the rows of the insert that triggered this */
NOW() /* convert to timestamp */
)
This last query should return the public.submission.id value that it has just created to the insert the user requested so that it ends up being something like this:
INSERT INTO predictions(customer, date, billing)
VALUES
(#submission_id, 1, '2018-01-05', 543.42),
(#submission_id, 4, '2018-04-02', 553.21),
...
(#submission_id, 423, '2019-11-18', 38.87) /* 1000th row */
Where #submission_id should be the value retrieved from the trigger (and the some for all the 1000 rows)
How could I count the rows inserted by the user to use them as value for submissions.records_num?
How could I retrieve team.id to insert during the trigger execution, assuming I know team.name beforehand?
Thank you!
Kind regards
A trigger function, when used for a row-level trigger, has access to the old and new state of the table.
CREATE OR REPLACE FUNCTION insert_submission() RETURNS TRIGGER AS
$$
BEGIN
INSERT INTO submissions(team_id, records_num, timestamp)
VALUES (NEW.foo, NEW.bar, '2018-04-21 00:00:00');
RETURN NULL;
END
$$ LANGUAGE plpgsql;
It's not clear from the description, which fields you expect to retrieve from the row that triggers this function. So you'll need to substitute NEW.foo and NEW.bar with field references in the NEW row state.

Variables in PostgreSQL with UUID

All my tables use UUID primary keys. I want to fill tables with test data in one sql file, but can't use UUID PK as FK in other tables, because default function uuid_generate_v4() generates a random value during execution.
Example what I want:
SET Uservar = uuid_generate_v4();
SET Postvar = uuid_generate_v4();
INSERT INTO public."User" (id, name) VALUES (Uservar, 'Foo Bar');
INSERT INTO public."Post" (id, content, userId) VALUES (Postvar, 'Test message', Uservar)
How to do this? Or how to select already created UUID and store for next insert?
E.g. say you had table like this:
create table my_table(uuid_column uuid PRIMARY KEY NOT NULL);
You can insert a variablized uuid like so:
DO $$
DECLARE
my_uuid uuid = uuid_generate_v4();
BEGIN
insert into my_table (uuid_column) values (my_uuid);
select * from my_table where uuid_column = my_uuid;
END $$;
Check this documentation.
N.B. To have uuid_generate_v4() available, make sure you have the below snipped ran before your use it:
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
When you combine both INSERTs into a single statement you can re-use the uuid values quite easily:
WITH newUser(id uuid) AS (
INSERT INTO public."User" (id, name)
VALUES (uuid_generate_v4(), 'Foo Bar')
RETURNING id
)
INSERT INTO public."Post" (id, content, userId)
SELECT uuid_generate_v4(), 'Test message', id
FROM newUser;
When you want to add a post for an existing user, you can use a very similar approach:
INSERT INTO public."Post" (id, content, userId)
SELECT uuid_generate_v4(), 'Test message', id
FROM public."User"
WHERE name = 'Foo Bar';
This would also work when the PK's are auto-generated (i.e. id uuid PRIMARY KEY DEFAULT uuid_generate_v4()) but then you would not explicitly include the PK columns in the INSERT statements.
As I cannot comment
it should be
DO $$
DECLARE
my_uuid uuid := uuid_generate_v4();
BEGIN
insert into my_table (uuid_column) values (my_uuid);
select * from my_table where uuid_column = my_uuid;
END $$;

Why this sequence increments by 2?

I can't understand why this sequence is incremented by 2.
Is there any error in sequence to increment by 1? I need this to insert primary key value in table 'food'.
CREATE SEQUENCE food_id_ai START WITH 1 INCREMENT BY 1 CACHE 100;
create table food(
food_id integer,
f_name varchar(30) not null,
category varchar(30) not null,
price number(4),
amount number(4)
);
alter table food add constraint fpk primary key(food_id);
CREATE OR REPLACE TRIGGER insert_into_food
BEFORE INSERT ON food
FOR EACH ROW
BEGIN
:new.food_id:= food_id_ai.nextval;
END;
/
insert into food values(food_id_ai.nextval,'ruchi', 'chanachur' , 8, 50);
insert into food values(food_id_ai.nextval,'chips', 'chips' , 8, 50);
insert into food values(food_id_ai.nextval,'aeromatic', 'soap' , 8, 50);
insert into food values(food_id_ai.nextval,'handwash', 'toyletries', 8, 50);
insert into food values(food_id_ai.nextval,'tissue', 'toyletries' , 8, 50);
Because you're accessing the sequence both in your INSERT statement and in the trigger that is launched for each row, of course it's incremented by two.
Choose one.
I'd go for the trigger-based one, since you won't have to remember to specify the sequence in each insert statement you may execute.
In that case, you'll have to explicitly list the columns you are going to insert VALUES to:
INSERT INTO food (f_name, category, price, amount)
VALUES ('ruchi', 'chanachur' , 8, 50);
you have two options to correct this.
modify insert statement to be like this:
insert into food (f_name, category,price , amount)
values ('ruchi', 'chanachur' , 8, 50);
or modify you triggers as follow:
CREATE OR REPLACE TRIGGER insert_into_food
BEFORE INSERT ON food
FOR EACH ROW
BEGIN
if :new.food_id is null then
:new.food_id:= food_id_ai.nextval;
end if;
END;
/