I just want to store records in two way.
If it exists, it will update information
If it dosn't exist, it will insert as new record
Here are the outline schemas for my tables:
product_purchase_item has product_purchase_item_id, product_id, quantity columns
product_stock has product_stock_id, product_id, product_total_quantity columns
I was creating a trigger
DELIMITER //
CREATE TRIGGER store_check AFTER INSERT ON product_purchase_item
FOR EACH ROW
BEGIN
DECLARE X INTEGER;
SET X =(SELECT product_id FROM product_stock where product_id = NEW.product_id);
IF NEW.product_id !=X THEN
INSERT INTO product_stock VALUES(NULL,NEW.product_id,NEW.quantity);
ELSE
UPDATE product_stock
SET product_total_quantity=product_total_quantity+NEW.quantity
WHERE product_id=NEW.product_id;
END IF;
END;//
DELIMITER;//
The problem is in product_stock table, INSERT query doesn't work when product record not exist, but update query work does properly when product record does exist.
Is it really the INSERT statement that "doesn't work"? Or, is the problem that the INSERT statement is not being executed at all?
What happens when the preceding SELECT query does not return a row? What value gets assigned to X?
When X has that value, does a conditional test "foo != X" return TRUE, or does it return something else (like FALSE, or NULL)?
Have you tried something like this?
IF NEW.product_ID = X THEN
UPDATE ...
ELSE
INSERT ...
END IF;
(I know it's bad practice to answer a question with a question; but it seemed apropros, since answering those will get you the answer to the question you really wanted to ask.)
You can do the desired operation through one of MySQL's standard commands:
INSERT INTO product_stock (product_id,product_total_quantity)
VALUES (NEW.product_id,NEW.quantity)
ON DUPLICATE KEY UPDATE product_total_quantity=product_total_quantity+NEW.quantity;
see the MySQL manual
Related
I am new in mysql and I am facing a problem I can't overcome, so I need your help.
I have created a trigger that looks like this:
delimiter //
CREATE trigger NEWBOOKTRIGGER138
after insert ON NEW_SALES
FOR each row
BEGIN
IF (SELECT COUNT(*) FROM NEW_SALES NS
INNER JOIN NEW_BOOKS NB ON NS.ISBN=NB.ISBN GROUP BY GENRE)>138 then
update GENRE set genre=GENRE|| "BEST.SELLER";
ELSEif (SELECT COUNT(*) FROM NEW_SALES NS
INNER JOIN NEW_BOOKS NB ON NS.ISBN=NB.ISBN GROUP BY GENRE)<=138 then
update GENRE set genre= GENRE;
end if;
END;//
In order to change the genre title(in table new_books) into genre BEST.SELLER if a book appears more than 138 times in the new_sales table, let's say.
I get a message that the trigger was successfully created.
The problem is that when I try to insert a tuple in new_books table (in order to see if the trigger actually works) I get an Error code 1242: Subquery returns more than 1 row.
I try to insert the tuple this way:
INSERT INTO NEW_SALES(ISBN,cid)
VALUES ("6666666666666",555);
When I drop the trigger the above insert command works just fine,
But how can I test the functionality of the trigger if I can't insert a new tuple?
Thank u in advance guys.
Kostas
You seem to be updating the wrong table, I assume you want to update the genre column in the NEW_BOOKS table. And it only seems necessary to update it for the book that you're currently inserting, which is NEW.isbn, so you don't need a JOIN.
Also, MySQL doesn't use || for concatenation, it uses the function CONCAT().
CREATE trigger NEWBOOKTRIGGER138
after insert ON NEW_SALES
FOR each row
IF (SELECT COUNT(*) FROM NEW_SALES WHERE isbn = NEW.isbn) > 138
THEN
UPDATE NEW_BOOKS AS b
SET genre = CONCAT(genre, "BEST.SELLER")
WHERE b.isbn = NEW.isbn;
You don't need an ELSEIF statement, since you don't need to do anything to keep the genre the same.
I am writing a trigger to keep track of all the changes that happens in a table. Unfortunately the table has 150+ columns and I wanted to avoid writing each column in the code (Ex. new.col1, new.col2....) and thus I wrote a following query in "after update trigger"
INSERT INTO logs SELECT *, NOW() FROM abc WHERE abc.id = NEW.Id;
This idea is causing multiple issue due to duplication of data that is not changed in update query.
In a nutshell I want to dynamically find out which columns were part of the update query and if that is not possible is there a way to iterate through all the columns of "new" row so I can dynamically compare old.#colName == new.#colName?
I have already seen
Oracle PL/SQL: Loop Over Trigger Columns Dynamically, How to determine if anything changed in update trigger in t-sql and MySQL UPDATE trigger: INSERTing the values of the columns that actually changed.
The last link is the closes to what I need with only one difference, I don't want to hard code column names in following statment because I have way over 100+ columns in all the tables I am going to write similar trigger for!!
IF NEW.column1 <> OLD.column1 THEN INSERT INTO... END IF; IF NEW.column2 <> OLD.column2 THEN INSERT INTO... END IF
I've been doing a bit of research on this this morning and looks like I have come across much of the same search results as you. Ultimately it looks to me like there's no way to loop over all table columns and reference the corresponding old/new values. I'm settling on explicitly checking each column and then logging:
IF (NEW.fld1 <> OLD.fld1) OR (NEW.fld1 IS NOT NULL AND OLD.fld1 IS NULL) OR (NEW.fld1 IS NULL AND OLD.fld1 IS NOT NULL) THEN
INSERT INTO `fld_audit` (`table`, `fldname`, `oldval`, `newval`)
VALUES ("tblname", "fld1", OLD.fld1, NEW.fld1);
END IF;
IF (NEW.fld2 <> OLD.fld2) OR (NEW.fld2 IS NOT NULL AND OLD.fld2 IS NULL) OR (NEW.fld2 IS NULL AND OLD.fld2 IS NOT NULL) THEN
INSERT INTO `fld_audit` (`table`, `fldname`, `oldval`, `newval`)
VALUES ("tblname", "fld2", OLD.fld2, NEW.fld2);
END IF; ...
I found an inkling of another solution here. In theory you could have 3 delimited lists, one for column names, one for old vals and one for new vals. You would have to explicitly reference the old and new vals, but that would be one line (easier to maintain or copy/paste to implement on other tables) and you could then loop. So in pseudo code it would look something like this:
fields_array = concat_ws(",", "fld1", "fld2");
old_vals_array = concat_ws(",", OLD.fld1, OLD.fld2);
new_vals_array = concat_ws(",", NEW.fld1, NEW.fld2);
foreach fields_array as key => field_name
INSERT INTO `fld_audit` (`table`, `fldname`, `oldval`, `newval`)
VALUES ("tblname", field_name, old_vals_array[key], vew_vals_array[key]);
I haven't thought this through too much. You might need to call into a stored procedure rather than set variables. But it might be worth looking into. I've spent enough time on my triggers already. Not sure I could validate (to my boss) trial and error time on a more elegant solution.
As ingratiatednerd already suggested, you can use CONCAT_WS to make strings out of all required values and make a single compare statement.
Perhaps the following is useful to someone:
DECLARE old_concat, new_concat text;
SET old_concat = CONCAT_WS(',', OLD.fld1, OLD.fld2, ...);
SET new_concat = CONCAT_WS(',', NEW.fld1, NEW.fld2, ...);
IF old_concat <> new_concat
THEN
INSERT STATEMENT
END IF;
Assume I've got an users table with 1M users on MySQL/InnoDB:
users
userId (Primary Key, Int)
status (Int)
more data
If I would want to have an exact count of the amount of users with status = 1 (denoting an activate account), what would be the way to go for big tables, I was thinking along the lines of:
usercounts
status
count
And then run an TRIGGER AFTER INSERT on users that updates the appropiate columns in usercounts
Would this be the best way to go?
ps. An extra small question: Since you also need an TRIGGER AFTER UPDATE on users for when status changes, is there a syntax available that:
Covers both the TRIGGER AFTER INSERT and TRIGGER AFTER UPDATE on status?
Increments the count by one if a count already is present, else inserts a new (status, count = 0) pair?
Would this be the best way to go?
Best (opinion-based) or not but it's definitely a possible way to go.
is there a syntax available that: covers both the TRIGGER AFTER INSERT and TRIGGER AFTER UPDATE on status?
No. There isn't a compound trigger syntax in MySQL. You'll have to create separate triggers.
is there a syntax available that: increments the count by one if a count already is present, else inserts a new (status, count = 0) pair?
Yes. You can use ON DUPLICATE KEY clause in INSERT statement. Make sure that status is a PK in usercounts table.
Now if users can be deleted even if only for maintenance purposes you also need to cover it with AFTER DELETE trigger.
That being said your triggers might look something like
CREATE TRIGGER tg_ai_users
AFTER INSERT ON users
FOR EACH ROW
INSERT INTO usercounts (status, cnt)
VALUES (NEW.status, 1)
ON DUPLICATE KEY UPDATE cnt = cnt + 1;
CREATE TRIGGER tg_ad_users
AFTER DELETE ON users
FOR EACH ROW
UPDATE usercounts
SET cnt = cnt - 1
WHERE status = OLD.status;
DELIMITER $$
CREATE TRIGGER tg_au_users
AFTER UPDATE ON users
FOR EACH ROW
BEGIN
IF NOT NEW.status <=> OLD.status THEN -- proceed ONLY if status has been changed
UPDATE usercounts
SET cnt = cnt - 1
WHERE status = OLD.status;
INSERT INTO usercounts (status, cnt) VALUES (NEW.status, 1)
ON DUPLICATE KEY UPDATE cnt = cnt + 1;
END IF;
END$$
DELIMITER ;
To initially populate usercounts table use
INSERT INTO usercounts (status, cnt)
SELECT status, COUNT(*)
FROM users
GROUP BY status
Here is SQLFiddle demo
I think there are simpler options available to you.
Just add an index to the field you'd like to count on.
ALTER TABLE users ADD KEY (status);
Now a select should be very fast.
SELECT COUNT(*) FROM users WHERE status = 1
I am writing a trigger to keep track of all the changes that happens in a table. Unfortunately the table has 150+ columns and I wanted to avoid writing each column in the code (Ex. new.col1, new.col2....) and thus I wrote a following query in "after update trigger"
INSERT INTO logs SELECT *, NOW() FROM abc WHERE abc.id = NEW.Id;
This idea is causing multiple issue due to duplication of data that is not changed in update query.
In a nutshell I want to dynamically find out which columns were part of the update query and if that is not possible is there a way to iterate through all the columns of "new" row so I can dynamically compare old.#colName == new.#colName?
I have already seen
Oracle PL/SQL: Loop Over Trigger Columns Dynamically, How to determine if anything changed in update trigger in t-sql and MySQL UPDATE trigger: INSERTing the values of the columns that actually changed.
The last link is the closes to what I need with only one difference, I don't want to hard code column names in following statment because I have way over 100+ columns in all the tables I am going to write similar trigger for!!
IF NEW.column1 <> OLD.column1 THEN INSERT INTO... END IF; IF NEW.column2 <> OLD.column2 THEN INSERT INTO... END IF
I've been doing a bit of research on this this morning and looks like I have come across much of the same search results as you. Ultimately it looks to me like there's no way to loop over all table columns and reference the corresponding old/new values. I'm settling on explicitly checking each column and then logging:
IF (NEW.fld1 <> OLD.fld1) OR (NEW.fld1 IS NOT NULL AND OLD.fld1 IS NULL) OR (NEW.fld1 IS NULL AND OLD.fld1 IS NOT NULL) THEN
INSERT INTO `fld_audit` (`table`, `fldname`, `oldval`, `newval`)
VALUES ("tblname", "fld1", OLD.fld1, NEW.fld1);
END IF;
IF (NEW.fld2 <> OLD.fld2) OR (NEW.fld2 IS NOT NULL AND OLD.fld2 IS NULL) OR (NEW.fld2 IS NULL AND OLD.fld2 IS NOT NULL) THEN
INSERT INTO `fld_audit` (`table`, `fldname`, `oldval`, `newval`)
VALUES ("tblname", "fld2", OLD.fld2, NEW.fld2);
END IF; ...
I found an inkling of another solution here. In theory you could have 3 delimited lists, one for column names, one for old vals and one for new vals. You would have to explicitly reference the old and new vals, but that would be one line (easier to maintain or copy/paste to implement on other tables) and you could then loop. So in pseudo code it would look something like this:
fields_array = concat_ws(",", "fld1", "fld2");
old_vals_array = concat_ws(",", OLD.fld1, OLD.fld2);
new_vals_array = concat_ws(",", NEW.fld1, NEW.fld2);
foreach fields_array as key => field_name
INSERT INTO `fld_audit` (`table`, `fldname`, `oldval`, `newval`)
VALUES ("tblname", field_name, old_vals_array[key], vew_vals_array[key]);
I haven't thought this through too much. You might need to call into a stored procedure rather than set variables. But it might be worth looking into. I've spent enough time on my triggers already. Not sure I could validate (to my boss) trial and error time on a more elegant solution.
As ingratiatednerd already suggested, you can use CONCAT_WS to make strings out of all required values and make a single compare statement.
Perhaps the following is useful to someone:
DECLARE old_concat, new_concat text;
SET old_concat = CONCAT_WS(',', OLD.fld1, OLD.fld2, ...);
SET new_concat = CONCAT_WS(',', NEW.fld1, NEW.fld2, ...);
IF old_concat <> new_concat
THEN
INSERT STATEMENT
END IF;
What I'm looking to do is insert a record, then deactivate previous records with the same ID because they will no longer be in use. However, I'm looking to do this in the simplest way possible. Deleting the record really isn't an option.
Attempted order of operations:
Insert with active inUse value inUse = 1
Update the following records for the same ID that are no longer in use: inUse = 0
My first thought was to run this query:
UPDATE page_tags
SET inUse = IF(inUse = 1, 0, 1)
WHERE page_id = 23678459
AND tag_id NOT IN (10, 4);
The only problem with this query is that if it's run again, it will toggle all of those deactivated values back to 1. I need all of the tags for the specific ID to only toggle back if they are being targeted by the WHERE statement.
Sounds like a job for trigger. Something like will perhaps do (pseudocode)?
UPDATE for handling reuse of previuos tags:
Do your insert/update:
INSERT INTO ... ON DUPLICATE KEY UPDATE
Then use two triggers, one for inserts and one for updates.
CREATE TRIGGER tr_inuse_insert BEFORE INSERT ON page_tags
FOR EACH ROW BEGIN
UPDATE page_tags SET inuse=0 WHERE page_id = NEW.page_id;
END;
CREATE TRIGGER tr_inuse_update BEFORE UPDATE ON page_tags
FOR EACH ROW BEGIN
UPDATE page_tags SET inuse=0 WHERE page_id = NEW.page_id;
END;
#John P has a decent answer, however his answer requires the use of triggers. That to me seems to be more than needed to solve the problem at hand. The current working solution is:
Create an Unique Index on page_tags.page_id and page_tags.tag_id.
Update all rows where the *page_id = 234234*:
UPDATE page_tags SET inUse = 0 WHERE page_id = 234234
Insert tags:
INSERT INTO page_tags (page_id, tag_id, inUse) VALUES (234234, 49343, 1) ON DUPLICATE KEY UPDATE inUse = 1