Select entire [NEW] Row in Before Insert Trigger MySQL - mysql

I have a trigger containing this:
SET v1 = CONCAT_WS(',',NEW.ID, NEW.Name, NEW.Type, NEW.Value);
Can this be simplified into something like this to include the entire new row?:
SET v1 = CONCAT_WS(',',NEW.*);
(I've tried variations of the above however they causes syntax errors)
Thanks

No, there's no easy way to do this. You have to reference each column.
The only real workaround is to use the table metadata to help you generate the statement you want, and then include that statement in your procedure.
You wouldn't want to do this dynamically in the TRIGGER, even if it were possible.
SELECT CONCAT('NEW.`',GROUP_CONCAT(c.column_name
ORDER BY ORDINAL_POSITION SEPARATOR '`,NEW.`'),'`')
FROM information_schema.columns
WHERE table_schema = DATABASE()
AND table_name = 'mytable'
That should get you a string that looks like:
NEW.`ID`,NEW.`Name`,NEW.`Type`,NEW.`Value`
And you can paste that into your trigger body. (Of course you could extend the CONCAT to generate the whole line.)
The downside is that when new columns are added to the table, those won't get automatically included; that will require a change in the trigger. If a column gets dropped or renamed, your trigger will start throwing exceptions; again requiring a fix to the trigger.
UPDATE
Q:How can I convert this string into a MySQL query?
#query = "CONCAT_WS(',','CCC','64',NEW.Record,NEW.ID,NEW.Name,NEW.User_ID,NEW.State_Record,NEW.Hash);"
I wouldn't convert that to a query. I would just use that as static line of code (with no double quotes) in the body of your trigger, just like the original statement you had in your TRIGGER.
SET v1 = CONCAT_WS(',','CCC','64',NEW.Record,NEW.ID,NEW.Name,NEW.User_ID,NEW.State_Record,NEW.Hash);
(It wasn't clear what you intended to do with that string.)
If you are trying to create a SELECT statement, you could try removing that semicolon from the end of the string, and prepending a SELECT keyword on it. But I don't think the NEW. references to the column values of the current row will be recognized in that context. That might happen, but you'd need to test.
If I needed to do something like that, I would do it using user variables,
SET #new_id = NEW.ID;
SET #new_name = NEW.Name;
SELECT #new_id, #new_name
It's not at all clear to me what you are going to do with the result set returned by a query like. If you are attempting to audit changes to the table, the normative pattern is to run an INSERT of the column values into an changelog table,
INSERT INTO mytable_changelog (ID, Name) VALUES (NEW.ID, NEW.Name);
It really depends on what you are trying to accomplish.

Related

Migrating from MySQL to SQL Server, How to Implement Triggers

So I have this trigger that I wrote for a MySQL environment, and which I now need to transfer to a SQL Server environment.
Being unfamiliar with Transact SQL, I have a little trouble translating from one to the other or creating an equivalent. Here is the simplified query:
CREATE TRIGGER <myTrigger> BEFORE INSERT ON <myTable>
IF NEW.<myColumnContainingBoolean> = TRUE THEN
SET NEW.<myColumnReferenceCode> = CONCAT(YEAR(NOW()),MONTH(NOW()),DAY(NOW()), 'indice');
ENDIF;
The goal is to add a reference number (today's date writted yyyymmdd + 'indice') according to the value of a boolean contained in the query, to summarize, if, at the time of the INSERT, the value of the boolean is on TRUE then we insert the code on this same line, otherwise we don't write a reference. Here is a maybe more explicit example :
Example
I have sincerely tried a lot of things, what seems to come closest to my request is this one (which, of course, does not work):
CREATE TRIGGER <myTrigger>
ON <myTable>
AFTER INSERT
AS
BEGIN
IF <myColumnContainingBoolean>
SET <myColumnReferenceCode> = CONCAT(YEAR(GETDATE()),MONTH(GETDATE()),DAY(GETDATE()), 'indice');
FROM inserted
END
GO
Ok, I guess we have to use a trigger (immense sigh).
Here's how you would do it in SQL Server:
CREATE TRIGGER <your schema>.<your table>_Insert ON <your schema>.<your table>
INSTEAD OF INSERT
AS
BEGIN
INSERT INTO <your schema>.<your table> (<your other columns>,<myColumnReferenceCode>)
SELECT
<your other columns>
,CASE
WHEN <myColumnContainingBoolean> = 1 THEN FORMAT(GETDATE(),'yyyyMMdd') + 'indice'
ELSE <myColumnReferenceCode>
END
FROM
inserted
END
GO
If you're using an auto-incremented (IDENTITY) column, make sure to leave it off your insert list inside the trigger.
Other observations: You could probably just make <myColumnReferenceCode> a date and store GETDATE() and get the same functionality, but I don't know all of your circumstances.

SQL where syntax error with trigger

create trigger cal_retweet before insert on T
for each row begin
set NEW.retweet_change = NEW.retweet_count - retweet_count where id_str = NEW.id_str
end
SQL said there is syntax error near "where id_str = NEW.id_str"
My table looks like this. Where id_str is a unique identifier for a specific tweet. Since I am inserting 50 tweets from a single user every minute, there would be many same id_str. What I want to look at is the change of retweet_count every minute. tweeted_at is when the user tweeted, created_at is when this data is inserted into my database. I want to generate retweet_change for each new data inserted into the database compared to the same old tweet (into the column retweet_change). How should I write the trigger?
After reading some of your comments I changed my code to :
create trigger cal_retweet before update on T
for each row
begin
set NEW.retweet_change = NEW.retweet_count - OLD.retweet_count;
end;
There is still syntax error
There are several issues with this trigger.
You have some syntax errors. You need proper semicolons to delimit your statements.
You have a WHERE statement that is out of place (and actually not needed). You are acting on only a single row at a time, you don't have to match on the id_str.
In order to factor in a calculation using an existing value from the row, you need access to the OLD keyword. For that, you need a trigger that happens on UPDATE, not INSERT. On INSERT, the retweet_change is simply the same as retweet_count; you could alter your INSERT statement to fix that problem.
You may need to explicitly add a statement delimiter as per the comments below.
So all together, I think this trigger should look like:
DELIMITER //
CREATE TRIGGER cal_retweet BEFORE UPDATE ON T
FOR EACH ROW
BEGIN
SET NEW.retweet_change = NEW.retweet_count - OLD.retweet_count;
END;//
DELIMITER ;

Select result set into a variable, and then use that variable in an update later in the same stored procedure

Okay, so this is either not possible, or I suck at Googling. (the latter being the more likely here)
My question is this: Using stored procedures, is it possible to select a result set of ids into a variable, and then use those ids as the condition for an IN in an update that runs in the same SP?
I'm going for something like this (I recognize I'm ignoring delimiter conflicts here)
CREATE PROCEDURE test()
BEGIN
DECLARE var1 INT DEFAULT 0;
SELECT id INTO var1 FROM table WHERE this='this';
some other stuff
UPDATE table2 SET blah='blah' WHERE fk_id IN (var1);
END;
"some other stuff" basically manipulates data in such a way that I wont be able to get the appropriate list of ids by using an inner query inside the IN, so I basically need to "save" the appropriate list of ids into a variable and then use that variable later. Can this be done?
Please don't suggest other ways of doing this/restructuring my data/logic so I don't have to worry about this. All I need to know is if/how this can be done. I have backup options if it can't be done, this would just be the most preferable way.
Thanks in advance.
Something like this:
CREATE PROCEDURE test()
BEGIN
CREATE TEMPORARY TABLE IF NOT EXISTS tmp AS (SELECT id FROM table WHERE this='this');
some other stuff
UPDATE table2 SET blah='blah' WHERE fk_id IN (SELECT id FROM tmp);
END;

Is it possible to pass the NEW and the OLD tables from a trigger into a procedure in MySQL?

Is it possible to pass the NEW and the OLD tables from a trigger into a procedure in MySQL?
I suspect no, since there is no such a datatype as table that a procedure accepts.
Any workarounds possible?
Ideally it would look like this:
CREATE TRIGGER Product_log AFTER UPDATE ON Product
FOR EACH ROW BEGIN
call logChanges(OLD, NEW);
END;
You can explicitly pass each field:
CALL logChanges(OLD.colA, OLD.colB, NEW.colA, NEW.colB);
Or if logChanges must be sufficiently generic that it can handle such calls from different tables, one could concatenate the field values into a single string using a suitable delimiter (e.g. the unit separator):
CALL logChanges(CONCAT_WS(CHAR(31), OLD.colA, old.colB),
CONCAT_WS(CHAR(31), NEW.colA, NEW.colB));
Or if data types must be preserved, one could insert the records into a temporary from which logChanges reads.
it's not possible because there is no NEW or OLD table. The entire trigger is related to the table - the "new" and "old" refer to the rows and the values they contained before and after the event that was triggered. In other words, your example would be:
call logChanges(OLD.customername, NEW.customername)
You could also save all the OLD data in a history table (which I expect logchanges does anyways), basically being a clone of the production table something like this:
BEGIN
IF OLD.customer_name != NEW.customer_name
THEN
INSERT INTO myTable_chagne_history
(
customer_id ,
customer_name ,
another_field ,
edit_time
)
VALUES
(
OLD.customer_id,
OLD.customer_name,
OLD.another_field ,
NEW.time_edit_was_made
);
END IF;
END;

MySql stored procedure SET command -- when it errors

After Googling for awhile I didn't see an answer. Anyway I have a situation in a stored procedure where I do a set select like:
SET someVariable = (SELECT ...)
Anyway, due to some redundant records existing somewhere else in the system, this SELECT query used in the SET returns more than one row. I'm guessing this will cause breakage or badness? True, false?
Thanks.
True. When assigning to a variable, the query must return a single row, containing a single column. You can also do it with this syntax:
SELECT someColumn INTO myVariable ... LIMIT 1;