MYSQL Stored Procedure to Iterate Json Array Data - mysql

I have a couple of columns that are json arrays that have datetime data like this:
["2017-04-18 11:05:00.000000"]
["2017-04-20 11:05:00.000000"]
["2017-04-22 11:05:00.000000"]
["2017-12-11 22:14:02.000000", "2017-12-11 22:14:08.000000", "2017-12-11 22:19:13.000000", "2017-12-11 22:20:44.000000", "2017-12-11 22:21:54.000000", "2017-12-11 22:23:09.000000"]
["2017-12-13 13:21:04.000000"]
["2017-12-14 13:10:44.000000", "2017-12-14 13:21:51.000000"]
["2017-12-15 13:27:21.000000", "2017-12-15 13:30:21.000000"]
["2017-12-16 15:15:22.000000"]
The goal is to parse out the datetime data and store it into a separate table from which I plan on doing some fun stuff. Currently, it only inserts the first record only, and it inserts it ~180000 times. My current code is:
BEGIN
DECLARE finished INTEGER DEFAULT 0;
DECLARE i INTEGER DEFAULT 0;
DECLARE usages VARCHAR(4000);
-- declare cursor for employee email
DEClARE curUsages
CURSOR FOR
SELECT associated_usages from usagesTbl where associated_usages not like '[]';
-- declare NOT FOUND handler
DECLARE CONTINUE HANDLER
FOR NOT FOUND SET finished = 1;
OPEN curUsages;
getUsages: LOOP
FETCH curUsages INTO usages;
IF finished = 1 THEN
LEAVE getUsages;
END IF;
WHILE i < JSON_LENGTH(usages) DO
INSERT INTO usagesTbl VALUES (JSON_EXTRACT(usages, CONCAT('$[',i,']')));
SET i = i + 1;
END WHILE;
SET i = 0;
END LOOP getUsages;
CLOSE curUsages;
END;
it seems that the while loop variable "i" is not increasing, and I am getting constantly stuck in the loop. The reason for me thinking this is that I pulled out the JSON_EXTRACT code and wrote this for testing:
set #i = 0;
select JSON_EXTRACT(associated_usages, CONCAT('$[',#i,']')) from usagesTbl where associated_usages not like '[]';
I can change the value of #i to whatever index I want and I get the right data. Im just stuck on why it doesn't work in the while loop during the stored procedure. Any help is greatly appreciated!

not sure if this could be the issue, but I see this:
DEClARE curUsages
Should be this:
DECLARE curUsages
Can it be the simple typo ? (the 1 for the L)

Fixed it! It somehow created an infinite loop that just kept on inserting data even when the stored proc said it was done running. I dropped and recreated the table, and changed the datatype of usages back from VARCHAR to json, and it worked like a charm.

Related

Duplicate problem in MySQL with autoincrement id

I have an issue with my database.
Table structure is:
Table name: sales
sale_id (autoincrement)
date (datetime)
total (decimal)
etc.
I have 2 computers, one is "the server" and the other is "the client", when I Insert in "sales" sometimes the database saves more than 1 record, it's an issue kind of random because one day could be normal just save 1 record as is but other day could save 2 or more duplicates.
My code is:
qry1.SQL.Text := 'SELECT * FROM sales '
+ 'WHERE sale_id = 1';
qry1.Open;
qry1.Insert;
qry1.FieldByName('date').AsDateTime := Date;
qry1.FieldByName('total').AsFloat := total;
qry1.Post;
saleId := qry1.FieldByName('sale_id').AsInteger;
qry1.Close;
// Code to save sale details using saleId.
I'm using Delphi 10.3 + ZeosLib 7.2.6-stable + MySQL 8.0
I opened the ports in the server so I have a direct connection to MySQL, I don't know what could be happening
Hope you can help me
Update----
Thanks for your kind answers,
#nbk Yes, I did it already.
#A Lombardo I used "where" to get just 1 record and then I use the query to insert the new one similar to use TTable but instead of load the hole table I just get one record and I can insert (qry.Insert),
#TheSatinKnight not only I get two records, sometimes I get 3 or more, but makes sense probably the keayboard is not working well and could send "enter" key more than once.
#fpiette, I will do ti right now.
I will keep you posted.
There are better ways to accomplish an insert than to open a TZTable and inserting on that open table.
As another approach, drop 2 TZQuery (NOT TZTable) on your form (which I'll assume is TForm1 - change as appropriate).
Assuming the name is ZQuery1 and ZQuery2.
Set its connection property the same as your TZTable, so it uses the same connector.
Set ZQuery1.SQL property to 'Insert into sales (date, total) values (:pdate, :ptotal)' //(w/o quotes)
Set ZQuery2.SQL property to 'select last_insert_id() as iddb'
now add the Function below to your form's Private delcaration
TForm1 = class(TForm)
ZQuery1: TZQuery; //added when dropped on form
ZQuery2: TZQuery;
private
{ Private declarations }
function AddNewSale(SaleDate: TDateTime; Total: Double): Integer; //add this line
public
{ Public declarations }
end;
and then add the following code to your form's methods
var
Form1: TForm1;
implementation
{$R *.dfm}
function TForm1.AddNewSale(SaleDate: TDateTime; Total: Double): Integer;
begin
ZQuery1.ParamByName('pdate').AsDateTime := SaleDate;
ZQuery1.ParamByName('ptotal').AsFloat := Total;
ZQuery1.ExecSQL; //*Execute* the Insert - Only "open" SQL that returns a result set
//now the record has been added to your DB
if ZQuery1.RowsAffected = 1 then //check to ensure it was inserted
begin
ZQuery2.Open;
try
Result := ZQuery2.FieldByName('iddb').AsInteger;
finally
ZQuery2.Close;
end;
end
else
result := -1;//indicate error by returning negative value
end;
now in the place you want to insert the record, simply call this function:
var
ReturnValue: Integer;
begin
ReturnValue := AddNewSale(Date, total);
if ReturnValue < 0 then
//error happened
else
begin
//Everything worked
end;
end;
Thanks again for all your kind answers.
At the end the problem was keyboard, It had a problem with "Enter" key, so when you pressed it, it send more than one pulsation so #TheSatinKnigh your approach was correct
#fpiette I created the log file and I figured out as you said the request had been executed twice or more.
I know maybe it is a silly thing for a programmer because I was disabling the button to late, sorry for that
#A Lombardo thanks for you code I like it better than mine I will use it

Converting delimited values into a comma separated list

My Webapp (PHP/jQuery/MySQL) has features which enable me to send out nicely formatted html email notifications to my customers based on certain events. The code works nicely and merges data from my Database into form fields although I need to enhance it to be able to provide enriched/localised/reformatted data in some circumstances.
For example:
- Provide date/time values in a user's own timezone
- Provide monetary values formatted to a user's locale
This requires me to do another pass of the email content to detect whether any fields remain unmerged before sending the email off to the user and if so, to format those field values appropriately before sending the email. Therefore what I want to do is extract a list of all delimited fieldnames from a table field value and return that list in comma delimited form.
I can already count how many times a delimeter appears
I can also find the position of the first delimeter
It looks like it would be easy to split the values if I was using the same opening and closing delimeters but because I have many email templates already in use, this isn't currently viable
I don't have any code for this yet. I'm just trying to avoid writing my own MySQL function to do this, by using existing MySQL functions if they are capable of doing this.
I've tried using various combinations of SUBSTRING, SUBSTRING_INDEX, LOCATE.
So what I need to be able to do is something like this:
SELECT msg_id, values_found_between(msg_content,"<",">") AS comma_delimited_list;
So for example, with source data of...
msg_id | msg_content
-------+------------
1 | The quick brown <fox> jumps over the lazy <dog>
2 | The quick brown fox jumps over the lazy dog
I can get a resulting recordset such as this:
msg_id | comma_seperated_list
-------+------------
1 | fox,dog
Alright, I had a crack and this seems to work well:
CREATE FUNCTION db.`FN_find_values_between`(`in_haystack` VARCHAR(10000), `in_opening_delimiter` VARCHAR(1),`in_closing_delimiter` VARCHAR(1)) RETURNS varchar(1000) CHARSET utf8
BEGIN
DECLARE numFoundOpen INT DEFAULT 0;
DECLARE numFoundClose INT DEFAULT 0;
DECLARE numFoundTarget INT DEFAULT 0;
DECLARE numCurrentIndex INT DEFAULT 0;
DECLARE strOutput VARCHAR(1000) DEFAULT "";
DECLARE numSearchFromPos INT DEFAULT 1;
DECLARE numCurrentCharPosStart INT DEFAULT 1;
DECLARE numCurrentCharPosEnd INT DEFAULT 1;
DECLARE strCurrentFieldname VARCHAR(50) DEFAULT "";
DECLARE numLength INT DEFAULT 0;
SET numFoundOpen=
(SELECT
ROUND ((LENGTH(in_haystack)- LENGTH( REPLACE (in_haystack, in_opening_delimiter, ""))) / LENGTH(in_opening_delimiter)));
SET numFoundClose=
(SELECT
ROUND ((LENGTH(in_haystack)- LENGTH( REPLACE (in_haystack, in_closing_delimiter, ""))) / LENGTH(in_closing_delimiter)));
IF (numFoundOpen=numFoundClose) THEN
SET numFoundTarget=numFoundOpen;
END IF;
WHILE numCurrentIndex < numFoundTarget DO
SET numCurrentIndex=numCurrentIndex+1;
SET numCurrentCharPosStart = LOCATE(in_opening_delimiter, in_haystack, numSearchFromPos);
SET numCurrentCharPosEnd = LOCATE(in_closing_delimiter, in_haystack, numSearchFromPos);
SET numLength=1+(numCurrentCharPosEnd-numCurrentCharPosStart);
SET strCurrentFieldname=SUBSTRING(in_haystack,numCurrentCharPosStart,numLength);
SET strOutput=CONCAT(strOutput,strCurrentFieldname,",");
SET strCurrentFieldname="";
SET numSearchFromPos=numCurrentCharPosEnd+1;
END WHILE;
IF (strOutput <> "") THEN
SET strOutput=LEFT(strOutput,LENGTH(strOutput)-1);
END IF;
RETURN strOutput;
END;
As per the code above, I managed to write my own MySQL function to do this.

Generating JSON with cursor in MySQL

What is the best way to extract results from a cursor to a JSON format using version 5.7?
I need the JSON to look something like this:
{
"2018V": {
"students": {
"1": {
"fName": "John",
"dob": "4/8/1998",
"classes": []
},... and more student ID's
}
},... and more semesters
}
I was advised to use only one cursor to fetch a lot of data instead of three or four nested ones, so the cursor looks like this:
DECLARE done BOOLEAN DEFAULT FALSE;
DECLARE infoJson JSON DEFAULT '{}';
DECLARE currSemester CHAR(10);
DECLARE currStudentID INT;
DECLARE currStudentName VARCHAR(140);
DECLARE currStudentSSN VARCHAR(10);
DECLARE currCourseNumber CHAR(10);
DECLARE curStudentsInfo CURSOR FOR SELECT `StudentCourses`.semester, `Students`.studentID, `Students`.studentName,`Students`.studentSSN, `StudentCourses`.courseNumber
FROM `StudentCourses`
INNER JOIN `Students` ON `StudentCourses`.studentID = `Students`.studentID
ORDER BY `StudentCourses`.semester;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done := TRUE;
OPEN curStudentsInfo;
infoLoop: LOOP
FETCH curStudentsInfo INTO currSemester, currStudentID, currStudentName, currStudentSSN, currCourseNumber;
IF done THEN
CLOSE curStudentsInfo;
LEAVE infoLoop;
END IF;
-- Code here
END LOOP infoLoop;
The data that I'm working with looks something like this:
Here are four people in total and only two semesters on record but there's a lot more in the database.
semester studentID studentName studentSSN courseNumber
--------------------------------------------------------
2018H 3 Birkir Geir 2001979230 GSF2A3U
2018H 3 Birkir Geir 2001979230 STÆ303
2018V 5 Reynir 1508984089 STÆ203
2018V 6 Róbert Ingi 1707001234 STÆ603
...
Now what kind of approach should I take here? I've been trying to figure this out now for quite some time and I just find it so hard to code this kind of thing in MySQL. I don't know exactly what to use. I feel like everything I come up with would be a complete mess.

Mysql recursive procedure

I have a table name css_diectory with 3 columns directory_id, directory-name and root_directory. I am storing hierarchical directory structure in this table. I have written a procedure to retrieve all the descendants given the directory_id. But it doesn't work. Can anyone please help me.
Here is the procedure
CREATE PROCEDURE getDescendants
(IN rootId varchar(32), INOUT desecendantsFolderId varchar(3200))
BEGIN
DECLARE endOfRecord INTEGER DEFAULT 0;
DECLARE folderId varchar(32) DEFAULT "";
DECLARE folderName varchar(32) DEFAULT "";
DECLARE folderCursor CURSOR FOR
Select directory_id, directory_name from css_directory where root_directory=rootId;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET endOfRecord = 1;
OPEN folderCursor;
getFolderId: LOOP
FETCH folderCursor INTO folderId, folderName;
IF endOfRecord = 1 THEN
LEAVE getFolderId;
END IF;
Call getDescendants(folderId,desecendantsFolderId);
SET desecendantsFolderId = CONCAT(folderId,",",desecendantsFolderId);
call getDescendants(folderId,desecendantsFolderId);
END LOOP getFolderId;
END
Edit: The output of this procedure is always a null set. It does not produce any error
I'm not 100% sure I follow what you're doing, but it looks like there's a problem here:
SET desecendantsFolderId = CONCAT(folderId,",",desecendantsFolderId);
When any argument to CONCAT() is null, then the return value is null. Presumably descendantsFolderId is initially null, and if so, I don't see where that would change.
There are several ways to remedy this, but here is one of them:
SET desecendantsFolderId = NULLIF(CONCAT_WS(",",folderId,desecendantsFolderId),"");
CONCAT_WS() is like CONCAT(), except the first argument is used as a separator amd null arguments beyond the first one are disregarded, and empty string is returned if all subsequent arguments are null. NULLIF() is probably not technically needed, based on the rest of the code, but will ensure that the final result of CONCAT_WS() will be turned back to null if the input args are indeed all null.

Create a MySQL procedure variable which is initialized when not provided

I need to create a MYSQL procedure where the procedure accepts several parameters and works with them. However, in the case where it is not present, the parameter variables pick some 'default' values and continue. Similar to how the pseudo-function-overload is handled in PHP.
This code is what I could come up with.
CREATE PROCEDURE PROC_INS_CONTENT_TEST(IN DATA_VAL LONGTEXT)
BEGIN
IF (DATA_VAL IS NULL) THEN SET DATA_VAL='DEFAULT'; END IF;
INSERT INTO CONTENT_TEST (DATA) VALUES (DATA_VAL);
END
And this code does not work the way I want it to behave. Is there a way to assign the default value to the variable right when the parameter is declared?
Yes there is.
If you want to set default value on variable on your function/procedure you can do this:
CREATE PROCEDURE PROC_INS_CONTENT_TEST(IN DATA_VAL LONGTEXT)
BEGIN
DECLARE my_data_double DOUBLE DEFAULT 0;
DECLARE my_data_varchar VARCHAR(20) DEFAULT 'default_value';
DECLARE DATA_VAL LONGTEXT DEFAULT 'default_value';
DECLARE my_data_integer INT DEFAULT 0;
END
Or if you want to assign value you can do something like:
SET my_data_value = 12345;
inside your function/procedure.