MySQL Variable Assignment via Procedure Not Working Correctly - mysql

In the code below, I'm trying go through the results of endDateTable row by row, comparing the current row's endDate to the previous row's endDate. If there has been any change since the previous, we increment #revisionNum. However, upon populating the new table, all of the #revisionNum entries are 0. What am I doing wrong?
NOTE: I'm using prepared statements in this manner since doing a straightforward SELECT into a variable gives a syntax error due to the LIMIT clause not allowing a variable in our version of MySQL.
BEGIN
DECLARE _currentEndDate DATETIME DEFAULT now();
DECLARE _priorEndDate DATETIME DEFAULT now();
SET #ResultsCount = (SELECT COUNT(*) FROM mainTable);
SET #j = 0;
WHILE #j < #ResultsCount DO
SET #revisionNum = 0;
/*CURRENT END DATE*/
SET #appResultQueryCurrent = CONCAT('
SELECT
end_date
INTO _currentEndDate
FROM endDateTable
LIMIT ', #j, ', 1'
);
PREPARE currentQueryStmt FROM #appResultQueryCurrent;
EXECUTE currentQueryStmt;
/*PREVIOUS END DATE*/
SET #appResultQueryPrior = CONCAT('
SELECT
end_date
INTO _priorAppEndDate
FROM endDateTable
LIMIT ', IF(#j = 0, 0, #j - 1), ', 1'
);
PREPARE priorQueryStmt FROM #appResultQueryPrior;
EXECUTE priorQueryStmt;
SET #revisionNum = IF(
#j = 0 OR (_currentEndDate = _priorEndDate),
#revisionNum,
IF(
_currentEndDate != _priorEndDate,
#revisionNum + 1,
#revisionNum
)
);
INSERT INTO finalTable (RevisionNum)
SELECT
#revisionNum AS RevisionNum
FROM endDateTable;
SET #j = #j +1;
END WHILE;
END $$

You don't need a loop, you can use INSERT INTO ... SELECT ..., incrementing the variable in the select query.
You also need an ORDER BY criteria to specify how to order the rows when comparing one row to the previous row.
INSERT INTO finalTable (RevisionNum, otherColumn)
SELECT revision, otherColumn
FROM (
SELECT IF(end_date = #prev_end_date, #revision, #revision := #revision + 1) AS revision,
#prev_end_date := end_date,
otherColumn
FROM endDateTable
CROSS JOIN (SELECT #prev_end_date := NULL, #revision := -1) AS vars
ORDER BY id) AS x
DEMO

The offset value in the LIMIT clause is tenuous without an ORDER BY.
Without an ORDER BY clause, MySQL is free to return results in any sequence.
There is no guarantee that LIMIT 41,1 will return the row before LIMIT 42,1, or that it won't return the exact same row as LIMIT 13,1 did.
(A table in a relational database represents an unordered set of tuples, there is no guaranteed "order" or rows in a table.)
But just adding ORDER BY to the queries isn't enough to fix the Rube-Goldberg-esque rigmarole.
In the code shown, it looks like each time through the loop, we're inserting a copy of endDateTable into finalTable. If that's 1,000 rows in endDateTable, we're going to get 1,000,000 rows (1,000 x 1,000) inserted into finalTable. Not at all clear why we need so many copies.
Given the code shown, it's not clear what the objective is. Looks like we are conditionally incrementing revisionNum, the end result of which is the highest revision num. Just guessing here.
If there is some kind of requirement to do this in a LOOP construct, within a procedure, I'd think we'd do a cursor loop. And we can use procedure variables vs user-defined variables.
Something along these lines:
BEGIN
DECLARE ld_current_end_date DATETIME;
DECLARE ld_prior_end_date DATETIME;
DECLARE li_done INT;
DECLARE li_revision_num INT;
DECLARE lcsr_end_date CURSOR FOR SELECT t.end_date FROM `endDateTable` t ORDER BY NULL;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET li_done = TRUE;
SET li_done = FALSE;
SET li_revision_num = 0;
OPEN lcsr_end_date;
FETCH lcsr_end_date INTO ld_current_end_date;
SET ld_prior_end_date = ld_current_end_date;
WHILE NOT li_done DO
SET li_revision_num = li_revision_num + IF( ld_current_end_date <=> ld_prior_end_date ,0,1);
SET ld_prior_end_date := ld_current_end_date;
FETCH lcsr_end_date INTO ld_current_end_date;
END WHILE;
CLOSE lcsr_end_date;
INSERT INTO `finalTable` (revisionnum) VALUES (li_revision_num);
END $$
Note the "order by" clause on the SELECT, its not clear what the rows should be ordered on, so we're using a literal as a placeholder.
As the end result, we insert a single row into finalTable.
Again, it's not clear what the code in the question is supposed to achieve, but doing a cursor loop across ordered rows would be much more efficient than a bazillion dynamic SQL executions fetching individual rows.

Related

mysql concat_ws with the result of a custom function prevents like '%str%' from working

So I have the following sql that works:
SELECT CONCAT_WS('',`brand`,`pattern`,`product_code`,`size`,`normal_price`,`sale_price`,`text_special`,`load_index`,`speed_index`,`id`) `all_columns` FROM baz_tyres
HAVING (`all_columns` LIKE '%con%')
ORDER BY brand asc
LIMIT 0, 10
This works fine returning any record that contains the search string in any column such as "ContinentalContiCrossContact® LX21549340225/65R17."
But then I also wanted to be able to match just the numerical value from size so I added the custom function I wound here:
How to get only Digits from String in mysql?
DELIMITER $$
CREATE FUNCTION `ExtractNumber`(in_string VARCHAR(50))
RETURNS INT
NO SQL
BEGIN
DECLARE ctrNumber VARCHAR(50);
DECLARE finNumber VARCHAR(50) DEFAULT '';
DECLARE sChar VARCHAR(1);
DECLARE inti INTEGER DEFAULT 1;
IF LENGTH(in_string) > 0 THEN
WHILE(inti <= LENGTH(in_string)) DO
SET sChar = SUBSTRING(in_string, inti, 1);
SET ctrNumber = FIND_IN_SET(sChar, '0,1,2,3,4,5,6,7,8,9');
IF ctrNumber > 0 THEN
SET finNumber = CONCAT(finNumber, sChar);
END IF;
SET inti = inti + 1;
END WHILE;
RETURN CAST(finNumber AS UNSIGNED);
ELSE
RETURN 0;
END IF;
END$$
DELIMITER ;
Now that I have this function I also want to concat the resulting number and use it for the search.
So I added ExtractNumber(size) into the congat
SELECT CONCAT_WS('',ExtractNumber(`size`),`brand`,`pattern`,`product_code`,`size`,`normal_price`,`sale_price`,`text_special`,`load_index`,`speed_index`,`id`) `all_columns` FROM baz_tyres
HAVING (`all_columns` LIKE '%con%')
ORDER BY brand asc
LIMIT 0, 10
When the function is involved the like search fails to find any matches. However if I change the havving to a where condition checking for a specific brand name...
SELECT CONCAT_WS('',ExtractNumber(`size`),`brand`,`pattern`,`product_code`,`size`,`normal_price`,`sale_price`,`text_special`,`load_index`,`speed_index`,`id`) `all_columns` FROM baz_tyres
WHERE (`brand` LIKE '%con%')
ORDER BY brand asc
LIMIT 0, 10
Then I can see that indeed the concat with the function inside does actually work returning "2147483647ContinentalVancoFourSeason 20473361205/7...." but performing a like on this resulting string doesn't match when it should.
Some columns have special chars and I have tried casting the function result to utf8 and it had no effect.
Any ideas why I cant do a like on this concat string?
UPDATE:
It's working now..
I had to put the conversion inside the iterator function itself.
RETURN CAST(finNumber AS CHAR);
For some reason convert or cast with the function inside would produce the correct result but still wouldn't allow a like comparison to match afterwards.
So now the following query
SELECT *,CONCAT_WS('',ExtractNumber_CHAR(`size`),`brand`,`pattern`,`product_code`,`size`,`normal_price`,`sale_price`,`text_special`,`load_index`,`speed_index`,`id`) `all_columns`
FROM `baz_tyres`
HAVING (`all_columns` LIKE '%nat%' )
ORDER BY `brand` DESC
LIMIT 0,10
Produces the desired result however a NEW problem has now appeared that is really strange..
If I do the exact same query but order by ASC instead of DESC then I get 0 results.
Really strange that order affects weather any results are returned. If the extract number function is removed ordering either way returns results.
When I put the function back in I can only get results when order by desc.
Can anyone tell me why this odd behavior would occur?
Snytax of using having clause is this
SELECT column_name(s)
FROM table_name
WHERE condition
GROUP BY column_name(s)
HAVING condition
ORDER BY
Having is used with aggregated function after using group by
Try to change query by using group by else use where instead having

SQL Server T-SQL breaking a string into a temp table for a join

We have a SQL Server Scalar Function and part of the process is to take one of the input values and do the following
'inputvalue'
Create a table variable and populate with the following rows
inputvalue
inputvalu
inputval
inputva
inputv
input
inpu
inp
Then this table is joined to a query, ordered by len of the inputvalue desc and returns the top 1. The actual code is here
DECLARE #Result NVARCHAR(20);
DECLARE #tempDialCodes TABLE (tempDialCode NVARCHAR(20));
DECLARE #counter INT = LEN(#PhoneNumber);
WHILE #counter > 2
BEGIN
INSERT INTO #tempDialCodes(tempDialCode) VALUES(#PhoneNumber);
SET #PhoneNumber = SUBSTRING(#PhoneNumber, 1, #counter - 1);
SET #counter = #counter - 1;
END
SET #Result = (SELECT TOP 1 [DialCodeID]
FROM DialCodes dc JOIN #tempDialCodes s
ON dc.DialCode = s.tempDialCode
ORDER BY LEN(DialCode) DESC);
RETURN #Result
It works fine but I am asking if there is a way to replace the while loop and somehow joining to the inputvalue to get the same result. When I say it works fine, it's too dam slow but it does work.
I'm stumped on how to break up this string without using a loop and to a table variable but my warning light tells me this is not efficient for running against a table with a million rows.
Are you familiar with tally tables? The speed difference can be incredible. I try to replace every loop with a tally table if possible. The only time I haven't been able to so far is when calling a proc from within a cursor. If using this solution I would recommend a permanent dbo.Tally table with a sufficiently large size rather than recreating every time in the function. You will find other uses for it!
declare #PhoneNumber nvarchar(20) = 'inputvalue';
declare #tempDialCodes table (tempDialCode nvarchar(20));
--create and populate tally table if you don't already a permanent one
--arbitrary 1000 rows for demo...you should figure out if that is enough
--this a 1-based tally table - you will need to tweak if you make it 0-based
declare #Tally table (N int primary key);
insert #Tally
select top (1000) row_number() over (order by o1.object_id) from sys.columns o1, sys.columns o2 order by 1;
--select * from #Tally order by N;
insert #tempDialCodes
select substring(#PhoneNumber, 1, t.N)
from #Tally t
where t.N between 3 and len(#PhoneNumber)
order by t.N desc;
select *
from #tempDialCodes
order by len(tempDialCode) desc;

Insert sequential numbers based on another field - MySQL

There is a similar question
Insert sequential number in MySQL
I want to insert sequential numbers to the table, but based on another field. I have two columns page_numner and parent, so the rows with same parent should have page_number as consequtive numbers. If parent changes, the page should start from 1 again and increase by one.
I was thinking to use smth like this
SELECT #i:=0;
SELECT #p:=0;
UPDATE my_table AS t SET page_number = CASE
WHEN #p = t.`parent` THEN #i:=#i+1
ELSE 1 -- assign current parent to #p ??
END
but, it cant figure out how to assign the new parent into #p for the else case.
Please note, that I am trying to achieve this with pure mysql (if possible of course)
Thanks
You can do what you want with this code:
set #p := -1;
set #i := 0;
UPDATE my_table t
SET page_number = (CASE WHEN #p = t.`parent` THEN #i := #i+ 1
WHEN (#p := t.parent) = NULL THEN NULL -- never happens
ELSE #i := 1
END)
ORDER BY t.parent;
Unfortunately, MySQL doesn't allow both ORDER BY and JOIN in the same UPDATE query. If it did, you could initialize the variables in the query.
Note the second condition just does the assignment. = NULL never returns TRUE.

Loop in a MySql stored procedure error code 1064

I'm reasonably new to writing MySQL stored procedures, and I'm trying to get my head around using loops and variables.
I have a table with a column called STAT_NAME, where several rows could have the same value for that column. I want to create a temporary column which numbers the occurrences of each STAT_NAME value, so for example first time STAT_NAME is "stat A", set STAT_COUNT to 1, 2nd time 2 etc. And then start again at 1 for "stat B" and so on.
I've got as far as creating the the temporary table with an empty column called STAT_COUNT, and sorted the table by STAT_NAME.
I've then tried to loop through all the rows and set STAT_COUNT accordingly. However, I get the following error:
Error Code: 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 '#counting,1) THEN SET #STAT_NAME_CURR = (SELECT #STAT_NAME FROM tmp1 LIMIT #'
Is anyone able to explain why what I've written is not OK? I think it would be fine if I used a system variable instead of #counting, and I don't understand why. I've tried googling the problem but not getting anywhere!
cheers.
DROP PROCEDURE IF EXISTS collate_stats;
delimiter //
CREATE PROCEDURE collate_stats()
begin
create temporary table tmp1 engine=memory
SELECT STAT_NAME, STAT, '' AS STAT_COUNT
FROM performance_testing_stats.20131014
ORDER BY STAT_NAME;
-- stat name that we are currently looking at
SET #STAT_NAME_CURR := "";
-- number of times this name has been found so far
SET #STAT_NAME_COUNT := 0;
-- Use to loop through all rows
SET #n := (SELECT COUNT(*) FROM tmp1);
-- Row reached so far
SET #counting := 0;
WHILE #counting < #n DO
-- IF #STAT_NAME_CURR is not equal to the STAT_NAME for the current row,
-- THEN set #STAT_NAME_CURR to STAT_NAME value and reset #STAT_NAME_COUNT
-- ELSE just increment #STAT_NAME_COUNT
IF #STAT_NAME_CURR <> (SELECT #STAT_NAME FROM tmp1 LIMIT #counting,1) THEN
SET #STAT_NAME_CURR = (SELECT #STAT_NAME FROM tmp1 LIMIT #counting,1);
SET #STAT_NAME_COUNT = 0;
ELSE
SET #STAT_NAME_COUNT = #STAT_NAME_COUNT + 1;
END IF;
-- Set STAT_COUNT for current row to value of #STAT_NAME_COUNT
UPDATE tmp1 SET STAT_COUNT = #STAT_NAME_COUNT WHERE STAT_NAME = #STAT_NAME_CURR AND STAT_COUNT = '' LIMIT 1;
-- Move to next row
SET #counting = #counting + 1;
END WHILE;
select * from tmp1;
END //
delimiter ;
You might want to look into using cursors instead of the while loop.
However, you could probably accomplish what you are trying to do with a simple GROUP BY query:
SELECT STAT_NAME, STAT, COUNT(1) as STAT_COUNT
FROM performance_testing_stats.20131014
GROUP BY STAT_NAME, STAT
ORDER BY STAT_NAME, STAT;
Unless I'm missing something about what you are doing.

COALESCE truncating concatenation output

In a recent post Sql server rtrim not working for me, suggestions?, I got some good help getting a csv string out of a select query. It's behaving unexpectedly though, and I can't find any similar examples or documentation on it. The query returns 802 records without the coalesce statement, as a normal select. With the coalesce, I'm getting back just 81. I get this same result if I output to text, or output to file. This query returns 800+ rows:
declare #maxDate date = (select MAX(TradeDate) from tblDailyPricingAndVol)
select p.Symbol, ','
from tblDailyPricingAndVol p
where p.Volume > 1000000 and p.Clse <= 40 and p.TradeDate = #maxDate
order by p.Symbol
But when I attempt to concatenate those values, many are missing:
declare #maxDate date = (select MAX(TradeDate) from tblDailyPricingAndVol)
declare #str VARCHAR(MAX)
SELECT #str = COALESCE(#str+',' ,'') + LTRIM(RTRIM((p.Symbol)))
FROM tblDailyPricingAndVol p
WHERE p.Volume > 1000000 and p.Clse <= 40 and p.TradeDate = #maxDate
ORDER by p.Symbol
SELECT #str
This should be working fine, however here is how I would do it:
DECLARE #str VARCHAR(MAX) = '';
SELECT #str += ',' + LTRIM(RTRIM(Symbol))
FROM dbo.tblDailyPricingAndVol
WHERE Volume > 1000000 AND Clse <= 40 AND radeDate = #maxDate
ORDER by Symbol;
SET #str = STUFF(#str, 1, 1, '');
To determine whether the string is complete, stop looking at the output in Management Studio. This is always going to be truncated if you exceed the number of characters Management Studio will show. You can run a couple of tests to check the variable without inspecting it in its entirety:
A. Compare the datalength of the individual parts to the datalength of the result.
SELECT SUM(DATALENGTH(LTRIM(RTRIM(Symbol)))) FROM dbo.tblDailyPricingAndVol
WHERE ...
-- concatenation query here
SELECT DATALENGTH(#str);
-- these should be equal or off by one.
B. Compare the end of the variable to the last element in the set.
SELECT TOP 1 Symbol FROM dbo.tblDailyPricingAndVol
WHERE ...
ORDER BY Symbol DESC;
-- concatenation query here
SELECT RIGHT(#str, 20);
-- is the last element in the set represented at the end of the string?