How to write conditional query in MySQL? - mysql

I came from MSSQL and I'm trying to learn MySQL.
Documentation seem to not help me.
I don't want my query to start always with SELECT, I want my query to SELECT, INSERT or UPDATE depending on the condition. I can't seem to do it with MySQL. Here is my code:
IF COALESCE((SELECT 1 FROM gt_postmeta WHERE meta_value = '12345' LIMIT 1), 0) THEN
BEGIN
INSERT INTO gt_posts (
post_date,
post_content,
post_title,
post_name,
post_type )
VALUES (
'',
'0000493729150.jpg',
'SHAMROCK',
'',
'');
SET #parent_id = (SELECT id FROM gt_posts ORDER BY id DESC LIMIT 1);
INSERT INTO gt_postmeta(
post_id,
meta_key,
meta_value)
VALUES
(#parent_id, '_visibility', 'visible'),
(#parent_id, '_price', 'H72 SEMI-GLOSS DARK EARTH'),
(#parent_id, '_regular_price', 'H72 SEMI-GLOSS DARK EARTH'),
(#parent_id, '_sku', '12345');
INSERT INTO gt_warehouse(
warehouse_id,
stock,
product_sku)
VALUES(
'178',
'',
'12345');
END;
ELSE
BEGIN
IF COALESCE((SELECT 1 FROM gt_warehouse WHERE product_sku = '12345' AND warehouse_id = '178' LIMIT 1), 0) THEN
BEGIN
UPDATE
gt_warehouse SET stock = ''
WHERE product_sku = '12345'
AND warehouse_id = '178';
END;
ELSE
BEGIN
INSERT INTO gt_warehouse(
warehouse_id,
stock,
product_sku)
VALUES (
'',
'',
'',
'12345');
END;
END IF;
END;
END IF;

Follow: You must do the following things to perform this task in MYSQL
1- you have use STORED PROCEDURE in MYSQL to set query block as package exactly as in SQL SERVER but syntax is bit different
CREATE DEFINER=`root`#`localhost` PROCEDURE `sp_report`(
------
------
)
BEGIN
--Query block with condition
END;
2- You don't need COALESCE to check the existence of value, you can simply use EXISTS (LIMIT is also optional)
IF EXISTS(SELECT 1 FROM gt_postmeta WHERE meta_value = '12345') THEN
3- After this you may get minor issues like proper use of semicolon, BEGIN & END which you can resolve by yourself.

I think this should work:
SELECT id, login, ( IF(requires='privacy-weight',0,privacy-weight) ) AS privacy-weight, requires
FROM [mytable]
WHERE [mywhereclause]
For more information on how the IF function works in MySQL see the docs at http://dev.mysql.com/doc/refman/5.5/en/if.html

Related

Creating / Using a stored procedure with a Laravel migration

This one is a little squirrely!
I have built a stored procedure that works fine. However, when I put it into a Laravel (eloquent) migration, the migration builds the stored procedure but when it is called it gives me an error 1292 (Truncated incorrect DOUBLE value). From mysql workbench, I then right click on the stored procedure built by the migration, copy the create statement, delete the stored procedure and rebuild it from its own create statement.... AND..... EVERYTHING WORKS FINE!
I am open to any and all guesses. I obviously want this stored procedure to be part of my migrations and not have it be something that needs to be built manually. Thanks in advance:
DELIMITER $$
CREATE PROCEDURE sp_cancelTasksOnApplicationCancel(IN loanAppId INT(20))
BEGIN
SELECT loanAppId;
#SET #taskids := 0;
update tasks set task_status = 26, task_status_note = 'Application Cancelled or Denied'
where loan_app_id = loanAppId AND task_status IN (22, 23, 24) AND ( SELECT #taskids := CONCAT_WS(',', id, #taskids) );
#SELECT #taskids;
SET #cleanids = SUBSTRING(#taskids, 1, CHAR_LENGTH(#taskids) -1);
#SELECT #cleanids;
SET #pos = 0;
SET #len = 0;
WHILE LOCATE(',', #cleanids, #pos+1) > 0 DO
SET #len = LOCATE(',', #cleanids,#pos+1) - #pos;
SET #id = SUBSTRING(#cleanids, #pos, #len);
#select (#id);
IF (#id <> 0) THEN
insert into status_historys
(loan_app_id, `type`, type_recid, type_keyfield,type_status_field, type_status_date_field, type_status_note_field, type_status_userid_field, `status`, status_date, status_note, status_userid, created_at, updated_at)
values(loanAppId, 'tasks', #id, 'id', 'task status', 'tasks status datetime', 'tasks status note', 'tasks status userid', 26, now(), 'Application Cancelled or Denied', 1, now(), now());
END IF;
SET #pos = LOCATE(',', #taskids, #pos+#len) + 1;
END WHILE;
END $$
DELIMITER ;
############## UPDATE
The problem lies in the following two lines. Even from the command line it gives a warning about the incorrect double value.
SET #taskids := '0';
update tasks set task_status = 22, task_status_note = 'Just Testing'
where loan_app_id = loanAppId AND task_status IN (22, 23, 24) AND ( SELECT
#taskids := CONCAT_WS(',', cast(id as char), #taskids) );
OK - The main problem was the select keyword in the update where clause. I believe it kept re-declaring the #taskids user variable and mysql gave warnings if the stored procedure was built from the workbench, but failed if the stored procedure was built by a migration.
So the correct code was
#snip#
#Note string '0' rather than 0
SET #taskids := '0';
#note casting of id and removing the select keyword before the first #taskids
update tasks set task_status = 22, task_status_note = 'Just Testing'
where loan_app_id = loanAppId AND task_status IN (22, 23, 24) AND (
#taskids := CONCAT_WS(',', cast(id as char), #taskids) );
#snip#

MySQL migrate data from one table to another

I'm attempting to move data from one table to another, as part of a restructure to the database architecture. I'm using PHPMyAdmin and MySQL to do so.
The SQL is meant to, for each emergency_contacts.id, move e_c.id and e_c.activity_id to activities_emergency_contacts, where the pair will form a composite key to link a contact with an activity.
The following SQL returns an error:
CREATE PROCEDURE dowhile()
BEGIN
SET #cid = (SELECT MIN(`id`) FROM `emergency_contacts`);
SET #aid = (SELECT `activity_id` FROM `emergency_contacts` WHERE `id` = #cid);
WHILE #cid IS NOT NULL DO
INSERT INTO activities_emergency_contacts (activity_id, contact_id)
VALUES (#aid, #cid);
SET #cid = (SELECT MIN(id) FROM emergency_contacts WHERE #cid < id);
SET #aid = (SELECT activity_id FROM emergency_contacts WHERE id = #cid);
END WHILE;
END;
CALL dowhile();
SQL query:
CREATE PROCEDURE dowhile() BEGIN SET #cid = (SELECT MIN(id) FROM
emergency_contacts)
MySQL said:
#1064 - You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '' at line 3
I have searched the MySQL specific documentation to try and find any issues with my SET, WHILE, BEGIN/END, INSERT, CREATE - just about every line. I'm unsure how to proceed. Any advice would be greatly appreciated.
EDIT: I missed a closing bracket inside the WHILE on SELECT MIN (id), however I've fixed this and am still getting the exact same issue.
EDIT: The issue was that I was not using DELIMITER. Correct SQL:
DELIMITER $$
CREATE PROCEDURE dowhile()
BEGIN
SET #cid = (SELECT MIN(`id`) FROM `emergency_contacts`);
SET #aid = (SELECT `activity_id` FROM `emergency_contacts` WHERE `id` = #cid);
WHILE #cid IS NOT NULL DO
INSERT INTO activities_emergency_contacts (activity_id, contact_id) VALUES (#aid, #cid);
SET #cid = (SELECT MIN(id) FROM emergency_contacts WHERE #cid < id);
SET #aid = (SELECT activity_id FROM emergency_contacts WHERE id = #cid);
END WHILE;
END$$
DELIMITER ;
CALL dowhile();

Custom auto-increment column

I am making a C# project in which I need help to generate and insert fields like below on MySQL database.
161013001
Where:
16 is Year,
10 is Month,
13 is day
and 001 is auto-increment numbers that reset each days.
Eg.
161012-001
161012-002
161012-002
161013-001
161013-002
161014-001
161014-002
161014-003
161014-004
161014-005
161015-001
please guide me how to make this that ID reset each day and start from 1 after every day.
Here both MySQL & SQL Server Implementation are added,
MySQL:
DROP TEMPORARY TABLE TempDate;
CREATE TEMPORARY TABLE TempDate(
Id VARCHAR(50),
Comments VARCHAR(50)
);
INSERT INTO TempDate(Id,Comments)
SELECT '190630-001', '1'
UNION
SELECT '190630-002', '2'
UNION
SELECT '190701-001', '1'
UNION
SELECT '190701-002', '2'
UNION
SELECT '190701-003', '3';
SET #v_ToDay = '';
SET #v_ToDay = (SELECT date_format(current_date(),'%y%m%d'));
SET #v_TotalByDay = '' ;
SET #v_TotalByDay =(
SELECT CONCAT('000',CAST(CASE WHEN COUNT(1) = 0 THEN 1 ELSE COUNT(1)+1 END as CHAR))
FROM TempDate
WHERE LEFT(Id,6) = #v_ToDay);
SELECT CONCAT(#v_ToDay, '-', CASE WHEN CHAR_LENGTH(RTRIM(#v_TotalByDay)) > 3 THEN RIGHT(#v_TotalByDay,3) ELSE #v_TotalByDay END) as NewIdColumn
SQL Server:
DECLARE #TempDate TABLE(
Id NVARCHAR(50),
Comments NVARCHAR(MAX)
)
INSERT INTO #TempDate(Id,Comments)
SELECT '190630-001', '1'
UNION
SELECT '190630-002', '2'
UNION
SELECT '190701-001', '1'
UNION
SELECT '190701-002', '2'
UNION
SELECT '190701-003', '3'
DECLARE #ToDay NVARCHAR(20) = (SELECT CONVERT(NVARCHAR(6), GETDATE(), 12))
DECLARE #TotalByDay NVARCHAR(20) = ''
SELECT #TotalByDay = '000' + CAST(CASE WHEN COUNT(1) = 0 THEN 1 ELSE COUNT(1)+1 END as NVARCHAR(20) )
FROM #TempDate
WHERE LEFT(Id,6) = #ToDay
SELECT #ToDay + '-' + CASE WHEN LEN(#TotalByDay) > 3 THEN RIGHT(#TotalByDay,3) ELSE #TotalByDay END as NewIdColumn
my situation is little more different then this question ....
i have a legacy database (it's not operational only use for reporting
purpose..)
in this DB transaction table was a auto increment tranx id column.
like 1, 2, 3 ...... but now our new report need meaningful tranx id
(yyMMDD<count of that day>) like this question. so actually i need a
select query to solve this problem.
with the help of #Khairul 's logic i solve my problem ....
i share my solution for other's help....
SELECT
trnx_id, account_id, pay_amount,counter_id, trantime, trandate
FROM(
SELECT
#id:=IF(#prev != t.trandate, #rownum:=1, #rownum:=#rownum+1)
,#prev:=t.trandate
,CONCAT(
SUBSTR(YEAR(t.`trandate`),3) -- year
,IF(LENGTH(MONTH(t.`trandate`))=1,CONCAT('0',MONTH(t.`trandate`)),MONTH(t.`trandate`)) -- month
,IF(LENGTH(DAY(t.`trandate`))=1,CONCAT('0',DAY(t.`trandate`)),DAY(t.`trandate`)) -- day
,IF(LENGTH(#id)=1,CONCAT('000',#id),IF(LENGTH(#id)=2,CONCAT('00',#id),IF(LENGTH(#id)=3,CONCAT('0',#id),#id))) -- count
) AS trnx_id
,t.*
FROM tax_info t ORDER BY t.`trandate`, t.`trantime`
) AS te
and my query result is ..........
After solving my problem i try to solve this question .......
for this i use a trigger for input auto increment custom column ...
my code is below , here my payment column has a custom tranx id ....
DELIMITER $$
DROP TRIGGER tranxidGeneration$$
CREATE
TRIGGER tranxidGeneration BEFORE INSERT ON payment
FOR EACH ROW BEGIN
DECLARE v_tranx_id_on INT;
-- count total row of that day
select IFNULL(COUNT(tranx_id),0)+1 Into v_tranx_id_on from payment where SUBSTR(tranx_id,1,6) = DATE_FORMAT(NOW(), "%y%m%d");
-- set custom generate id into tranx_id column
SET NEW.tranx_id := CONCAT(DATE_FORMAT(NOW(), "%y%m%d"),LPAD(v_tranx_id_on,4,0)) ;
END;
$$
DELIMITER ;

MySQL trigger regexp not working

I am trying to group users by their email address.
REGEXP is not working. Help
BEGIN
DECLARE group_id tinyint(4);
SET #user_email = ( SELECT email FROM `users` WHERE id = NEW.id );
IF (SELECT #user_email REGEXP '\.com$') THEN SET group_id = 17;
ELSE SET group_id = 18;
END IF;
INSERT INTO `user_usergroup_map` (`user_id`, `group_id`) VALUES (NEW.id, group_id);
END
--------- New -------------------
I did some more testing.
This worked...
BEGIN
INSERT INTO `user_usergroup_map` (`user_id`, `group_id`) VALUES( 3, 18 );
END
But this didn't work
BEGIN
INSERT INTO `user_usergroup_map` (`user_id`, `group_id`) VALUES(
NEW.id, 18);
END
Rather than try to figure out what is not working, let me suggest this simplification:
BEGIN
INSERT INTO `user_usergroup_map` (`user_id`, `group_id`)
SELECT NEW.id,
IF( email REGEXP '[.]com$', 17, 18 )
FROM users
WHERE id = NEW.id;
END
One difference: Your version will always insert a row; mine won't if id is missing from users.

Calling stored procedure sequentially from .sql file

I'm stuck here.
I've got a Procedure that I want to run X* times in a row. (*X is couple of thousands times)
The procedure based on input data does this:
1. Looks for an actions.id, if not found LEAVEs.
2. Looks for users.id, if not found, creates one and uses LAST_INSERT_ID();
3-5. Looks for summaries.id (3 types, total, daily and monthly), if not found, creates one and uses it's ID.
6. Once all required ids are collected, INSERTs new row into actions and either updates the summaries rows in a transaction, so if any fails - it does a ROLLBACK - no harm done.
7. Depending on the outcome SELECTs message.
CREATE PROCEDURE NEW_ACTION(
IN a_date TIMESTAMP,
IN u_name VARCHAR(255),
IN a_name VARCHAR(255),
IN a_chars INT,
IN url VARCHAR(255),
IN ip VARCHAR(15))
lbl_proc: BEGIN
DECLARE a_id, u_id, us_id, usd_id, usm_id, a_day, a_month, error INT;
DECLARE CONTINUE HANDLER FOR SQLSTATE '23000' SET error = 1;
SET error = 0;
SET a_day = DATE_FORMAT(SUBSTRING(a_date ,1,10), '%Y%m%d');
SET a_month = SUBSTRING(a_day, 1, 6);
/* 1. RETREIVING action.id */
SET a_id = (SELECT `id` FROM `actions` WHERE `name` = a_name);
IF a_id IS NULL THEN
SELECT 'error';
LEAVE lbl_proc;
END IF;
/* 2. RETREIVING users.id */
SET u_id = (SELECT `id` FROM `users` WHERE `name` = u_name);
IF u_id IS NULL THEN
INSERT INTO `users` (name) VALUES (u_name);
SET u_id = (SELECT LAST_INSERT_ID());
END IF;
/* 3. RETREIVING user_summaries.id */
SET us_id = (SELECT `id` FROM `users_summaries` WHERE `user_id` = u_id AND `action_id` = a_id);
IF us_id IS NULL THEN
INSERT INTO `users_summaries` (user_id, action_id) VALUES (u_id, a_id);
SET us_id = (SELECT LAST_INSERT_ID());
END IF;
/* 4. RETREIVING user_summaries_days.id */
SET usd_id = (SELECT `id` FROM `users_summaries_days` WHERE `day` = a_day AND `user_id` = u_id AND `action_id` = a_id);
IF usd_id IS NULL THEN
INSERT INTO `users_summaries_days` (day, user_id, action_id) VALUES (a_day, u_id, a_id);
SET usd_id = (SELECT LAST_INSERT_ID());
END IF;
/* 5. RETREIVING user_summaries_months.id */
SET usm_id = (SELECT `id` FROM `users_summaries_months` WHERE `month` = a_month AND `user_id` = u_id AND `action_id` = a_id);
IF usm_id IS NULL THEN
INSERT INTO `users_summaries_months` (month, user_id, action_id) VALUES (a_month, u_id, a_id);
SET usm_id = (SELECT LAST_INSERT_ID());
END IF;
/* 6. SAVING action AND UPDATING summaries */
SET autocommit = 0;
START TRANSACTION;
INSERT INTO `users_actions` (`date`, `user_id`, `action_id`, `chars`, `url`, `ip`) VALUES (a_date, u_id, a_id, a_chars, url, ip);
UPDATE `users_summaries` SET qty = qty + 1, chars = chars + a_chars WHERE id = us_id;
UPDATE `users_summaries_days` SET qty = qty + 1, chars = chars + a_chars WHERE id = usd_id;
UPDATE `users_summaries_months` SET qty = qty + 1, chars = chars + a_chars WHERE id = usm_id;
IF error = 1 THEN
SELECT 'error';
ROLLBACK;
LEAVE lbl_proc;
ELSE
SELECT 'success';
COMMIT;
END IF;
END;
Now, I've got raw data that I want to feed into this procedure. There's currently about 3000 rows.
I tried all the solutions I knew:
A. # mysql -uuser -ppass DB < calls.sql - Using php I've basically created a list of calls like this:
CALL NEW_ACTION('2010-11-01 13:23:00', 'username1', 'actionname1', '100', 'http://example.com/', '0.0.0.0');
CALL NEW_ACTION('2010-11-01 13:23:00', 'username2', 'actionname1', '100', 'http://example.com/', '0.0.0.0');
CALL NEW_ACTION('2010-11-01 13:23:00', 'username1', 'actionname2', '100', 'http://example.com/', '0.0.0.0');
...
This fails always (tried few times) at row 452 where it found two summary IDs (step 3).
I thought this could be due to the fact that earlier (rows 375-376) there are calls for the same user for the same action.
As if mysql didn't update tables in time, so the summary row created in CALL from line 375 isn't yet visible when line 376 gets executed - therefore creating another summary line.
Tought I'd try delaying calls...
B. Using mysql's SLEEP(duration).
This didn't change anything. Execution stops at the very same CALL again.
I'm out of ideas now.
Suggestions and help hugely appreciated.
NOTE: action names and user names repeat.
PS. Bear in mind this is one of my first procedures ever written.
PS2. Running mysql 5.1.52-community-log 64bit (Windows 7U), PHP 5.3.2 and Apache 2.2.17
EDIT
I've removed PHP related part of question to a separate question here.
EDIT2
Ok, I've deleted the first 200 calls from the .sql file. For some reason it went fine past the previous line that was stopping execution. Now it stopped at row 1618.
This would mean, that at one point a newly INSERTed summary row is no visible for a moment, therefore when it happens that one of the following iterations want to SELECT it, it's not yet accessible for them. Is that a MySQL bug?
EDIT3
Now there's another interesting thing I noticed. I investigated where two users_summaries get created. This happens (not always, but if, then it is) when there are two CALLs referring to the same user and action in close proximity. They could be next to each other or separated by 1 or 2 different calls.
If I move one of them (within .sql file) like 50-100 rows lower (executed earlier) than it's fine. I even managed to make the .sql file work as a whole. But this still doesn't really solve the problem. With 3000 rows it's not that bad, but if I had 100000, I'm lost. I can't rely on manual tweaks to .sql file.
This isn't really a solution, but a workaround.
Just to clarify, summary tables had id column as PRIMARY KEY with AUTO_INCREMENT option and indexes on both user_id and action_id column.
My investigation showed that although my procedure was looking for an entry that existed using WHERE user_id = u_id AND action_id = a_id in certain situations it didn't find it causing new row being inserted with the same user_id and action_id values - something I did not want.
Debugging the procedure showed that the summary row I was looking for, although not accessible with WHERE user_id = u_id AND action_id = a_id condition, was properly returned when calling it's id - PRIMARY KEY.
With this find I decided to change format of id column, from UNASIGNED INT with AUTO_INCEREMENT to a CHAR(32) which consisted of:
<user_id>|<action_id>
This meant that I knew exactly what the id of the row I wanted is even before it existed. This solved the problem really. It also enabled me to use INSERT ... ON DUPLICATE KEY UPDATE ... construct.
Below my updated procedure:
CREATE PROCEDURE `NEW_ACTION`(
IN a_date TIMESTAMP,
IN u_name VARCHAR(255),
IN a_name VARCHAR(255),
IN a_chars INT,
IN url VARCHAR(255),
IN ip VARCHAR(15))
SQL SECURITY INVOKER
lbl_proc: BEGIN
DECLARE a_id, u_id, a_day, a_month, error INT;
DECLARE us_id, usd_id, usm_id CHAR(48);
DECLARE sep CHAR(1);
DECLARE CONTINUE HANDLER FOR SQLSTATE '23000' SET error = 1;
SET sep = '|';
SET error = 0;
SET a_day = DATE_FORMAT(SUBSTRING(a_date ,1,10), '%Y%m%d');
SET a_month = SUBSTRING(a_day, 1, 6);
/* RETREIVING action.id */
SET a_id = (SELECT `id` FROM `game_actions` WHERE `name` = a_name);
IF a_id IS NULL THEN
SELECT 'error';
LEAVE lbl_proc;
END IF;
/* RETREIVING users.id */
SET u_id = (SELECT `id` FROM `game_users` WHERE `name` = u_name);
IF u_id IS NULL THEN
INSERT INTO `game_users` (name) VALUES (u_name);
SET u_id = LAST_INSERT_ID();
END IF;
/* SETTING summaries ids */
SET us_id = CONCAT(u_id, sep, a_id);
SET usd_id = CONCAT(a_day, sep, u_id, sep, a_id);
SET usm_id = CONCAT(a_month, sep, u_id, sep, a_id);
/* SAVING action AND UPDATING summaries */
SET autocommit = 0;
START TRANSACTION;
INSERT INTO `game_users_actions` (`date`, `user_id`, `action_id`, `chars`, `url`, `ip`)
VALUES (a_date, u_id, a_id, a_chars, url, ip);
INSERT INTO `game_users_summaries` (`id`, `user_id`, `action_id`, `qty`, `chars`)
VALUES (us_id, u_id, a_id, 1, a_chars)
ON DUPLICATE KEY UPDATE qty = qty + 1, chars = chars + a_chars;
INSERT INTO `game_users_summaries_days` (`id`, `day`, `user_id`, `action_id`, `qty`, `chars`)
VALUES (usd_id, a_day, u_id, a_id, 1, a_chars)
ON DUPLICATE KEY UPDATE qty = qty + 1, chars = chars + a_chars;
INSERT INTO `game_users_summaries_months` (`id`, `month`, `user_id`, `action_id`, `qty`, `chars`)
VALUES (usm_id, a_month, u_id, a_id, 1, a_chars)
ON DUPLICATE KEY UPDATE qty = qty + 1, chars = chars + a_chars;
IF error = 1 THEN
SELECT 'error';
ROLLBACK;
LEAVE lbl_proc;
ELSE
SELECT 'success';
COMMIT;
END IF;
END
Anyway, I still think there's some kind of a bug in MySQL, but I consider problem solved.