SQL IF - ELSE Statement - mysql

I have a table like this :
+-------+------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+------------+------+-----+---------+-------+
| ID | bigint(20) | NO | PRI | NULL | |
| view | bigint(20) | NO | | NULL | |
+-------+------------+------+-----+---------+-------+
Is it possible to do this with SQL's IF-ELSE statement ?
Check if there is ID=1 row in table
If there is , increase view column by 1 .
If there isn't, insert new row to table with ID=1
It should be something like this :
IF((SELECT COUNT(ID) FROM wp_viewcount WHERE ID=1) == 0)
BEGIN
INSERT INTO wp_viewcount VALUES (1,1)
END
ELSE
BEGIN
UPDATE wp_viewcount SET view=view+1 WHERE ID=1
END

The following SQL statement will result in the IF - ELSE logic you want, by using the on duplicate key syntax.
insert into wp_viewcount values(1,1) on duplicate key update view=view+1;

You can only use MySQL's if in a stored procedure. For example:
DELIMITER //
CREATE PROCEDURE `test_procedure` (IN wp_id INT)
BEGIN
IF( (SELECT COUNT(*) FROM wp_viewcount WHERE id = wp_id)<1) THEN
INSERT INTO wp_viewcount(id,view) VALUES (wp_id,1);
ELSE
UPDATE wp_viewcount SET view=view+1 WHERE ID=wp_id;
END IF;
END //
Given your use case, you might be better served by MySQL's INSERT ... ON DUPLICATE KEY UPDATE.

Why not use
if not exists (select * from wp_viewcount where id = 1)
begin
--insert logic
end
else
begin
--update logic
end

Related

MySQL : Cascade update on same table

I have a MySQL table in which each records references its parent_id :
| id | Summary | parent_id | hidden |
-------------------------------------
| 1 | First | NULL | 0 |
| 2 | Hello | 1 | 0 |
| 3 | john | 1 | 0 |
| 4 | Second | NULL | 0 |
| 5 | World | 2 | 0 |
| 6 | Doe | 4 | 0 |
I would like to cascade update so that if line 1 becomes hidden, its child (line 2) and the childs of its childs (line 5) becomes also hidden.
Is it possible with MySQL.
I already have a DELETE ON CASCADE constraint which works fine.
CONSTRAINT FK_ID_With_CascadeDelete FOREIGN KEY (parent_id) REFERENCES MyTable (id) ON DELETE CASCADE
You can use Stored Procedure as follows.
DELIMITER $$
DROP PROCEDURE IF EXISTS `update_node`$$
CREATE PROCEDURE `update_node`(IN p_id INT)
proc: BEGIN
DECLARE e_no_id CONDITION FOR SQLSTATE '45000';
IF ( p_id IS NULL ) THEN
SIGNAL e_no_id SET MESSAGE_TEXT = 'The id cannot be empty.';
LEAVE proc;
END IF;
DROP TEMPORARY TABLE IF EXISTS del_temp_table;
CREATE TEMPORARY TABLE IF NOT EXISTS del_temp_table(
id INTEGER UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
delids INTEGER UNSIGNED NOT NULL,
node INTEGER DEFAULT 0
);
SET #rept := #loopcount := 1;
INSERT INTO del_temp_table (delids, node) SELECT p_id, #rept;
myloop: WHILE (1 = 1)
DO
SELECT COUNT(id) AS cnt FROM test WHERE `parent_id` IN ( SELECT DISTINCT(delids) FROM del_temp_table WHERE node = #rept ) INTO #loopcount;
IF (#loopcount = 0) THEN
LEAVE myloop;
ELSE
SET #rept := #rept + 1;
SELECT GROUP_CONCAT(d1.delids) INTO #wherein FROM ( SELECT DISTINCT(delids) FROM del_temp_table WHERE node = (#rept - 1) ) AS d1;
INSERT INTO del_temp_table (delids, node) SELECT id, #rept FROM test WHERE FIND_IN_SET( parent_id, #wherein ) > 0;
END IF ;
END WHILE myloop;
UPDATE test SET hidden = 1 WHERE id IN ( SELECT delids FROM del_temp_table );
END$$
DELIMITER ;
Here i used Temporary table to store recursive nodes and iterated each time by using that table to fetch next child and inserted it into same table.
I believe this could help you. Replace table name test with your table name
I think you simply can't do that. Even with an UPDATE TRIGGER you cannot update other rows on the same table.
Maybe this is not the answer you were expecting but probably your best choice is to use a PROCEDURE like this:
DELIMITER //
CREATE PROCEDURE UpdateHiden(IN pid INT, IN phidden INT)
BEGIN
UPDATE mytable
SET HIDDEN = phidden
WHERE ID = pid OR parent_id = pid;
END; //
DELIMITER ;
See sample on DB Fiddle here

mysql use IF NOT NULL as part of transaction

I am using a MySQL transaction. If the initial where myHistoryNum result (match name+rowActive) is not NULL, then continue the transaction.
I have tried various combinations, however cannot get the insert statement to trigger when myHistoryNum is set.
history | uuid | name | rowActive
24 | abcd | Art | 1 <<< is match, trigger an insert
999 | zxcv | Art | 0 <<< no match, do not trigger insert
start transaction;
select #myHistNum := max(history), #olduuid := uuid
from mytable WHERE (name=art AND rowActive=1);
# START IS NOT NULL test
CASE WHEN #myHistNum IS NOT NULL THEN
set #histNum = #histNum + 1;
INSERT INTO mytable
.....
END; <<<ALT, tried END AS;
commit;
also tried
IF #myHistNum IS NOT NULL
THEN
.....
END IF;
commit;
If I'm not mistaken, control flow statements (like IF, WHEN) can only be used within stored programs in MySQL

Trigger to invalidate cache after update

I'm trying to write a trigger to invalidate the word index for my story database. However I can't seem to figure out how to stop the trigger from firing again during the indexing operation. I know I need to place an if statement to stop the update, but I can't seem to figure out what it should look like.
CREATE TRIGGER trMarkStoryForReindex BEFORE UPDATE ON Chapters
FOR EACH ROW BEGIN
-- any update to any chapter invalidates the index for the whole story
-- we could just periodically flush the story index, but this way is
-- better.
SET New.isIndexed = FALSE;
-- delete the index for that story
-- It will get rebuilt in at most 15 minutes
DELETE FROM freq WHERE storyid = NEW.StoryId;
END;
I basically want the trigger to fire only when isIndexed is has not been set in the update statement causing the trigger.
My data model looks like so:
Chapters
id
isIndexed
StoryId
Freq
word
storyid
Here's my proposal for solution. I've tested this on SQL fiddle, and it seems to work:
-- Database setup
create table chapters (
id int unsigned not null auto_increment primary key,
isIndexed boolean default false,
storyId int not null,
index idx_storyId(storyId)
);
create table freq (
word varchar(50),
storyId int not null,
index idx_storyId(storyId)
);
delimiter //
create trigger bi_chapters before update on chapters
for each row
begin
if new.isIndexed = false then
delete from freq where storyId = new.storyId;
end if;
end //
delimiter ;
insert into freq(word, storyId)
values ('one', 1), ('two', 1), ('three', 2);
insert into chapters(isIndexed, storyId)
values (true, 1), (true, 2);
When you select the values from freq (before updating chapters) you get this:
select * from chapters;
| id | isIndexed | storyId |
|----|-----------|---------|
| 1 | false | 1 |
| 2 | true | 2 |
select * from freq;
| word | storyId |
|-------|---------|
| one | 1 |
| two | 1 |
| three | 2 |
Now, do an update to chapters and select from freq again:
update chapters
set isIndexed = false
where storyId = 1;
select * from freq;
| word | storyId |
|-------|---------|
| three | 2 |
The only modification I did is that if block that checks if the new row is updated to false. If I've understood your question correctly, this would do what you need.
SQL fiddle example

MySQL Trigger after insert and add timestamp when data changed

This is my perl sample code and what i needed is add a timestamp into columns when data changed(updated).
use strict;
use DBI;
use POSIX qw(strftime);
my $datestring = strftime "%Y-%m-%d %H:%M:%S",localtime;
my $dbh = DBI->("dbi:mysql:dbname=perldb","root","111111",{RaiseError=>1})
or die DBI::errstr();
my $mac1='mac1';
my $ip1='ip1';
my $mac2='mac2';
my $ip2='ip2';
$dbh->do("DROP TABLE IF EXISTS Test1");
$dbh->do("CREATE TABLE Test1(Id INT PRIMARY KEY AUTO_INCREMENT,IP TEXT,MAC TEXT,TIME DATETIME,PrevTime DATETIME) ENGINE=InnoDB");
$dbh->do("CREATE Trigger trigger1 AFTER INSERT ON Test1
FOR EACH ROW
BEGIN
IF OLD.MAC!=NEW.MAC
THEN SET NEW.TIME='$datestring';
END IF;
END;")
$dbh->do("INSERT INTO Test1(IP,MAC) VALUES('$ip1','$mac1') ON DUPLICATE KEY UPDATE IP=VALUE(IP)");
$dbh->do("INSERT INTO Test1(IP,MAC) VALUES('$ip2','$mac2') ON DUPLICATE KEY UPDATE IP=VALUE(IP)");
$dbh->disconnect();
The goal I want to reach is run this code once then modify $mac1 to 'mac3'.
So that it will trigger SET timestamp because the data was changed.
Also SET old one timestamp to PrevTIME, but OLD for INSERT.
Result I need maybe like this:
+----+------+------+---------------------+----------+
| Id | IP | MAC | TIME | PrevTime |
+----+------+------+---------------------+----------+
| 1 | ip1 | mac1 | 2015-03-03 12:34:56 | NULL |
| 2 | ip2 | mac2 | 2015-03-03 12:34:56 | NULL |
+----+------+------+---------------------+----------+
Then
+----+------+------+---------------------+---------------------+
| Id | IP | MAC | TIME | PrevTime |
+----+------+------+---------------------+---------------------+
| 1 | ip1 | mac3 | 2015-03-03 12:40:00 | 2015-03-03 12:34:56 |
| 2 | ip2 | mac2 | 2015-03-03 12:34:56 | NULL |
+----+------+------+---------------------+---------------------+
Anyidea? Please help me figure out,THANK YOU.
-------------------------------SOLUTION---------------------------------------
use strict;
use DBI;
use POSIX qw(strftime);
my $datestring = strftime "%Y-%m-%d %H:%M:%S",localtime;
my $dbh = DBI->("dbi:mysql:dbname=perldb","root","111111",{RaiseError=>1})
or die DBI::errstr();
my $mac1='mac1';
my $ip1='ip1';
my $mac2='mac2';
my $ip2='ip2';
$dbh->do("CREATE TABLE IF NOT EXISTS Test1(Id INT PRIMARY KEY AUTO_INCREMENT,IP TEXT,MAC TEXT,TIME DATETIME,PrevTime DATETIME,UNIQUE(IP)) ENGINE=InnoDB");
$dbh->do("CREATE Trigger trigger1 BEFORE INSERT ON Test1
FOR EACH ROW
BEGIN
SET NEW.TIME='$datestring';
END;")
$dbh->do("CREATE Trigger trigger2 BEFORE UPDATE ON Test1
FOR EACH ROW
BEGIN
IF OLD.MAC <> NEW.MAC
THEN
SET NEW.PrevTIME=OLD.TIME;
SET NEW.TIME='$datestring';
END IF;
END;")
$dbh->do("INSERT INTO Test1(IP,MAC) VALUES('$ip1','$mac1') ON DUPLICATE KEY UPDATE IP=VALUE(MAC)");
$dbh->do("INSERT INTO Test1(IP,MAC) VALUES('$ip2','$mac2') ON DUPLICATE KEY UPDATE IP=VALUE(MAC)");
$dbh->disconnect();
You actually need 2 triggers one before insert and one before update. The before insert will set the TIME to now(), while the before update will check the old and new values of mac and set the PrevTime to old TIME and TIME to now()
Here are the triggers, you can have them in your code
delimiter //
create trigger trigger1 before insert on Test1
for each row
begin
set new.TIME = now();
end ; //
delimiter //
create trigger trigger2 before update on Test1
for each row
begin
if old.MAC <> new.MAC then
set new.PrevTime = old.TIME ;
set new.TIME = now();
end if;
end ;//
Triggers are event specific.
You are using an INSERT and hence AFTER or BEFORE Trigger works defined for INSERT events.
You actually need a BEFORE UPDATE trigger.
Add a similar trigger for BEFORE TRIGGER
Example:
CREATE TRIGGER bu_on_Test BEFORE UPDATE ON Test1
FOR EACH ROW
BEGIN
IF OLD.MAC <> NEW.MAC THEN
SET NEW.TIME='$datestring';
END IF;
END;
But note that Triggers won't take dynamic input parameters like '$datestring'.
If such a value is set in your scripting language, that is for lifetime of trigger definition and is not going to change during its execution.

MySQL: splits row into multiple rows with join table?

I have an existing table with the following structure
+-------------+-------+-------+-------+
| employee_id | val_1 | val_2 | val_3 | ...
+-------------+-------+-------+-------+
| 123 | A | B | C |
I want to change this single table into 2 tables - one which contains the values in seperate rows, and another with becomes a join table for this. For example, the above would be turned into this:
+-------------+--------+ +----+-------+
| employee_id | val_id | | id | value |
+-------------+--------+ +----+-------+
| 123 | 1 | | 1 | A |
+-------------+--------+ +----+-------+
| 123 | 2 | | 2 | B |
+-------------+--------+ +----+-------+
| 123 | 3 | | 3 | C |
+-------------+--------+ +----+-------+
What's the best SQL to use to convert the existing table into these 2 new tables? I can create the values table easy enough, but I'm not sure how to create the join table at the same times.
Something like this (psuedo-code only, sorry):
For each row in (SELECT employee_id, val_1, val_2, val_3 FROM existing_table)
{
for each val in (row.Values)
{
INSERT INTO new_values (val)
val_id = SELECT LAST_INSERT_ID();
INSERT INTO new_employees (employee_id, val_id);
}
}
There's probably a set-based way of doing this to avoid the loops... but sorry, I don't know what it is as like you, I'm not sure how to get the identity of the values table back into the parent employee table.
And also, while cursors are generally frowned on, this sort of one-off operation is exactly what they're designed for (ie I wouldn't recommend cursors for regular transaction or report processing, but for a re-structure of data.... why not?).
for the first result
`INSERT INTO new_val
SELECT emp_id, REPLACE(UPPER(column_name), 'VAL_', '') FROM
information_schema.COLUMNS ,
employee
WHERE TABLE_NAME = 'employee' AND TABLE_SCHEMA = 'myschema' AND column_name LIKE 'VAL_%'`;
using first result, populate the query and use it to insert into new table;
May be minor fine tuning required. Not tested
SELECT CONCAT('select ', new_val.number, ', VAL_', new_val.number, '
FROM employee, new_val
WHERE new_val.emp_id = employee.emp_id and new_val.number = ', val.number,
' union all' ) FROM
val ;
So here's what I ended up writing to do this. As the procedure name suggests, I was expecting there to be a more straightforward way of doing this!
CREATE PROCEDURE iWasHopingItWouldBeSimpler()
BEGIN
DECLARE loop_done BOOLEAN DEFAULT 0;
DECLARE emp_id BIGINT(20);
DECLARE val1 DECIMAL(19,2);
DECLARE val2 DECIMAL(19,2);
DECLARE val3 DECIMAL(19,2);
DECLARE emp CURSOR
FOR
SELECT employee_id, val1, val2, val3 FROM existing;
-- Declare continue handler
DECLARE CONTINUE HANDLER FOR SQLSTATE '02000' SET loop_done=1;
OPEN emp;
-- Loop through all rows
REPEAT
FETCH emp INTO emp_id, val1, val2, val3;
INSERT INTO new_values (value) VALUES(val1);
INSERT INTO new_join (employee_id, values_id) VALUES(emp_id, LAST_INSERT_ID());
INSERT INTO new_values (value) VALUES(val2);
INSERT INTO new_join (employee_id, values_id) VALUES(emp_id, LAST_INSERT_ID());
INSERT INTO new_values (value) VALUES(val3);
INSERT INTO new_join (employee_id, values_id) VALUES(emp_id, LAST_INSERT_ID());
-- End of loop
UNTIL loop_done END REPEAT;
CLOSE emp;
SET loop_done=0;
END;