Why does MySQL find an error in the CONCAT statement? - mysql

I'm trying to write a stored procedure in MySQL, where I need to loop on a cursor, and execute an SQL statement that uses a piece of data from the cursor that gets fetched into a variable, then executed as SQL. The cursor orders_cur is sorted on this_addr; in a given block of this_addr records, the first record is skipped, and the rest need to be flagged by setting the duplicateorder="1".
For some reason, I cannot get the line with the CONCAT function to work without giving me an error:
OPEN orders_cur;
order_loop: LOOP
-- Now loop on orders_cur until this_addr = match_addr
find_addr_loop: REPEAT
FETCH orders_cur INTO this_addr,this_orderid;
UNTIL this_addr = match_addr
END REPEAT;
-- Skip the first order that matched by performing another fetch
FETCH orders_cur INTO this_addr,this_orderid;
-- Now start next loop which does the real work; set duplicateorder on the remaining records in cursor,
-- using the orders_cur.order_id to locate the actual record in the Reservations table.
set_dupe_loop: WHILE this_addr = match_addr
SET #sql = CONCAT('UPDATE Reservations SET duplicateorder = \'1\' WHERE order_id=',this_orderid);
PREPARE runme FROM #sql;
EXECUTE runme;
FETCH orders_cur INTO this_addr,this_orderid;
END WHILE set_dupe_loop:;
DEALLOCATE PREPARE runme;
END LOOP order_loop;
I have tried every variation possible on escaping the literals that I need around the '1' to no avail, and am going cross-eyed...if anyone sees where my error lies, I would very much appreciate it...
--rixter

You don't need a cursor for this operation. You can do:
UPDATE Reservations r JOIN
(SELECT this_addr, MIN(order_id) as minoi
FROM Reservations r2
WHERE this_addr = match_addr
GROUP BY this_addr
) dups
ON r.this_addr = dups.this_addr and r.order_id > dups.minoi
SET r.duplicateorder = 1;
In general, you should avoid cursors, especially those that require dynamic SQL. When you can express the logic as a set operation, it is usually best to do so.

Nothing wrong with the CONCAT, the loop is not initiated/enclosed properly.
This set_dupe_loop: WHILE this_addr = match_addr
should be this set_dupe_loop: WHILE this_addr = match_addr DO

Related

How do I pull out specific strings from a comma separated string?

I have a string like so;
"field1=lance,field2=peter,field3=john"
The actual string has 20+fields, and I want to pull out specific values by name.
For instance, I want to pull out the value for "field2" and return the value "peter",
Can someone give me an elegant way of doing this in MySQL?
I should mention that this is a standard field format coming out of an eCommerce system. I have no control over the format. It would be possible to extract the data cleanly through the API, but that would be significant extra work, especially as I have the data already in this format.
interesting question. There is a lot below so let's break it down. We essentially build a query and execute the stmt
DELIMITER $$
DROP PROCEDURE IF EXISTS proc_loop_test$$
CREATE PROCEDURE proc_loop_test()
#create empty query string
set #sqlstring = '';
#set your string of fields, not sure where this comes from
set #mystring = 'field1=lance,field2=peter,field3=john';
#create number of times we will loop through the string
set #num = (select length(#mystring)
- length(replace('field1=lance,field2=peter,field3=john',',','')) +1);
#init loop
loop LOOP
#create a short string just taking the "last" field/value pair
set #shtstring = (select SUBSTRING_INDEX(#mystring,',',-1));
#recreate your query string removing the substring we created
set #mystring = (select left(replace(#mystring,#shtstring,''),length(replace(#mystring,#shtstring,''))-1));
#add to your query string, we will build this for each
set #sqlstring = concat(#sqlstring ,(select
concat('''',SUBSTRING_INDEX(#shtstring,'=',-1),''''
,' as ',
left(#shtstring,(position('=' in #shtstring) -1))) ),',');
#reduce our count by one as we have removed the latest field
set #num = #num - 1;
#leave the loop when no fields left
if #num = 0 then leave LOOP;
end if;
end loop LOOP;
END$$
DELIMITER ;
#create a query statement to execute
set #query = (select concat('select ',left(#sqlstring, length(#sqlstring)-1)));
#execute the query!
PREPARE stmt FROM #query;
EXECUTE stmt;
Result
field3 field2 field1
john peter lance
There is no array logic, this would be simple in presto SQL etc. Because you have an arbitrary number of fields being defined at any time we are going to need to loop, and unfortunately you cannot loop in mysql without creating a procedure
That is the first few lines. We also create our full string from your source and the number of iterations (number of fields in string).
Then basically we isolate the "last" field/value pair iterively, rearrange each one so field1=john turns into more sql friendly 'john' as field',
We reduce our counter and string each time we loop through until counter is 0. At which point we stop.
We then prepare a query with our value/field pairs and a 'select' string. Then execute and you get your values as fields
Credit
Dynamic Strings prepare/exec
Looping and stored procs
Simulating Split function
This answer from #Akina's comment works perfectly.
SUBSTRING_INDEX(SUBSTRING_INDEX(column, 'field2=', -1), ',', 1)
And WHERE accordingly.

SSRS add event data and manually updating SSRS Parameter

I am not sure if this is the best way, but it work for me. THe only downside is I need to add a random seconds of delay in order to use the current parameter.
Please look at my code below.
In SQL JOB of SSRS I modify step 1 of the SQL JOB. Prior to do exec addevent I update parameter value.
declare #xml as xml
set quoted_identifier ON
DECLARE db_cursor CURSOR FOR
SELECT C.[City]
FROM #FService C
GROUP BY C.[City]
ORDER BY C.[City]
DECLARE #CityLoop VARCHAR(50);
OPEN db_cursor;
FETCH NEXT FROM db_cursor INTO #CityLoop;
WHILE ##FETCH_STATUS = 0
BEGIN
-- THIS IS TO UPDATE XML
set #xml = (select Parameters
from ReportServer.dbo.Subscriptions sb
where sb.SubscriptionID = 'GUID')
set #xml.modify(' replace value of
(/ParameterValues/ParameterValue[2]/Value/text())[1] with
sql:variable("#CityLoop") ')
-- HERE WE UPDATE PARAMETER TABLE
update ReportServer.dbo.Subscriptions
set parameters = convert(nvarchar(max), #xml)
where SubscriptionID = 'GUID'
-- HERE IS THE ORIGINAL EXEC
exec [ReportServer].dbo.AddEvent #EventType='TimedSubscription', #EventData='GUID'
WAITFOR DELAY '00:00:30';
--RAISERROR (#DealLoop, 0, 1) WITH NOWAIT
FETCH NEXT FROM db_cursor INTO #CityLoop;
END;
CLOSE db_cursor;
DEALLOCATE db_cursor;
Notice we add a 30 seconds delay for the SSRS to pickup and execute the report.
is there a way to avoid random seconds and execute the report directly? or AddEvent is the only way to do this?
if I don't have a delay, the report will only run using the last #cityLoop parameter. that is why I add the delay then the job works properly by sending email every 30 seconds for each City that is available in the parameter query
I am using SSRS and SQL Server 2012
thank you for your help
I adapted this in order to solve the risky random wait times.
SQL Server central article[https://www.sqlservercentral.com/articles/dynamically-name-reportfile-export-in-ssrs]
It basically uses a wait for execution
-- WAIT for EXECUTION
set #bool1 = ISNULL((select eventdata from event where eventdata = #SBidEventData), '')
while #bool1 <> ''
begin
set #bool1 = ISNULL((select eventdata from event where eventdata = #SBidEventData), '')
if #bool1 = ''
begin
-- SET PATH AND PARAMETER TO THE WAY IT WAS
WAITFOR DELAY '000:00:07'
update Catalog
set Path = '/Report/Report1'
where ItemID = #ItemID
update Subscriptions
set Parameters = '<ParameterValues><ParameterValue><Name>Year</Name><Value>2019</Value></ParameterValue><ParameterValue><Name>Category</Name><Value>HONDA</Value></ParameterValue><ParameterValue><Name>Month</Name><Value>12</Value></ParameterValue></ParameterValues>'
where SubscriptionID = #SBidEventData
end
end

insert large volume of data in mysql

I want to insert atleast 500,000 fresh records in one shot. For which I have used while loop inside procedure. My query is working fine but it is taking alot of time to execute. So, I am looking for a solution using which I can make the process of insertion of large volume of data faster. I have gone through many links but I didn't find them resourceful.
Note:
I want to insert fresh data, not the data from existing table.
Syntactically the code provided below is correct and working. So please do not provide any suggestions regarding syntax.
Below is my procedure:
BEGIN
DECLARE x INT;
DECLARE start_s INT;
DECLARE end_s INT;
SET x = 0;
PREPARE stmt FROM
'insert into primary_packing(job_id, barcode, start_sn, end_sn, bundle_number, client_code, deno, number_sheets, number_pins, status)
values (?,?,?,?,?,?,?,?,?,?)';
SET #job_id = job_id, #barcode = barcode, #bundle_number = bundle_number, #client_code = client_code, #deno = deno, #number_sheets = number_sheets, #number_pins = number_pins, #status = 1;
set #start_sn = start_sn;
set #end_sn = end_sn;
WHILE x <= multiply DO
set #start_s = (start_sn+(diff*x));
set #end_s = ((end_sn-1)+(diff*x)+diff);
EXECUTE stmt USING #job_id, #barcode, #start_s, #end_s, #bundle_number, #client_code, #deno, #number_sheets, #number_pins ,#status;
SET x = x + 1;
END WHILE;
DEALLOCATE PREPARE stmt;
END
Use MYSQL command LOAD DATA INFILE to load your .csv files records to specific table.
For more information and eg. please reffer the following link
http://dev.mysql.com/doc/refman/5.1/en/load-data.html

Where clause not working in stored procedure, when working outside of it

We built a piece of dynamic sql that generates a wide view from data in long format. Seen here:
CREATE PROCEDURE `selectPivotedTermpoints`(studyid varchar(300))
BEGIN
SET SESSION group_concat_max_len = 10000000;
SET #psql = NULL;
SET #finalSQL = NULL;
SET #StudyID = studyid;
SELECT
GROUP_CONCAT(DISTINCT
CONCAT('SUM(CASE WHEN terminate = ''', REPLACE(Terminate,'''', ''''''), ''' THEN 1 ELSE 0 END) AS `', REPLACE(Terminate,'''', ''), '`')
) INTO #psql
FROM Dashboard
WHERE studyid = #StudyID
AND completion_status = 'terminate';
SET #finalSQL = CONCAT('
SELECT Sample_provider as Provider,
completion_status as `Status`,',
#psql,'
FROM Dashboard
WHERE studyid = ''', #StudyID, '''
AND completion_status = ''terminate''
GROUP BY Sample_provider');
SELECT #finalSQL;
PREPARE stmt FROM #finalSQL;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
END
When the sql is run as a query,(from SET to DEALLOCATE)setting #StudyID manually, we return a table with only the columns for that specific study(distinct Terminate as columns for only that study), however when the query is turned into a stored procedure and run it is generating a table with columns for all studies(all distinct Terminate as columns).
It appears that the first where clause (in the select group_concat) is being ignored when run as a stored procedure, but this is not the case when run as a simple query.
Stored procedure call:
selectPivotedTermpoints('bhp_03a');
Does anyone know why this is the case and / or how I can correct the issue?
I helped someone with a similar issue recently in another question; it confused us for quite a while. Change the parameter name to something else, I am guessing that WHERE is using it instead of the field in the table.
(You might be able to get away with Dashboard.studyid as well, but changing the parameter name will cause less confusion; and I am not positive how the query in #finalSQL would behave either.)

MySQL generates many new result-set

I've got MySQL procedure which I execute in MySQL Workbench. The problem is that it generates many new result-set, and GUI client show it (...many times) -
I found solution, which I can use instead just select clause to avoid generating result-sets. It looks like:
SELECT EXISTS(
SELECT ...
)
INTO #resultNm
Unfortunately it won't work with LIMIT ?,1 where ? is my variable 'i'. I also cant use just 'i' instead % because I am working on MySQL 5.1 (and limit clauses can't be done in other way). So my question - are other possibilities to hide result-sets?
CREATE PROCEDURE LOOPDOSSIERS(starter integer, finish integer)
BEGIN
DECLARE n INT DEFAULT 0;
DECLARE i INT DEFAULT 0;
DECLARE row_oid binary(16);
DECLARE row_str VARCHAR(256);
DECLARE row_old VARCHAR(256);
SET i=starter;
WHILE i<finish DO
-- SET row_str = ();
SET #row_str = 'select CAST(concat(d.prefixe_numero,d.numero) as CHAR) from courrier_concerne_dossier as x
join dossier as d on
d.oid = x.dossier_oid limit ?,1';
PREPARE stmt1 FROM #row_str;
EXECUTE stmt1 USING #i;
SET #row_oid = 'select x.courrier_oid
from courrier_concerne_dossier as x
join dossier as d on
d.oid = x.dossier_oid LIMIT ?,1';
PREPARE stmt2 FROM #row_oid;
EXECUTE stmt2 USING #i;
select dossiers_str from courrier_envoye where oid = row_oid into row_old;
update courrier_envoye set dossiers_str = CAST(CONCAT(row_str, " ", row_old) AS CHAR) where oid = row_oid;
SET i = i + 1;
END WHILE;
End;
;;
LIMIT without an ORDER BY clause doesn't have a well defined behavior. For your parameters to work in a sensible way, you'll need to order by something. The starter and finish variables aren't very meaningful at the moment, but it's currently not clear what they're intended to be.
I think you can probably accomplish this whole procedure in a single query using the syntax in this answer (also probably much faster). That probably won't work with LIMIT, but I'd highly recommend using a range of some kind in the where clause rather than a limit anyway.