FIND_IN_SET slow, can't use IN() - mysql

I have a stored procedure and it's running slow. Is there a better way to pass the table id's to the procedure rather that use FIND_IN_SET?
The table column e.fk_trans_history_id is an INT(32)
DELIMITER $$
CREATE DEFINER=`root`#`localhost` PROCEDURE `1_trans_hist_ent_items_sel`(b INT,tid TEXT)
BEGIN
SELECT
e.id,
e.fk_trans_history_id,
e.fk_prod_pack_id,
e.line_num,
e.vat_code,
e.code,
e.make,
e.model,
e.price,
e.discount,
e.cover,
e.warranty,
p.make,
p.model,
p.weight,
p.width,
p.depth,
p.height,
p.cost,
p.cover_value_each,
p.web_model,
k.stock - k.repair
FROM
1_trans_history_entries e
LEFT JOIN
1_products p ON p.id = e.fk_prod_pack_id
AND LEFT(code, 1) <> 'P'
LEFT JOIN
1_stock k ON k.fk_products_id = p.id AND k.branch = b
WHERE
(e.code IN ('MiscShip' , 'Collect')
OR (ASCII(e.code) > 47
AND ASCII(e.code) < 58))
AND FIND_IN_SET(e.fk_trans_history_id, tid)
ORDER BY e.id;
END

Put the IDs you want to match into a temporary table, and join with that table.
CREATE TEMPORARY TABLE temp_trans_hist_ent_items_sel_ids (
id
);
INSERT INTO temp_trans_hist_ent_items_sel_ids VALUES (1), (10), ...;
Then the query in the procedure can use:
JOIN temp_trans_hist_ent_items_sel_ids AS temp ON e.fk_trans_history_id = temp.id

I found the best and the fastest is to prepare a query like this.
CREATE DEFINER=`root`#`localhost` PROCEDURE `1_trans_hist_ent_items_sel`(b INT, tid TEXT)
BEGIN
--
SET #query=CONCAT( '
SELECT
e.id,
e.fk_trans_history_id,
e.fk_prod_pack_id,
e.line_num,
e.vat_code,
e.code,
e.make,
e.model,
e.price,
e.discount,
e.cover,
e.warranty,
p.make,
p.model,
p.weight,
p.width,
p.depth,
p.height,
p.cost,
p.cover_value_each,
p.web_model,
k.stock - k.repair
FROM
1_trans_history_entries e
LEFT JOIN
1_products p ON p.id = e.fk_prod_pack_id
AND LEFT(code, 1) <> \'P\'
LEFT JOIN
1_stock k ON k.fk_products_id = p.id AND k.branch = ',b,'
WHERE
(e.code IN (\'MiscShip\' , \'Collect\')
OR (ASCII(e.code) > 47
AND ASCII(e.code) < 58))
AND e.fk_trans_history_id IN(' , tid , ')
ORDER BY e.id;');
--
PREPARE sql_query FROM #query;
EXECUTE sql_query;
--
SET #query = '';
--
END

Thanks to Stephen Bouffe. My code is shorter and it would be easier to understand and implement this solution. In my case, it looks like this
SET #query=CONCAT( '
DELETE FROM markup
WHERE id IN (', #ids, ');
');
PREPARE sql_query FROM #query;
EXECUTE sql_query;
Where #ids is a string like '7,12,52'.
It works significantly faster then FIND_IN_SET.

Related

Converting T-SQL to MySQL with temp tables

I am working on converting a T-SQL stored procedure into MySQL. I am not familiar with T-SQL and am working on becoming more familiar with temp tables and stored procedures. Thanks for your help in advance.
The T-SQL original looks like this (EDIT: note this is only a portion of the original procedure used to produce a report):
DROP TABLE IF EXISTS #accounts;
SELECT d.data_id
,p.pp_name AS name
,CONVERT(tinyint,1) AS flag
,d.pd_date
,CONVERT(char(6),pd_date,112) AS date_period
,CONVERT(varchar(3),0) AS n_phones
INTO #accounts
from table_detail d
JOIN table_pool p ON d.pp_id = p.pp_id
JOIN table_type t ON p.pp_type_id = t.pp_type_id
JOIN Inventory i ON d.data_id = i.data_id
JOIN Product pr ON i.product_id = pr.product_id
WHERE pp_name IN (SELECT name FROM Sandbox..desired_sandbox)
AND DATEDIFF(MONTH,pd_date,GETDATE()) < 3
UPDATE a
SET a.flag = 0
FROM #accounts a
JOIN table_detail d ON a.data_id = d.data_id
JOIN table_pool p ON d.pp_id = p.pp_id
WHERE d.pd_date < a.pd_date
AND pp_name != 'error';
My current update is below. Do I need to wrap this in a CREATE TEMPORARY TABLE IF NOT EXISTS accounts AS (<insert query here>) instead of the INTO #accounts?
SELECT d.data_id
,p.pp_name AS name
,CONVERT(tinyint,1) AS flag
,d.pd_date
,DATE_FORMAT(pd_date,'%Y%m%d') AS date_period
,CONVERT(varchar(3),0) AS n_phones
FROM table_detail d
JOIN table_pool p ON d.pp_id = p.pp_id
JOIN table_type t ON p.pp_type_id = t.pp_type_id
JOIN Inventory i ON d.data_id = i.data_id
JOIN Product pr ON i.product_id = pr.product_id
WHERE pp_name IN (SELECT name FROM Sandbox..desired_sandbox)
AND TIMESTAMPDIFF(MONTH,pd_date,NOW()) < 3
Then do something like this assuming the syntax is right:
UPDATE accounts a -- Is this the correct way to add an alias and update the temp table?
JOIN table_detail d ON a.data_id = d.data_id
JOIN table_pool p ON d.pp_id = p.pp_id
SET a.flag = 0
WHERE d.pd_date < a.pd_date
AND pp_name != 'error';
Finally, I assume I could follow this post to wrap the final query into a stored procedure, correct? To summarize the code in the post:
drop procedure if exists procedure_name;
DELIMITER $$
create procedure procedure_name ()
BEGIN
DROP TEMPORARY TABLE IF EXISTS accounts;
CREATE TEMPORARY TABLE accounts AS (
SELECT...
FROM...
WHERE...
;
)
UPDATE accounts
JOIN ...
JOIN ...
SET...
WHERE...;
DROP TEMPORARY TABLE accounts; -- otherwise it survives the stored proc call
END
$$ -- signify end of block
DELIMITER ; -- reset to default delimiter
I wanted to post the solution that finally worked for me for others that might be having the same issue. The code below resolves this.
DROP PROCEDURE IF EXISTS procedure_name;
DELIMITER $$
CREATE PROCEDURE procedure_name ()
BEGIN
DROP TEMPORARY TABLE IF EXISTS accounts;
CREATE TEMPORARY TABLE accounts AS (
SELECT d.data_id
,p.pp_name AS name
,1 AS flag
,d.pd_date
,DATE_FORMAT(pd_date,'%Y%m%d') AS date_period
,CONVERT(varchar(3),0) AS n_phones
FROM table_detail d
JOIN table_pool p ON d.pp_id = p.pp_id
JOIN table_type t ON p.pp_type_id = t.pp_type_id
JOIN Inventory i ON d.data_id = i.data_id
JOIN Product pr ON i.product_id = pr.product_id
WHERE pp_name IN (SELECT name FROM Sandbox..desired_sandbox)
AND TIMESTAMPDIFF(MONTH,pd_date,NOW()) < 3
);
-- Update the temp table based on specified criteria
UPDATE accounts
JOIN ...
JOIN ...
SET...
WHERE...;
-- Create final Query for report
SELECT ...
FROM accounts
WHERE ...
GROUP BY ... -- whatever you need for final query
DROP TEMPORARY TABLE accounts; -- otherwise it survives the stored proc call
END
$$ -- signify end of block
DELIMITER ; -- reset to default delimiter
However, it is important to note the limits of MySQL temporary tables. Although I did not indicate it in my original post, later, I was attempting to join the temporary table onto itself. I needed to follow the suggestion outlined here. Essentially, if you need to refer to a temp table with itself you need to make a copy of the temp table: CREATE TEMPORARY TABLE accounts2 AS (SELECT * FROM accounts). Then you can join a temp table to itself.

Incorrect usage of UNION and INTO while Creating Procedure in MySql

While creating procedure following error occurs:
Error Code: 1221. Incorrect usage of UNION and INTO
My Procedure is:
SET sql_mode=ORACLE;
DELIMITER $$
CREATE PROCEDURE MARK_ATTENDANCE (IN EmployeeId INTEGER)
BEGIN
DECLARE EmpShift INTEGER(10);
DECLARE EmpMarkLoc INTEGER(10);
SET EmpShift = 0;
SET EmpMarkLoc = 0;
SELECT ed.default_shift, sl.location_id INTO EmpShift,EmpMarkLoc
FROM EMPLOYEE ed
LEFT JOIN sublocation sl
ON sl.sublocation_id = ed.sub_location_id
UNION ALL
SELECT ed.default_shift, sl.location_id
FROM EMPLOYEE ed
RIGHT JOIN sublocation sl
ON sl.sublocation_id = ed.sub_location_id
WHERE sl.sublocation_id IS NULL
AND emp_id = EmployeeId;
END ;
$$
DELIMITER ;
Actually I wanted a FULL OUTER JOIN and ended up on UNION ALL and tried to store output in some variables like EmpShift and EmpMarkLoc for further usage.
SELECT #EmpShift:=ed.default_shift, #EmpMarkLoc:=sl.location_id
used above in place of
SELECT ed.default_shift, sl.location_id INTO EmpShift,EmpMarkLoc
You can put the unioned queries in a derived table and select from there into the variables
SELECT u.default_shift,
u.location_id
INTO empshift,
empmarkloc
FROM (SELECT ed.default_shift,
sl.location_id
FROM employee ed
LEFT JOIN sublocation sl
ON sl.sublocation_id = ed.sub_location_id
UNION ALL
SELECT ed.default_shift,
sl.location_id
FROM employee ed
RIGHT JOIN sublocation sl
ON sl.sublocation_id = ed.sub_location_id
WHERE sl.sublocation_id IS NULL
AND emp_id = employeeid) u;
But be aware, if the unioned querys can select more than one row an assignment to scalar variables is problematic.

How do I put a list of id in an INNER JOIN with a sql variable?

I apologize in advance, I'm not good at sql.
I want to do something very simple in a sql procedure but I'm stuck :
SELECT donnee.id_enregistrement, ',#col_createur, #col_date_creation, #col_date_derniere_modification, #COLUMNS
FROM ta_donnee_champ donnee
In the variable #COLUMNS there is a list of id, that I search in the table "ta_donnee_champ donnee".
I also want to use the same list of id to search in another table in the same query.
How can I put that in an inner join?
Like something like this :
SELECT donnee.id_enregistrement, ',#col_createur, #col_date_creation, #col_date_derniere_modification, #COLUMNS,'
FROM ta_donnee_champ donnee
INNER JOIN ta_champ_tableau ct
ON ct.id_champ_colonne IN (#COLUMNS)
Can someone give me a simple explanation please?
******************************EDIT********************************
In this procedure #COLUMNS represents columns of the desired results.
To these columns, I need to add values, the following code works :
IF #COLUMNS IS NOT NULL THEN
SET #SQL = CONCAT(
'SELECT donnee.id_enregistrement, ',#col_createur, #col_date_creation, #col_date_derniere_modification, #COLUMNS,'
FROM ta_donnee_champ donnee
INNER JOIN t_enregistrement enregistrement
ON donnee.id_enregistrement = enregistrement.id_enregistrement
WHERE donnee.id_enregistrement IN (',IFNULL(p_id_enregistrement, 'donnee.id_enregistrement'),')
AND donnee.id_enregistrement in (
select DISTINCT id_enregistrement from ta_participant
where id_groupe = ', p_id_groupe,'
or id_groupe in (
select id_groupe_lu
from ta_droits_groupe
inner join t_groupe on ta_droits_groupe.id_groupe_lu = t_groupe.id_groupe
where id_groupe_lecteur = ', p_id_groupe,'
and t_groupe.id_application = ', p_id_application,'
)
)
GROUP BY donnee.id_enregistrement'
);
-- Prépare et exécute la requête
PREPARE stmt FROM #SQL;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
My question is : I need to add another table from which I must know that ids are inside. How do I proceed best to search not only for id's in ta_donnee_champ donnee but also in another table?
How is #COLUMNS set :
SET #COLUMNS = NULL;
IF p_autoriser_tableau = 1 THEN
SET #filtre = 'SELECT DISTINCT id_champ_colonne FROM ta_champ_tableau';
ELSE
SET #filtre = 'SELECT DISTINCT id_champ_tableau AS id_champ FROM ta_champ_tableau UNION SELECT DISTINCT id_champ_colonne FROM ta_champ_tableau';
END IF;
-- Construit les colonnes à  horizontaliser
SET #SQL1 = CONCAT('
SELECT GROUP_CONCAT(
DISTINCT CONCAT(
\'GROUP_CONCAT(IF(donnee.id_champ = \',
champ.id_champ ,
\', donnee.valeur, NULL)) AS `\',
champ.id_champ,\'`\'
)ORDER BY onglet.ordre, section.ordre, champ.ordre
) INTO #COLUMNS
FROM t_champ champ
INNER JOIN t_section section
ON section.id_section = champ.id_section
INNER JOIN t_onglet onglet
ON onglet.id_onglet = section.id_onglet
INNER JOIN ta_droits_champ droits_champ
ON droits_champ.id_groupe IN (
SELECT id_groupe FROM ta_agent_groupe WHERE id_agent = ', p_id_agent,'
)
AND droits_champ.id_champ = champ.id_champ
where onglet.id_application IN (', p_id_application,')
AND droits_champ.voir = 1
AND champ.id_champ NOT IN (',#filtre,')
AND
(
champ.date_fin_validite IS NULL
OR
date_format(champ.date_fin_validite, "%Y-%m-%d") > COALESCE(str_to_date("', p_date_validite,'", "%d/%m/%Y"), "01/01/1900")
)
;');
PREPARE stmt FROM #SQL1;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;

MySQL prepared statement in a stored procedure not behaving as expected

I have this MySQL Stored Procedure
DELIMITER $$
CREATE DEFINER=`dbuser`#`%` PROCEDURE `getTranslatedAnswer`(IN questionDesc VARCHAR(2500), col VARCHAR(10))
BEGIN
SET #paramVal = questionDesc;
SET #str = CONCAT('SELECT C.',col,' AS `answer`, D.',col,' AS `message`
FROM `option_group` A
INNER JOIN `questions_answers` B ON A.`option_id` = B.`option_id`
INNER JOIN `answers` C ON B.`answers_id` = C.`answers_id`
INNER JOIN `chat_message` D ON A.`option_group_id` = D.`option_group_id`
WHERE UPPER(`A`.',col,') = ? LIMIT 1');
PREPARE stmt FROM #str;
EXECUTE stmt USING #paramVal;
DEALLOCATE PREPARE stmt;
END
The above code when called using
call getTranslatedAnswer('我可以在机场买到预付SIM卡吗?','zh_chs');
will return 0 rows. Compared to as when I call the bare SQL below
SELECT C.zh_chs AS `answer`, D.zh_chs AS `message`
FROM `option_group` A
INNER JOIN `questions_answers` B ON A.`option_id` = B.`option_id`
INNER JOIN `answers` C ON B.`answers_id` = C.`answers_id`
INNER JOIN `chat_message` D ON A.`option_group_id` = D.`option_group_id`
WHERE UPPER(`A`.`zh_chs`) = '我可以在机场买到预付SIM卡吗?' LIMIT 1;
it returns 1 row. Am I missing something here? How do I debug this? Please advise, thanks!
I could be remembering wrong, but I seem to recall execute not actually returning results as you expecting it to.
What happens if you try something like this?
DELIMITER $$
CREATE DEFINER=`dbuser`#`%` PROCEDURE `getTranslatedAnswer`(IN questionDesc VARCHAR(2500), col VARCHAR(10))
BEGIN
SET #paramVal = questionDesc;
DROP TEMPORARY TABLE IF EXISTS `t_getTranslatedAnswer`;
SET #str = CONCAT('
CREATE TEMPORARY TABLE `t_getTranslatedAnswer`
SELECT C.',col,' AS `answer`, D.',col,' AS `message`
FROM `option_group` A
INNER JOIN `questions_answers` B ON A.`option_id` = B.`option_id`
INNER JOIN `answers` C ON B.`answers_id` = C.`answers_id`
INNER JOIN `chat_message` D ON A.`option_group_id` = D.`option_group_id`
WHERE UPPER(`A`.',col,') = ? LIMIT 1');
PREPARE stmt FROM #str;
EXECUTE stmt USING #paramVal;
DEALLOCATE PREPARE stmt;
SELECT * FROM `t_getTranslatedAnswer`;
DROP TEMPORARY TABLE `t_getTranslatedAnswer`;
END

if clause return more than one row

I need to get data from my table using if clause, here's an example of what I'm trying to do;
select if (condition='example',
(/*query1(return more than one row)*/),
(/*query2(return more than one row)*/) as data from table1 where iduser='1'
my complete query:
SELECT
if(bagian='DOKTER',
(SELECT b.ruper
from bsl_ruang b
join mr_ranap_dokter d
on (b.noregis=d.noregis)
where d.dokter = '999999'),
(SELECT u.nama
FROM m_unit u
join muser_tdinas t
on(t.unit=u.unit)
where id_user = '999999')) as ruper
FROM `muser` where id_user = '999999'
my tables;
bsl_ruang mr_ranap_dokter
++========++======++ ++========++========++
||noregis ||ruper || ||dokter ||noregis ||
++========++======++ ++========++========++
||10000000||ruper1|| ||9999999 ||10000000||
||10000001||ruper2|| ||9999999 ||10000001||
++========++======++ ++========++========++
m_unit muser_tdinas m_user
++========++======++ ++========++========++ ++========++=======++
||unit ||nama || ||id_user ||unit || ||id_user ||bagian ||
++========++======++ ++========++========++ ++========++=======++
||00001 ||ruper1|| ||8888888 ||00001 || ||9999999 ||DOKTER ||
||00002 ||ruper2|| ||8888888 ||00002 || ||8888888 ||PERAWAT||
++========++======++ ++========++========++ ++========++=======++
SUMMARY
so if bagian=dokter, it will join table bsl_ruang and mr_ranap_dokter and get ruper
and if bagian=perawat, it will join table m_unit and muser_tdinas and get nama
but if clause can only return one data, is there any other way to do this? note that I can't change my database
maybe this is not the best solution but I decided to use a procedure for my problem, here's my procedure;
CREATE DEFINER=`root`#`%` PROCEDURE `GetRuperRanap`(lIdUser Varchar(12))
BEGIN
Declare lBagian VarChar(20);
SELECT bagian into lBagian FROM `muser` where id_user = lIdUser;
if lBagian='DOKTER' then
SELECT distinct b.ruper as ruper from bsl_ruang b join mr_ranap_dokter d on (b.noregis=d.noregis) where d.dokter = lIdUser;
else
SELECT u.nama as ruper FROM m_unit u join muser_tdinas t on(t.unit=u.unit) where id_user = lIdUser;
end if;
END;
and I just need to call it on query, hope it help