How to add values in one table automatically when a condition is true - sql

So, Here is the condition.
I have a User_tbl whose code is as follow
CREATE TABLE Users_tbl (
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
username TEXT,
password TEXT,
user_type INT
);
user_type is either 0,1 or 2 .. If it is 0 then it is player , 1 is for coach and 2 is for audience.
Now, I want to create a new table which has lists of coach inside it . Whose schema will be
CREATE TABLE coach_tbl(
coach_id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
username TEXT,
password TEXT
)
and what I need is that, when the entry which is placed in Users_tbl has user_type =1 then it should Trigger one other query which will create an entry in coach_tbl and fill the columns. It should happen dynamically .

The following TRIGGER would accomplish what you want :-
CREATE TRIGGER setup_coach
AFTER INSERT ON Users_tbl
WHEN new.user_type = 1
BEGIN
INSERT INTO coach_tbl (username, password) VALUES(new.username,new.password);
END
;
The following was used for testing the above :-
DROP TABLE IF EXISTS Users_tbl;
CREATE TABLE IF NOT EXISTS Users_tbl (
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
username TEXT,
password TEXT,
user_type INT
);
DROP TABLE IF EXISTS coach_tbl;
CREATE TABLE IF NOT EXISTS coach_tbl(
coach_id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
username TEXT,
password TEXT
);
CREATE TRIGGER setup_coach
AFTER INSERT ON Users_tbl
WHEN new.user_type = 1
BEGIN
INSERT INTO coach_tbl (username, password) VALUES(new.username,new.password);
END
;
INSERT INTO Users_tbl (username, password,user_type)
VALUES
('Fred','fred1234',0),
('Bert','bert1234',1),
('Harold','harold1234',0),
('Alan','alan1234',1);
The result being :-
Supplementary (aka Why not the above)
Storing the exact same values. i.e. duplicating them, is contrary to normalisation and could lead to issues. e.g. if (using the above) "Bert"'s name or "password" changed you'd have to change it in two places. As it stands there is no need to duplicate this data as it would be easily available
For example, if you wanted to list the coaches you could use :-
SELECT username FROM coach_tbl;
Using the following, though, returns exactly the same but without the additional table :-
SELECT username FROM Users_tbl WHERE user_type = 1;
Supposing you had coach specific information say for example the number of times coaching then you could have an additional table such as :-
CREATE TABLE IF NOT EXISTS coaching_information
(
user_id_reference INTEGER REFERENCES Users_tbl(id),
number_of_times_coaching INTEGER DEFAULT 0
)
;
Along with a TRIGGER to automatically add default coaching information :-
CREATE TRIGGER autoadd_coaching_information_entry
AFTER INSERT ON Users_tbl
WHEN new.user_type = 1
BEGIN
INSERT INTO coaching_information
(user_id_reference)
VALUES (new.id)
;
END
;
Note! no need to set the number_of_times_coaching column as it will default to 0.
Assuming that all TABLES have been emptied then using (again):-
INSERT INTO Users_tbl (username, password,user_type)
VALUES
('Fred','fred1234',0),
('Bert','bert1234',1),
('Harold','harold1234',0),
('Alan','alan1234',1);
results in :-
2 refers to Bert, 4 to Alan
You could then list all coaches who have not coached (chosen for laziness of not having to update coaching inforamtion) :-
SELECT 'Coach '||username||' is up for a coaching experience.' AS coaching_needed
FROM coaching_information
JOIN Users_tbl ON Users_tbl.id = coaching_information.user_id_reference
WHERE coaching_information.number_of_times_coaching < 1
The result would be :-
Say Bert changed name to Charles e.g. using UPDATE Users_tbl SET username = 'Charles' WHERE username = 'Bert';
Then just with the 1 change the results from the above query would be :-
The full SQL used for testing the above was :-
DROP TABLE IF EXISTS coach_tbl;
DROP TABLE IF EXISTS Users_tbl;
CREATE TABLE IF NOT EXISTS Users_tbl (
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
username TEXT,
password TEXT,
user_type INT
);
DROP TRIGGER IF EXISTS setup_coach; -- get rid of defunct TRIGGER
CREATE TABLE IF NOT EXISTS coaching_information
(
user_id_reference INTEGER REFERENCES Users_tbl(id) ON DELETE CASCADE,
-- Note ON DELETE CASCADE will automatically delete a coach should
-- the coach's User_tbl entry be deleted i.e. the deletion is propoagted
-- to the children of the parent.
-- (could also use ON UPDATE CASCADE but unlikely to change the underlying id)
number_of_times_coaching INTEGER DEFAULT 0
)
;
DROP TRIGGER IF EXISTS autoadd_coaching_information_entry;
CREATE TRIGGER autoadd_coaching_information_entry
AFTER INSERT ON Users_tbl
WHEN new.user_type = 1
BEGIN
INSERT INTO coaching_information
(user_id_reference)
VALUES (new.id)
;
END
;
INSERT INTO Users_tbl (username, password,user_type)
VALUES
('Fred','fred1234',0),
('Bert','bert1234',1),
('Harold','harold1234',0),
('Alan','alan1234',1)
;
--
-- Note! oncommmenting this will change Bert's name to Charles
--UPDATE Users_tbl SET username = 'Charles' WHERE username = 'Bert';
--
SELECT 'Coach '||username||' is up for a coaching experience.' AS coaching_needed
FROM coaching_information
JOIN Users_tbl ON Users_tbl.id = coaching_information.user_id_reference
WHERE coaching_information.number_of_times_coaching < 1

Related

Update and log only changed rows with SQL in SQLite

I am writing an application/script in R that updates a SQLite database.
My apologies - I am not experienced with this.
My table consists of 4 fields Id,Name,LVL,Notes:
CREATE TABLE members (
Id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
Name TEXT NOT NULL,
LVL INTEGER NOT NULL,
Notes TEXT
);
INSERT INTO members (Name,LVL,Notes)
VALUES ('Jean',12,'First stage'),
('Jacques',1,'Second stage'),
('Amelie',1,'Second stage'),
('Louis',13,'Some other note altogether')
;
I want to check it against another table tmp
CREATE TABLE tmp (
Name TEXT NOT NULL,
LVL INTEGER NOT NULL,
Notes TEXT
);
INSERT INTO tmp (Name,LVL,Notes)
VALUES ('Jean',13,'First stage'),
('Jacques',1,'Second stage'),
('Amelie',1,'Third stage'),
('Louis',14,'Fourth stage')
;
and if there are changes in LVL and/or Notes fields (as LVL for Jean and Louis and Notes for Amelie and Louis) I want to update the members table with new values after I record the previous values (as whole rows) with a timestamp in member_changes table.
What would be the minimal set of queries to achieve this?
And what is the better design of the member_changes table? Would it be the same as members but with added rowID as primary key and timestamp fields? And naturally memberID would allow duplicates.
Many thanks,
Rob
SYNOPSIS of expanded answer
Thanks to #forpas kind answer I put this small system together with 2 additional triggers. New information comes in via tmp table. Member names are presumed to be unique; possibly primary key on members.Id was not needed. Nevertheless:
-- CREATE members table for current guild members
-- Id is prim key and Name has unique index
CREATE TABLE members (
Id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
Name TEXT NOT NULL UNIQUE,
LVL INTEGER NOT NULL,
Notes TEXT
);
-- SAMPLE DATA
INSERT INTO members (Name,LVL,Notes) VALUES
('Jean',12,'First stage'),
('Jacques',1,'Second stage'),
('Amelie',1,'Second stage'),
('Louis',13,'Some other note altogether');
-- LOG table to see membership changes over time
CREATE TABLE members_changes (
timestamp TEXT DEFAULT CURRENT_TIMESTAMP,
Id INTEGER REFERENCES members(Id),
Name TEXT NOT NULL,
LVL INTEGER NOT NULL,
Notes TEXT
);
-- TABLE through which the updates will come in via rvest
-- presumed cannot contain duplicate names
CREATE TABLE tmp (
Name TEXT NOT NULL UNIQUE,
LVL INTEGER NOT NULL,
Notes TEXT
);
-- TRIGGERS (3)
-- (1) UPDATES MEMBERS if insertion in tmp shows changes
-- also LOGS this change in members_changes
CREATE TRIGGER IF NOT EXISTS tr_insert_tmp AFTER INSERT ON tmp
BEGIN
INSERT INTO members_changes(Id,Name,LVL,Notes)
SELECT Id,Name,LVL,Notes
FROM members
WHERE Name = NEW.NAME AND (LVL IS NOT NEW.LVL OR Notes IS NOT NEW.Notes);
UPDATE members
SET LVL = NEW.LVL, Notes = NEW.Notes
WHERE Name = NEW.Name AND (LVL IS NOT NEW.LVL OR Notes IS NOT NEW.Notes);
END;
-- (2) LOGS DELETIONS from members
CREATE TRIGGER IF NOT EXISTS tr_delete_members BEFORE DELETE ON members
BEGIN
INSERT INTO members_changes(Id,Name,LVL,Notes)
SELECT Id,Name,LVL,Notes || " :Deleted"
FROM members
WHERE Name = OLD.Name;
END;
-- (3) LOGS INSERTS into members (new members)
CREATE TRIGGER IF NOT EXISTS tr_insert_members AFTER INSERT ON members
BEGIN
INSERT INTO members_changes(Id,Name,LVL,Notes)
SELECT Id,Name,LVL,Notes || " :Inserted"
FROM members
WHERE Name = NEW.Name;
END;
-- this shows all defined triggers
select * from sqlite_master where type = 'trigger';
-- QUERIES to be run from the script after tmp is updated (b,c,d)
-- ADD NEW MEMBERS
-- it should mostly fail (changes are slow and few)
-- this is logged via tr_insert_members
INSERT OR IGNORE INTO members(Name,LVL,Notes) SELECT Name, LVL, Notes FROM tmp;
-- DELETE OLD MEMBERS
-- logged via tr_delete_members
DELETE FROM members WHERE Name NOT IN (SELECT Name FROM tmp);
-- EMPTY tmp at the end of the script run
DELETE FROM tmp;
When application runs the only queries that need to be called are:
a) the one which populates tmp (from dataframe gathered by rvest)
b) query to add new members from tmp
c) query to delete members not in tmp
d) query to empty tmp
This is thanks to database setup kindly suggested by #forpas. I had never used triggers and finally made some sense of them. Very helpful for logging changes.
A proper design for members_changes is this:
CREATE TABLE members_changes (
timestamp TEXT DEFAULT CURRENT_TIMESTAMP,
Id INTEGER REFERENCES members(Id),
Name TEXT NOT NULL,
LVL INTEGER NOT NULL,
Notes TEXT
);
The column timestamp's default value is the current timestamp.
You need an AFTER INSERT trigger for the table tmp, so that for every inserted row in tmp, the respective row from members will be inserted in members_changes (if any value of LVL or Notes is different) and after that the new row from tmp will update the row of members:
CREATE TRIGGER IF NOT EXISTS tr_insert_tmp AFTER INSERT ON tmp
BEGIN
INSERT INTO members_changes(Id,Name,LVL,Notes)
SELECT Id,Name,LVL,Notes
FROM members
WHERE Name = NEW.NAME AND (LVL IS NOT NEW.LVL OR Notes IS NOT NEW.Notes);
UPDATE members
SET LVL = NEW.LVL, Notes = NEW.Notes
WHERE Name = NEW.Name AND (LVL IS NOT NEW.LVL OR Notes IS NOT NEW.Notes);
END;
See the demo.

Problem with alias in Sqliteman trying to make unique id´s

I´m trying to translate this function from SQL server to Sqlite, how can i do it? I know it´s different but I just couldn´t. Any information will be awesome. Thanks
This is the code:
CREATE TABLE "user" (
"numId" INT IDENTITY(1,1) NOT NULL,
"prefix" VARCHAR(50) NOT NULL,
"id" AS ("prefix"+RIGHT('000000'+CAST(ID AS VARCHAR(7)),7))PERSISTED,
Thanks.
Normal SQL tables are called rowid tables, and have a unique integer as their primary key (A INTEGER PRIMARY KEY column aliases this rowid). So there's the IDENTITY bit taken care of.
While Sqlite 3.31 (In beta as of this writing) supports generated columns, until versions with that feature become widely available over the next few years, you can either create a VIEW that adds your custom prefix, or do it in each individual SELECT where it matters. Something like:
sqlite> CREATE TABLE actual_user(numId INTEGER PRIMARY KEY, prefix TEXT NOT NULL);
sqlite> CREATE VIEW user(numId, prefix, id) AS
...> SELECT numId, prefix, printf('%s%07d', prefix, numId) FROM actual_user;
sqlite> INSERT INTO actual_user(prefix) VALUES ('TEST'), ('TEST'), ('TESTX');
sqlite> SELECT * FROM user;
numId prefix id
---------- ---------- ------------
1 TEST TEST0000001
2 TEST TEST0000002
3 TESTX TESTX0000003
You might also emulate it with triggers for your table that updates the id value on insertion and update. This more closely mimics a persist (stored in SQLite lingo) generated column, while the view mimics a virtual column:
sqlite> CREATE TABLE user(numId INTEGER PRIMARY KEY, prefix TEXT NOT NULL, id TEXT);
sqlite> CREATE TRIGGER user_ai AFTER INSERT ON user
...> BEGIN UPDATE user SET id = printf('%s%07d', new.prefix, new.numId) WHERE numId = new.numId; END;
sqlite> CREATE TRIGGER user_au AFTER UPDATE OF numId, prefix ON user
...> BEGIN UPDATE user SET id = printf('%s%07d', new.prefix, new.numId) WHERE numId = new.numId; END;
sqlite> INSERT INTO user(prefix) VALUES ('TEST');
sqlite> SELECT * FROM user;
numId prefix id
---------- ---------- ------------
1 TEST TEST0000001
sqlite> UPDATE user SET prefix='APPLE' WHERE numId = 1;
sqlite> SELECT * FROM user;
numId prefix id
---------- ---------- ------------
1 APPLE APPLE0000001

How can I fill empty table rows which references another table's column ID?

I want to fill out empty notification_settings for each user that already exists. IDs (PKs) are auto-generated by Hibernate in each table. Here is the user Table :
CREATE TABLE lottery_user (
id int8 not null,
email varchar(255) not null,
password varchar(255) not null,
notification_settings_id int8,
role varchar (50) default 'USER',
registry_date TIMESTAMP default now(),
primary key (id)
ADD CONSTRAINT FK_USER_TO_NOTIFICATION_SETTINGS
FOREIGN KEY (notification_settings_id) REFERENCES notification_settings
);
And here is the notification_settings table which I need to fill out for users that don't have it filled out for them.
CREATE TABLE notification_settings (
id int8 not NULL ,
test1_events bool DEFAULT TRUE ,
test2_events bool DEFAULT TRUE ,
test3_events bool DEFAULT TRUE ,
test4_events bool DEFAULT TRUE ,
PRIMARY KEY (id)
);
Basically, I need to use "INSERT INTO notification_settings (test1_events, test2_events, test3_events, test4_events) VALUES (True, True, True, True)" something similar to that. And of course, condition should be something like this "where these rows are empty for users". I can't seem to get Syntax right.
BIG NOTE: SQL code is for presentation purpose, so you can have an idea what kind of tables I have. I just need to get INSERT script right. Tables are working fine, just need to generate notification_settings values for users that already exist.
Another Note: Using Flyway, so it's not just about Hibernate. If that has to do with anything.
Are you just looking for:
INSERT INTO notification_settings (id)
SELECT id
FROM user
WHERE id NOT IN (SELECT id FROM notifiation_settings)
You might be looking to insert into an identity field:
SET IDENTITY_INSERT my_table ON
Since your foreign key constraint goes from notification_settings to user, the condition "where these rows are empty for user X" does not apply to your schema. On the other hand - "I want to fill out empty notification_settings for each user that already exists" can be done by using an insert...select construct:
set #rank=0
select #maxid = max(id) from notification_settings
insert into notification_settings (id)
select #maxid + #rank:=#rank+1 as rank
from user
where notification_settings_id is null
What is interesting is how you put those newly generated IDs back into the user table. Homework assignment for next time :)
INSERT INTO notification_settings (id)
SELECT u.id
FROM user u
WHERE
not exists (SELECT * FROM notifiation_settings ns where ns.id=i.id)
I will answer my own question how I dealt with it. Firstly, I insert IDs into notification_settings id, then I get those IDs and set them into lottery_user table's FK (notification_settings_id). Then I just delete unneeded IDs. Yea not perfect but it works.
INSERT INTO notification_settings (id) select lu.id from lottery_user lu where lu.id not in(select ns.id from notification_settings ns);
update lottery_user lu set notification_settings_id = (select ns.id from notification_settings ns where ns.id = lu.id) where lu.notification_settings_id is null;
delete from notification_settings ns where not exists (select * from lottery_user lu where lu.notification_settings_id = ns.id);
Also, script to Alter Sequence for new Lottery_user entities.
do $$
declare maxid int;
begin
select max(id) from lottery_user into maxid;
IF maxid IS NOT NULL THEN
EXECUTE 'ALTER SEQUENCE notification_settings_seq START with '|| maxid;
END IF;
end;
$$ language plpgsql;

Auto increment an ID with a string prefix in oracle sql?

I have created the following table and I wish to have the userID auto-increment whenever there is a row added to the database. However I would like the ID to be formatted as 000001 for example as below, since there are a few tables and it would be ideal to give each ID a string prefix:
userID
----------
user000001
user000002
user000003
CREATE TABLE UserTable (
userID VARCHAR(20),
username VARCHAR(250) NOT NULL,
firstName VARCHAR(250) NOT NULL,
lastName VARCHAR(250) NOT NULL,
CONSTRAINT pkUserID
PRIMARY KEY (userID),
CONSTRAINT uniUsername
UNIQUE (username)
);
You would have to use a combination of trigger and sequence as shown in the code below:
CREATE SEQUENCE CREATE SEQUENCE usertable_seq
START WITH 1
INCREMENT BY 1
NOCACHE
NOCYCLE;
/
CREATE OR REPLACE TRIGGER usertable_trigger
BEFORE INSERT ON UserTable
FOR EACH ROW
BEGIN
SELECT 'user' || to_char(usertable_seq.NEXTVAL, '000099')
INTO :new.userID
FROM dual;
END;
/
The prefix user is absolute pointless because it is attached to every ID. Drop it and use an ID NUMBER only. Plus, follow Jugal's advice.

Firebird autoIncrement issue

I've created Customers Table through following code :
CREATE TABLE CUSTOMERS (
ID INTEGER DEFAULT 1 NOT NULL,
"NAME" VARCHAR(30) CHARACTER SET UTF8 COLLATE UTF8,
"LASTNAME" VARCHAR(30) CHARACTER SET UTF8 COLLATE UTF8);
ALTER TABLE CUSTOMERS ADD PRIMARY KEY (ID);
SET TERM ^ ;
CREATE TRIGGER BI_CUSTOMERS_ID FOR CUSTOMERS
ACTIVE BEFORE INSERT
POSITION 1
AS
BEGIN
IF (NEW.ID IS NULL) THEN
NEW.ID = GEN_ID(CUSTOMERS_ID_GEN, 1);
END^
SET TERM ; ^
But when I inserting second row like :
insert into Customers(Name,LastName) values('Hamed','Kamrava');
It gets below error :
Violation of PRIMARY or UNIQUE KEY constraint "INTEG_2" on table "CUSTOMERS".
id is a primary key with default value 1.
In the first record, since you have not explicitly mentioned the value of id, it has inserted with 1. But you cannot have any other records with id = 1 since id is a Primary Key.
Use the statement:
insert into Customers(id, Name, LastName) values (2, 'Hamed', 'Kamrava');
This should insert the record. If you do not want to hardcode the value of ID for each row, suggest you to create a sequence and then during the insert, use,
insert into Customers(id, Name, LastName) values (nextval('<seq_name>'), <name>, <lastname>);
Since your trigger code is
IF (NEW.ID IS NULL) THEN
NEW.ID = GEN_ID(CUSTOMERS_ID_GEN, 1);
and, as #Orangecrush posted, you set a default value of 1, a unique id is never generated. So you should try to omit the default value in the ddl.