help with mysql triggers (checking values before insert) - mysql

hi I'm quite new to mysql and I'm trying to figure out how to use triggers.
what I'm trying to do:
I have 2 tables, max and sub_max, when I insert a new row to sub_max I want to check if the SUM of the values with the same foreign_key as the new row are less than the value in the max table. I think this sounds confusing so here are my tables:
CREATE TABLE max(
number INT ,
MaxAmount integer NOT NULL)
CREATE TABLE sub_max(
sub_number INT ,
sub_MaxAmount integer NOT NULL,
number INT,
FOREIGN KEY ( number ) REFERENCES max( number ))
and here is my code for the trigger, I know the syntax is off but this is the best I could do from looking up tutorials.
CREATE TRIGGER maxallowed
after insert on sub_max
FOR EACH ROW
BEGIN
DECLARE submax integer;
DECLARE maxmax integer;
submax = select sum(sub_MaxAmount) from sub_max where sub_number = new.sub_number;
submax = submax + new. sub_MaxAmount;
maxmax = select MaxAmount from max where number = new.number ;
if max>maxmax
rollback?
END
I wanted to know if I'm doing this remotely correctly. Thanks in advance.

Caveat - I am also learning triggers.
For the section:
if max>maxmax
rollback?
Would the syntax be something like?:
IF max > maxmax THEN
DELETE the id of the new record?
ELSE
do nothing?
END IF;

Related

Create an auto_increment column based on a group in MySQL

I'm quite new to SQL and databases.
I'm trying to make a preference table of an user.
The fields will be user_id, pref_no, prg_code.
Now if I create the table making pref_no auto_increment then it will automatically increase irrespective of the user_id.
So, my question is - Is there a way to define the table in such a way that it will be auto_incremented taking user_id into account or I have to explicitly find the last pref_no that has occurred for an user and then increment it by 1 before insertion?
Any help is appreciated. Thanks in advance.
Following what Mjh and Fahmina suggested, we can create a procedure for the insertion.
DELIMITER //
CREATE PROCEDURE test(IN u_id INT(7), p_code INT(5))
BEGIN
SELECT #pno:= MAX(pref_no) FROM temp_choice WHERE user_id = u_id;
IF #pno IS NULL THEN
SET #pno = 1;
ELSE
SET #pno = #pno + 1;
END IF;
INSERT INTO temp_choice VALUES (u_id, #pno, p_code);
END //
DELIMITER ;
Now we can easily insert data by using
CALL test(1234567, 10101);
To manage user's preference, you don't need user_id to be auto_incremented in this table, but pref_no has to be.
user_id will just be a refence (or foreign key in sql) to your user table (where user_id should be auto_incremented).
And to request preference for a given user your request would be :
SELECT * FROM [user table] INNER JOIN [pref table] ON ([user table].user_id = [pref table].user_id) WHERE [user table].user_id = ?
(replace '?' by the user_id you want)

Mysql prevent insert after some date

Is possible prevent the insertion in a mysql table after some date and time?
for example i have 2 tables:
TABLE exams
(
...
examID int primary key auto_increment,
examDate DATE,
examTime TIME,
...
)
TABLE inscripts
(
...
examID int not null references exams(examID),
insDate DATE,
insTime TIME,
...
)
i want to prevent the insertion of new inscriptions when the exam is playing.
There are some function to prevent this or check easily if current date and current time are equal or bigger than the date and time in matches table?
You could create a trigger that validates the insert before proceeding. Pseudo-code, but something like:
DELIMITER |
CREATE TRIGGER `Stop_Insert_During_Exam`
BEFORE UPDATE ON `inscripts`
FOR EACH ROW
BEGIN
DECLARE msg VARCHAR(255);
-- Check
-- Test if an exam is going on. If so:
SET msg = "You cannot add an inscription while an exam is going on.';
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = msg;
-- /Check
END;
|
Looking at your table, you already know the examid when you try to insert an inscription. So you can just grab the exam before you insert the inscription and check if it is ongoing. If it is just dont insert it and if it is not then insert it.

Should I use cursors in my SQL procedure?

I have a table that contains computer login and logoff events. Each row is a separate event with a timestamp, machine name, login or logoff event code and other details. I need to create a SQL procedure that goes through this table and locates corresponding login and logoff event and insert new rows into another table that contain the machine name, login time, logout time and duration time.
So, should I use a cursor to do this or is there a better way to go about this? The database is pretty huge so efficiency is certainly a concern. Any suggested pseudo code would be great as well.
[edit : pulled from comment]
Source table:
History (
mc_id
, hs_opcode
, hs_time
)
Existing data interpretation:
Login_Event = unique mc_id, hs_opcode = 1, and hs_time is the timestamp
Logout_Event = unique mc_id, hs_opcode = 2, and hs_time is the timestamp
First, your query will be simpler (and faster) if you can order the data in such a way that you don't need a complex subquery to pair up the rows. Since MySQL doesn't support CTE to do this on-the-fly, you'll need to create a temporary table:
CREATE TABLE history_ordered (
seq INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
hs_id INT,
mc_id VARCHAR(255),
mc_loggedinuser VARCHAR(255),
hs_time DATETIME,
hs_opcode INT
);
Then, pull and sort from your original table into the new table:
INSERT INTO history_ordered (
hs_id, mc_id, mc_loggedinuser,
hs_time, hs_opcode)
SELECT
hs_id, mc_id, mc_loggedinuser,
hs_time, hs_opcode
FROM history ORDER BY mc_id, hs_time;
You can now use this query to correlate the data:
SELECT li.mc_id,
li.mc_loggedinuser,
li.hs_time as login_time,
lo.hs_time as logout_time
FROM history_ordered AS li
JOIN history_ordered AS lo
ON lo.seq = li.seq + 1
AND li.hs_opcode = 1;
For future inserts, you can use a trigger like below to keep your duration table updated automatically:
DELIMITER $$
CREATE TRIGGER `match_login` AFTER INSERT ON `history`
FOR EACH ROW
BEGIN
IF NEW.hs_opcode = 2 THEN
DECLARE _user VARCHAR(255);
DECLARE _login DATETIME;
SELECT mc_loggedinuser, hs_time FROM history
WHERE hs_time = (
SELECT MAX(hs_time) FROM history
WHERE hs_opcode = 1
AND mc_id = NEW.mc_id
) INTO _user, _login;
INSERT INTO login_duration
SET machine = NEW.mc_id,
logout = NEW.hs_time,
user = _user,
login = _login;
END IF;
END$$
DELIMITER ;
CREATE TABLE dummy (fields you'll select data into, + additional fields as needed)
INSERT INTO dummy (columns from your source)
SELECT * FROM <all the tables where you need data for your target data set>
UPDATE dummy SET col1 = CASE WHEN this = this THEN that, etc
INSERT INTO targetTable
SELECT all columns FROM dummy
Without any code that you're working on.. it'll be hard to see if this approach will be any useful.. There may be some instances when you really need to loop through things.. and some instances when this approach can be used instead..
[EDIT: based on poster's comment]
Can you try executing this and see if you get the desired results?
INSERT INTO <your_target_table_here_with_the_three_columns_required>
SELECT li.mc_id, li.hs_time AS login_time, lo.hs_time AS logout_time
FROM
history AS li
INNER JOIN history AS lo
ON li.mc_id = lo.mc_id
AND li.hs_opcode = 1
AND lo.hs_opcode = 2
AND lo.hs_time = (
SELECT min(hs_time) AS hs_time
FROM history
WHERE hs_time > li.hs_time
AND mc_id = li.mc_id
)

track multiple identical entries

I got a table that I am allowing identical entries (duplicates, triplecates etc.) but I also got a column that I want everytime that an entry is being made to be updated with the how many times that entry exists.
So I thought to write a trigger, I can already find the duplicate entries by doing
select count(pid) from items group by pid having count(*);
but the thing is that this query returns less columns that the orinal table (cause there are many duplicates)
so there is no 1 to 1 relation between the query and the table so I can use update. How could I modify this to get the desired result
thank you in advanced.
The main problem that you'll face here is that MySQL will not allow you to modify the items table using an AFTER INSERT ... trigger following a modification to the items table itself (think of how this could lead to a circular reference).
One solution is to store the counts in a separate table altogether (say items_pid_info). The primary key of this table would be pid and it is this table that would be updated by the triggers on the main items table. When you need to access the pidCount for a given pid simply join onto this table and you will have up-to-date pid counts for your given pid. Hence:
create table items_pid_info
(pid int unsigned not null primary key,
pidCount int unsigned not null);
Now create INSERT, UPDATE and DELETE triggers on your items table to update the items_pid_info table:
DELIMITER &&
CREATE TRIGGER items_pid_count_ins_trg
AFTER INSERT ON items
FOR EACH ROW BEGIN
DECLARE c int;
set c := (select count(*) from items im where im.pid = NEW.pid);
insert into items_pid_info values (NEW.pid,c) on duplicate key update pidCount = c;
END&&
CREATE TRIGGER items_pid_count_upd_trg
AFTER UPDATE ON items
FOR EACH ROW BEGIN
DECLARE c int;
set c := (select count(*) from items im where im.pid = NEW.pid);
insert into items_pid_info values (NEW.pid,c) on duplicate key update pidCount = c;
END&&
CREATE TRIGGER items_pid_count_del_trg
AFTER DELETE ON items
FOR EACH ROW BEGIN
DECLARE c int;
set c := (select count(*) from items im where im.pid = OLD.pid);
insert into items_pid_info values (OLD.pid,c) on duplicate key update pidCount = c;
END&&
DELIMITER ;
Hope this helps.

MySQL - If exists, get primary key. Else, add entry

My table has two columns: "id" (Auto Increment, Primary) and "number" (Unique). Now I want to the following:
if the number already exists, return the id;
else, add entry to the table and return its id.
What's the most efficient method to do this job?
Note:
There is a greater probability that the number is new;
The table will contain hundreds of thousands of records.
Thank you!
INSERT IGNORE INTO table (number) VALUES (42);
SELECT id FROM table WHERE number = 42;
That's probably the most efficient in MySQL. You could use a Stored Procedure to lump them up, which may or may not be slightly more efficient.
EDIT:
If you think it's going to be rare that new numbers come up, this will be even faster:
SELECT id FROM table WHERE number = 42;
if (!id) {
INSERT INTO table WHERE number = 42;
id = SELECT #LAST_INSERT_ID;
}
There is a possible race condition here if concurrent threads simultaneously select then insert the same number at the same time. In this case, the later insert will fail. You could recover from this by re-selecting on this error condition.
Here is one such stored function that does what you describe:
CREATE FUNCTION `spNextNumber`(pNumber int) RETURNS int(11)
BEGIN
DECLARE returnValue int;
SET returnValue := (SELECT Number FROM Tbl WHERE Number = pNumber LIMIT 1);
IF returnValue IS NULL THEN
INSERT IGNORE INTO Tbl (Number) VALUES (pNumber);
SET returnValue := pNumber; -- LAST_INSERT_ID() can give you the real, surrogate key
END IF;
RETURN returnValue;
END
I know this is old, but it is a common problem. So, for the sake of anyone searching for a solution here are 4 different ways to accomplish this task with performance benchmarks. http://mikefenwick.com/blog/insert-into-database-or-return-id-of-duplicate-row-in-mysql/.