Dynamic value selection on INSERT statement - mysql

So my problem is the following. I've got a timeStatus column that will have one of two values on an INSERT statement, 'pending' or 'never', depending on whether the column fromDate is NULL or not.
I've made this prepared statement that doesn't work but represents what I intend. On the other hand I'm not sure if a constraint would be in order here, rather then having it specified on the statement. This way I could specify the status value for an insert or update and the table would know what to do. However I need some guidance as to what method to go with and where to go to learn it.
Here's the statement:
INSERT INTO Bservices (
servStatus, timeStatus,
fromDetails, fromDate, fromTime)
VALUES(
'pending', IF(ISNULL(`fromDate`)) 'pending' ELSE 'never',
'a', '', '')
The intended behavior is the following:
ON INSERT
if(fromDate == '') {
timeStatus = 'pending'
} else {
timeStatus = 'never'
}
ON UPDATE
if(timeStatus == 'pending' && fromDate != '') {
timeStatus = 'updated'
}

This doesn't work when you do it with expressions in the VALUES clause of an INSERT statement, because the expressions are evaluated before the row has been created. Therefore all columns are naturally NULL.
To do what you want, you need to write triggers BEFORE INSERT and BEFORE UPDATE. Something like the following, though I have not tested this so I'll leave debugging up to you:
CREATE TRIGGER insBservices
BEFORE INSERT ON Bservices
FOR EACH ROW
SET NEW.timeStatus = IF(NEW.fromDate IS NULL, 'pending', 'never');
CREATE TRIGGER updBservices
BEFORE UPDATE ON Bservices
FOR EACH ROW
SET NEW.timeStatus = IF(NEW.fromDate IS NOT NULL AND OLD.timeStatus = 'pending',
'updated', NEW.timeStatus);
Re your comment:
If you want to learn more about triggers, the MySQL manual is actually pretty weak in this area. They show you the syntax for reference, but not many examples. There are a lot of tricky parts.
For example, understanding when to use DELIMITER when you define triggers, to account for the ambiguity between semicolons inside the body of a trigger, versus the terminator of the CREATE TRIGGER statement itself. This applies to CREATE PROCEDURE and CREATE FUNCTION as well.
I wrote an example and an explanation in my answer to Create function through MySQLdb.
There are tutorials on triggers, for example:
http://code.tutsplus.com/articles/introduction-to-mysql-triggers--net-12226
http://www.mysqltutorial.org/mysql-triggers.aspx

Related

Get name of updated column in history table with update trigger (Mysql) [duplicate]

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;

node-mysql use default if null on insert

I'm fairly new to mysql, and I'm trying to get up and running with it in node using node-mysql. I have created a simple table like so:
CREATE TABLE myTable (
id SERIAL,
display BOOLEAN NOT NULL DEFAULT 1,
active BOOLEAN NOT NULL DEFAULT 0
) DEFAULT CHARSET utf8 COLLATE utf8_bin;
I am inserting rows like so:
db.query('INSERT INTO myTable SET ?', {
display: myObject.display,
active: myObject.active
}, callback);
As the docs say, node-mysql converts the object i'm passing in to key value pairs. This works great if myObject.display and myObject.active are both defined. If one or both aren't, node-mysql tries to insert NULL into the columns, which is not allowed. I intended for the default value to be used in this situation, but its throwing an error about the NULL value. So my question is:
1) Is there some special syntax to use when creating a table that will use the default when a null value is given, or 2) Is there some elegant way to do this with node-mysql that doesn't involve a bunch of object parsing?
Feel free to expand your answer if you see something else I could improve. My larger goal is to learn the best way to create a robust, safe, and concise mysql insert in node.
Your SQL syntax is incorrect for an INSERT statement.
INSERT
INSERT into TableName(id, display, active)
VALUES (?, ?, ?)
UPDATE
UPDATE TableName SET display = ?
WHERE id = ?
You would then populate the ? placeholders using db.query() and passing in your arguments.

MySql Basic table creation/handing

I'm trying to create a simple table where I insert field and I do some checks in MySql. I've used Microsoft SQL relatively easy. Instead, MySql give evrrytime query errors without even specifying what's going on. Poor MySql software design apart, here's what I'm trying to do:
1 table with 4 fields with an autoincremental autogenerated number to det an ID as primary key
CREATE TABLE `my_db`.`Patients_table` (
`ID_Patient` INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY ,
`Patient_name` VARCHAR( 200 ) NOT NULL ,
`Recovery_Date` DATETIME NOT NULL ,
`Recovery_count` INT NOT NULL
) ENGINE = MYISAM
a simple stored procedure to insert such fields and check if something exist before inserting:
CREATE PROCEDURE nameInsert(IN nome, IN data)
INSERT INTO Patients_table (Patient_name,Recovery_Date) values (nome,data)
IF (EXISTS (SELECT Recovery_count FROM Tabella_nomi) = 0) THEN
INSERT INTO (Patients_table (Recovery_count)
ELSE
SET Recovery_count = select Recovery_count+1 from Patients_table
END
this seems wrong on many levels and MySQL useless syntax checker does not help.
How can I do this? Thanks.
There seems to be a lot wrong with this block of code. (No offense intended!)
First, Procedures need to be wrapped with BEGIN and END:
CREATE PROCEDURE nameInsert(IN nome, IN data)
BEGIN
...[actually do stuff here]
END
Second, since your table is declared with all fields as NOT NULL, you must insert all fields with an INSERT statement (this includes the Recovery_Date column, and excludes the AUTO_INCREMENT column). You can add DEFAULT CURRENT_TIMESTAMP to the date column if you want it to be set automatically.
INSERT INTO Patients_table (Patient_name,Recovery_Date) values (nome,data)
Third, what exactly is your IF predicate doing?
EXISTS (SELECT Recovery_count FROM Tabella_nomi) = 0
If you want to check if a row exists, don't put the = 0 at the end. Also, Tabella_nomi isn't declared anywhere in that procedure. Also, your SELECT statement should have a WHERE clause, since I'm assuming you want to select a specific row (this is going to select a result set of all recovery_counts).
Fourth, the second INSERT statement seems a little messy. It should look more like the first INSERT, and keep the point I made above in mind.
INSERT INTO (Patients_table (Recovery_count)
Fifth, the ELSE statement
SET Recovery_count = select Recovery_count+1 from Patients_table
Has some problems too. SET is meant for setting variables, not values in rows. I'm not 100% sure what your intent is from this statement, but it looks like you meant to increment the Recovery_count column of a certain row if it already exists. In which case, you meant to do something like this:
UPDATE Patients_table SET Recovery_count = Recovery_count+1 WHERE <conditional predicate>
Where the conditional predicate is something like this:
Patients_name = nome
Try these things, and look at the errors it gives you when you try to execute the CREATE STATEMENT. I bet they're more useful then you think!

Mysql restrict value of a field to be one of the defined ones

I am using mysql database.
I have a field user_type in USER table. I would like to restrict the values in this field to be one of ('ADMIN','AGENT','CUSTOMER').
The insert statements should fail if they tried to insert anything else than the above possible values. Also, I need defaulting to 'CUSTOMER' is none is specified in the insert statements.
The possible solution I could think of is use of triggers, but I would like to know How this could be handled more efficiently (possibly in the create table ddl itself?).
Any ideas, How to do this?
This is what the column type "enum" is for. You treat it like a string, and behind the scenes it is stored as an int, and must be one of the values defined in the DDL:
CREATE TABLE users (
id int unsigned NOT NULL auto_increment primary key,
user_type enum('ADMIN', 'AGENT', 'CUSTOMER') NOT NULL default 'CUSTOMER'
)
Then insert like so:
INSERT INTO users (user_type) VALUES ('ADMIN'); // success
INSERT INTO users (user_type) VALUES ('ANONYMOUS'); // failure (or '' if not "strict" mode)
INSERT INTO users (user_type) VALUES (default(user_type)); // uses default
INSERT INTO users () VALUES (); // uses default
INSERT INTO users (user_type) VALUES (NULL); // failure
note
Note that for the query to actually fail, you must use "SQL strict mode". Otherwise, an "empty string" value (which is slightly special in that it has the numeric value of 0) is inserted.
Quoting this docs page:
When this manual refers to “strict mode,” it means a mode where at least one of STRICT_TRANS_TABLES or STRICT_ALL_TABLES is enabled.
I came across this post, and as it dates somewhat back, I was thinking of others coming across it these days too and miss the (in my opinion) simpler approach of simply adding a CHECKconstraint (e.g. this for MySQL, or this for MariaDB).
In my opinion, using a CHECK constraint is much easier than using things like ENUM and / or SET as you don't need to worry about the relations to integer indexes etc. when relying on them. They for example can become weird when you try to preset allowed integer values for a column.
Example, where you want to have a column which has values ranging from 1 to 5:
CREATE TABLE myTable (
myCol INT NOT NULL
CONSTRAINT CHECK (0 < `myCol` < 5)
);

mysql Trigger for logging, find changed columns

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;