I want to do something like this:
DECLARE VAR_NAME INT DEFAULT 0;
IF NEW.field_name != NULL THEN
SET VAR_NAME := 8;
END IF;
IF NEW.field_name = NULL THEN
SET VAR_NAME := 1;
END IF;
UPDATE table_name SET column_name = column_name + VAR_NAME WHERE table_id = NEW.given_id;
I have a trigger for when inserting a new value:
I declare a variable, I change its value based on some conditions and then I update a table by adding this value to the current value of a specific column where the condition is met 'table_id = NEW.given_id'
Even I don't get errors the desired result is not happening.
Right, as the comments indicate, you can't check for NULL with normal equality operators. The way that I understand this is that nothing is equal to, less than, or greater than, etc, NULL, as NULL is literally 'no value'.
You must instead use IS NULL or IS NOT NULL to check for NULL.
This link goes into further detail:
http://dev.mysql.com/doc/refman/5.7/en/working-with-null.html
Related
I have a legacy table with about 100 columns (90% nullable). In those 90 columns I want to remove all empty strings and set them to null. I know I can:
update table set column = NULL where column = '';
update table set column2 = NULL where column2 = '';
But that is tedious and error prone. There has to be a way to do this on the whole table?
UPDATE
TableName
SET
column01 = CASE column01 WHEN '' THEN NULL ELSE column01 END,
column02 = CASE column02 WHEN '' THEN NULL ELSE column02 END,
column03 = CASE column03 WHEN '' THEN NULL ELSE column03 END,
...,
column99 = CASE column99 WHEN '' THEN NULL ELSE column99 END
This is still doing it manually, but is slightly less painful than what you have because it doesn't require you to send a query for each and every column. Unless you want to go to the trouble of scripting it, you will have to put up with a certain amount of pain when doing something like this.
Edit: Added the ENDs
One possible script:
for col in $(echo "select column_name from information_schema.columns
where table_name='$TABLE'"|mysql --skip-column-names $DB)
do
echo update $TABLE set $col = NULL where $col = \'\'\;
done|mysql $DB
For newbies, you may still need more work after seeing the above answers. And it's not realistic to type thousands lines.
So here I provide a complete working code to let you avoid syntax errors etc.
DROP PROCEDURE IF EXISTS processallcolumns;
DELIMITER $$
CREATE PROCEDURE processallcolumns ()
BEGIN
DECLARE i,num_rows INT ;
DECLARE col_name char(250);
DECLARE col_names CURSOR FOR
SELECT column_name
FROM INFORMATION_SCHEMA.COLUMNS
WHERE table_name = 'PROCESSINGTABLE'
ORDER BY ordinal_position;
OPEN col_names ;
select FOUND_ROWS() into num_rows;
SET i = 1;
the_loop: LOOP
IF i > num_rows THEN
CLOSE col_names;
LEAVE the_loop;
END IF;
FETCH col_names
INTO col_name;
SET #command_text = CONCAT('UPDATE `PROCESSINGTABLE` SET ', col_name, '= IF(LENGTH(', col_name, ')=0, NULL,', col_name, ') WHERE 1 ;' ) ;
-- UPDATE `PROCESSINGTABLE` SET col_name=IF(LENGTH(col_name)=0,NULL,col_name) WHERE 1;
-- This won't work, because MySQL doesn't take varibles as column name.
PREPARE stmt FROM #command_text ;
EXECUTE stmt ;
SET i = i + 1;
END LOOP the_loop ;
END$$
DELIMITER ;
call processallcolumns ();
DROP PROCEDURE processallcolumns;
Hammerite's answer is good but you can also replace CASE statements with IFs.
UPDATE
TableName
SET
column01 = IF(column01 = '', NULL, column01),
column02 = IF(column02 = '', NULL, column02),
column03 = IF(column03 = '', NULL, column03),
...,
column99 = IF(column99 = '', NULL, column99)
There isn't a standard way - but you can interrogate the system catalog to get the relevant column names for the relevant table and generate the SQL to do it. You can also probably use a CASE expression to handle all the columns in a single pass - a bigger SQL statement.
UPDATE Table
SET Column1 = CASE Column1 = ' ' THEN NULL ELSE Column1 END,
...
Note that once you've generated the big UPDATE statement, all the work is done down in the server. This is much more efficient than selecting data to the client application, changing it there, and writing the result back to the database.
I think you'll need to pull each row into a language like C#, php, etc.
Something like:
rows = get-data()
foreach row in rows
foreach col in row.cols
if col == ''
col = null
end if
next
next
save-data()
You could write a simple function and pass your columns to it:
Usage:
SELECT
fn_nullify_if_empty(PotentiallyEmptyString)
FROM
table_name
;
Implementation:
DELIMITER $$
CREATE FUNCTION fn_nullify_if_empty(in_string VARCHAR(255))
RETURNS VARCHAR(255)
BEGIN
IF in_string = ''
THEN RETURN NULL;
ELSE RETURN in_string;
END IF;
END $$
DELIMITER ;
mysql V 5.6
Hi, I have an issue with a trigger BEFORE UPDATE : on a table, I have a flag defiend as a tinyint, not null, default value 1 . I have a before update trigger that set this flag to 1 whenever the row if updated. But, I also need in one process to explicitly set the flag to 0, so I tried that
create trigger t BEFORE UPDATE on table for each row
BEGIN
IF new.flag is NULL THEN
set new.flag = 1
END IF;
Problem : new.flag is never null. Checking with
IF new.flag is NULL THEN
set new.flag = 1
ELSEIF new.flag = 0 THEN
set new.flag = 3
END IF;
set the column to 3 whenever I update the table without including the flag in the update query. Worse, I cannot check against empty string to put the flag to 1, as a select ('' = 0) return true, if using :
IF new.flag is NULL OR new.flag = '' THEN
set new.flag = 1
END IF;
I can never explicitly set the flag to 0 . Shouldn't a column not part of the update be null in NEW ? What can I do, is it a mysql config to change ?
Thanks
NEW contains the values for all columns that the row will have after the update, not just the values you explicitly used in the update query.
To tell the trigger that you want to explicitly reset the value, you could use an otherwise unused (but valid) value to mark that information, and then act accordingly in the trigger, e.g., in your trigger, use (assuming signed tinyint)
IF new.flag = -1 THEN
set new.flag = 0;
ELSE
set new.flag = 1;
END IF;
Then in your update query,
update table set flag = -1;
will set the value to 0, any other value, or not using that column in your query at all, will set it to 1.
There is an alternative and more common approach without triggers, that works similar to your flag. You can use a column
last_update datetime default null on update current_timestamp;
null in this column would have the same meaning as flag = 0, any other value would have the same meaning as flag = 1. They are set automatically. If you want to reset your "flag", you can simply use
update table set last_update = null;
Your trigger approach will of course work too perfectly fine. Just make sure to document that behaviour somewhere.
The problem is that the FETCH INTO (in the loop) does not put the value into the variable. I've looked at MYSQL | SP | CURSOR - Fetch cursor into variable return null but the table is already populated.
The transaction table looks like this:
CREATE TABLE `transactionentry` (
`transactionid` bigint(20) NOT NULL AUTO_INCREMENT,
...
PRIMARY KEY (`transactionid`),
...
) ENGINE=InnoDB AUTO_INCREMENT=651 DEFAULT CHARSET=utf8;
The stored procedure:
PROCEDURE `doTxnHouseKeeping`()
BEGIN
-- Loop invariant
DECLARE noEntries INTEGER DEFAULT FALSE;
-- Error codes
DECLARE code CHAR(5) DEFAULT '00000';
DECLARE msg TEXT;
-- Txn vars
DECLARE transactionId BIGINT(20);
DECLARE lastTransactionId BIGINT(20) DEFAULT 0;
-- testing
DECLARE counter INT(11) DEFAULT 0;
DEClARE txnEntryCur CURSOR FOR
SELECT
`transactionid`
FROM
`transactionentry`
LIMIT 1;
DECLARE CONTINUE HANDLER FOR
NOT FOUND SET noEntries = TRUE;
DECLARE EXIT HANDLER FOR
SQLEXCEPTION
BEGIN
GET DIAGNOSTICS CONDITION 1
code = RETURNED_SQLSTATE, msg = MESSAGE_TEXT;
SELECT CONCAT('Error fetching transaction entries code: ', code, ' message: ', msg);
END;
OPEN txnEntryCur;
mainLoop: LOOP
FETCH
txnEntryCur
INTO
transactionId;
IF noEntries THEN
LEAVE mainLoop;
END IF;
IF transactionId IS NOT NULL THEN
INSERT INTO debugTable (`bigintval`) VALUES (transactionId);
ELSE
INSERT INTO debugTable (`strval`) VALUES ('transactionId is NULL');
END IF;
SET counter = counter + 1;
END LOOP mainLoop;
CLOSE txnEntryCur;
SELECT CONCAT("Count: ", counter);
END
Running the stored procedure returns this result:
+--------------------------+
|CONCAT("Count: ", counter)|
+--------------------------+
| Count: 1|
+--------------------------+
The result in the debug table is:
+------------+---------+-----------------------+
|iddebugTable|bigintval| strval|
+------------+---------+-----------------------+
| 1| NULL|"transactionId is NULL"|
+------------+---------+-----------------------+
Which means that the value was not copied in
When running the SQL (as it is in the stored procedure), it returns:
+-------------+
|transactionid|
+-------------+
| 591|
+-------------+
I found the problem and it is weird. It doesn't cause any error and / or exceptions, just doesn't put any values into the variables. The solution is to change the cursor declare statement from:
DECLARE txnEntryCur CURSOR FOR
SELECT
`transactionid`
FROM
`transactionentry`
LIMIT 1;
To:
DECLARE txnEntryCur CURSOR FOR
SELECT
`transactionentry`.`transactionid`
FROM
`transactionentry`
LIMIT 1;
Not even the documentation indicated that it might have been a problem (https://dev.mysql.com/doc/refman/5.7/en/declare-cursor.html)
I only fully qualify the SELECT (and WHERE) part of the SQL statement if I'm selecting from more than one table and thus never picked this up on more complex queries.
I hope this will save someone some time in the future.
Your problem is here:
DECLARE transactionId BIGINT(20);
You declare a variable named transactionId so when you do this:
DEClARE txnEntryCur CURSOR FOR
SELECT
`transactionid`
FROM
`transactionentry`
LIMIT 1;
Your cursor's select is picking up the variable you declared which is why fully qualifying the field works. However, if you don't want to fully qualify the field in your select you can rename your variable.
Try giving the variable transactionId a default value
...
DECLARE transactionId BIGINT(20) DEFAULT 0
...
and also replace
DECLARE noEntries INTEGER DEFAULT FALSE;
with
DECLARE noEntries BOOLEAN DEFAULT FALSE;
since you want to use it as a BOOLEAN value and set it to TRUE later in the procedure.
I have some of tables with identical field values which must not be repeated. I decided to make the generation of value when making INSERT. Trigger getting maximum value and add 1. But after SELECT MAX(account_number) AS last_account_number INTO last_account_number FROM companies; last_account_number always null, but the value is there and the request outside trigger itself runs smoothly.
CREATE TRIGGER generate_account_number_for_companies
before insert
ON companies FOR EACH ROW
BEGIN
DECLARE account_number INT;
DECLARE last_account_number INT;
SELECT MAX(account_number) AS last_account_number INTO last_account_number FROM companies;
IF last_account_number is null THEN
SET New.account_number = 10000000;
ELSE
SET New.account_number = last_account_number + 1;
END IF;
END
First, you do not need the AS last_account_number part in your SELECT ... INTO statement. Second, do you have data in your companies table currently, or is it empty? If it is not empty, try modifying the statement to exclude NULL values:
SELECT MAX(account_number) INTO last_account_number FROM companies WHERE account_number IS NOT NULL;
You could also add a coalesce in there to set it to 0 in the event of a NULL return.
SELECT COALESCE(MAX(account_number), 0) INTO last_account_number FROM companies WHERE account_number IS NOT NULL;
You also are declaring a variable with the same name as your column that you are trying to select, remove the line: DECLARE account_number INT; from your trigger.
I have recently been able to produce a procedure where if a variable is not set I can set it to null. Now I am now looking to have multiple variables, but if a value has not been set to that variable, for it then to return all rows.
BEGIN
DECLARE ps_Project_Leader VARCHAR(15);
DECLARE ps_RD_Plan VARCHAR (15);
DECLARE ps_Approval_Status VARCHAR (15);
DECLARE ps_Design_Plan VARCHAR (15);
SET ps_Project_Leader = ifnull(Project_Leader,null);
SET ps_RD_Plan = ifnull(RD_Plan,null);
SET ps_Approval_Status = ifnull(Approval_Status,null);
SET ps_Design_Plan = ifnull(Design_Plan,null);
SELECT pp.pid,
pp.description,
pp.approval_status,
pp.design_plan,
pp.rd_plan,
pp.estimated_completion,
pp.project_leader,
pp.actual_completion
FROM project_register pp
WHERE pp.project_leader =Project_Leader
OR Project_Leader is null
and pp.rd_plan =RD_Plan
OR RD_Plan is null
and pp.approval_status = Approval_Status
OR Approval_Status is null
and pp.design_plan = Design_Plan
OR Design_Plan is null
and
PP.actual_completion is null;
end
For instance if i have set 2 of the variables and not the other 2, I do not want it to search on the variables that have not been set.
Many Thanks in advance, if i have not made complete sense (i am new to this so i appologies) I will be happy to clear things up.
You need to parenthesize your WHERE expression correctly:
WHERE (pp.project_leader = ps_Project_Leader
OR ps_Project_Leader is null)
and (pp.rd_plan = ps_RD_Plan
OR ps_RD_Plan is null)
and (pp.approval_status = ps_Approval_Status
OR ps_Approval_Status is null)
and (pp.design_plan = ps_Design_Plan
OR ps_Design_Plan is null)
and PP.actual_completion is null;
because AND has higher precedence than OR.
You aren't referencing the local variables, only the procedure arguments. (It doesn't look like you actually need local variables.)
I prefer to use parens around the AND and OR predicates, even if they aren't required. I never have to lookup if AND or OR takes precedence when I use parens, because it doesn't matter, because I'm always specifying the precedence.
I'd help the reader out, and format my SQL like this:
WHERE ( pp.project_leader = Project_Leader OR Project_Leader IS NULL )
AND ( pp.rd_plan = RD_Plan OR RD_Plan IS NULL )
AND ( pp.approval_status = Approval_Status OR Approval_Status IS NULL )
AND ( pp.design_plan = Design_Plan OR Design_Plan IS NULL )
That way, each line is a "check" of a single column, which is either enabled (with a non-NULL value) or disabled with NULL value.
Really just personal preference, I just find it easier to read that way, even if the line is a little bit longer, I'd rather have the check all one one line.
Again, the local variables aren't needed.
But, you could just set local variables equal to the parameter values, and then reference the local variables in your SQL statement. That really helps out when a variable has the same name as a column, because if the are named the same, MySQL is going to assume it's a reference to column name rather than a variable name. Using a local variable gives you a chance to rename it so it won't be confused with a column name.
UPDATE
I just noticed that the parameter variables names ARE the same as the column names, and that's going to be a problem.
You want your variable names to be DIFFERENT than the column names. You want to make sure that the datatypes of the variables match the columns... later, when you change a column from VARCHAR(15) to VARCHAR(30), you'll need to revisit the procedure and change the definitions of the procedure arguments as well as the local variables.
BEGIN
-- local variable names are DISTINCT from any column name
-- in any table referenced by a query these are used in
DECLARE ps_Project_Leader VARCHAR(15);
DECLARE ps_RD_Plan VARCHAR(15);
...
-- copy parameter values to local variables
SET ps_Project_Leader = Project_Leader ;
SET ps_RD_Plan = RD_Plan ;
...
-- query references local variable names
...
WHERE ( pp.project_leader = ps_Project_Leader OR ps_Project_Leader IS NULL )
AND ( pp.rd_plan = ps_RD_Plan OR ps_RD_Plan IS NULL )
...