A better solution to replace an old cursor? - sql-server-2008

I have two tables like this
table1 sorted by paymentid, paydate:
paymentid paydate amount
1 20120101 100
1 20120101 150
1 20120101 150
2 20120115 100
2 20120115 100
...
table2 sorted by paymentid, paydate:
paymentid paydate pay1 pay2 pay3....base on the position from table1...up to pay20
1 20120101 null null null
2 20120115 null null null
...
after update, table2 should be like this
1 20120101 100 100 150 null.....
2 20120115 100 100 null null null.....
The possible solutions i can come up so far are:
1, a temp table for looping thru table1
2, a cursor...current solution:
declare #paymentid int
declare #paydate char(10)
declare #amount money
declare #count int
declare #saveid int
declare #savedate char(10)
delcare cur cursor for select paymentid, paydate, amount from table1 order by paymentid, paydate
open cur
fetch next from cur into #paymentid,#paydate,#amount
while ##fetch_status = 0
if #saveid <> #paymentid or #savedate <> #paydate
set #count = 1
set #saveid = #paymentid
set #savedate = #paydate
else set #count = #count + 1
if #count = 1
update table2
set pay1 = #amount
where paymentid = #paymentid and paydate = #paydate
if #count = 2
update table2
set pay2 = #amount
...
fetch next
...
Any suggestion will be appreciated.

Try this
SQL Fiddle
declare #Test Table (paymentid int,
paydate varchar(25), amount int)
INSERT INTO #Test
SELECT 1,'20120101',100
UNION ALL
SELECT 1,'20120101',150
UNION ALL
SELECT 1,'20120101',150
UNION ALL
SELECT 2,'20120115',100
UNION ALL
SELECT 2,'20120115',100
--INSERT into Table2 //insert here
SELECT * FROM (
SELECT *, 'pay' + CAST(Row_Number() OVER (Partition by paymentid,
paydate order by amount) AS VARCHAR) PayCol
FROM #Test
) V
PIVOT
(
MAX(Amount) For PayCol IN
(pay1,pay2,pay3,pay4, pay5, pay6, pay7, pay8, pay9, pay10,
pay11,pay12,pay13,pay14, pay15, pay16, pay17, pay18, pay19, pay20)
) As P

Related

Cursor in Mysql not working in Mysql 5.57

Cursor in Mysql not working in Mysql 5.57
I converted SQL query into MySQL query, SQL query give me records, but in Mysql query is doesn't provide any records and neither give any error
//Following is my SQL query
USE [Trackdb]
GO
/****** Object: StoredProcedure [dbo].[GetRecords] Script Date: 06-11-2019 09:48:05 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE [dbo].[GetRecords]
AS
BEGIN
DECLARE #Date VARCHAR(15)
create table #TempHotel
(
CatID Varchar(50),
Cnt INT,
Date Date
)
create table #TempRestaurant
(
CatID Varchar(50),
Cnt INT,
Date Date
)
DECLARE Track_CURSOR CURSOR
LOCAL FORWARD_ONLY FOR
SELECT Distinct(Convert(date,ms_date)) FROM tbltrackingrtt WHERE ms_date > '2017-11-01' AND heirarchy LIKE '%*145*%' AND category <> 0 AND category <> 145
OPEN Track_CURSOR
FETCH NEXT FROM Track_CURSOR INTO #Date
WHILE ##FETCH_STATUS = 0
BEGIN
--PRINT 'EMP_ID: ' + #Date --+ ' EMP_NAME '+#EMP_NAME +' EMP_SALARY ' +CONVERT(NVARCHAR(MAX),#EMP_SALARY) + ' EMP_CITY ' +#EMP_CITY
INSERT INTO #TempHotel
SELECT TOP 100 Category AS CatID, Count(id) Cnt, convert(Date,ms_date) Date FROM tbltrackingrtt
WHERE heirarchy LIKE '%*145*%' AND category <> 0 AND category <> 145 AND CONVERT(DATE,ms_date) = #Date
GROUP BY Category, convert(Date,ms_date)
ORDER BY convert(Date,ms_date) ASC, Count(id) DESC
INSERT INTO #TempRestaurant
SELECT TOP 100 Category AS CatID, Count(id) Cnt, convert(Date,ms_date) Date FROM tbltrackingrtt
WHERE heirarchy LIKE '%*169*%' AND category <> 0 AND category <> 169 AND CONVERT(DATE,ms_date) = #Date
GROUP BY Category, convert(Date,ms_date)
ORDER BY convert(Date,ms_date) ASC, Count(id) DESC
FETCH NEXT FROM Track_CURSOR INTO #Date
END
SELECT * FROM #TempHotel
SELECT * FROM #TempRestaurant
CLOSE Track_CURSOR
DEALLOCATE Track_CURSOR
DROP table #TempHotel
DROP table #TempRestaurant
END
// Following is my Mysql query
CREATE DEFINER=`root`#`localhost` PROCEDURE `GetRecords`()
BEGIN
DECLARE NOT_FOUND INT DEFAULT 0;
DECLARE v_Date VARCHAR(15);
DECLARE Track_CURSOR
CURSOR FOR
SELECT Distinct CAST(ms_date as Date) FROM tbltrackingrtt WHERE ms_date > '2017-11-01' AND heirarchy LIKE '%*145*%' AND category <> 0 AND category <> 145;
create temporary table TempHotel
(
CatID Varchar(50),
Cnt INT,
Date Date
);
create temporary table TempRestaurant
(
CatID Varchar(50),
Cnt INT,
Date Date
);
OPEN Track_CURSOR;
FETCH Track_CURSOR INTO v_Date;
WHILE NOT_FOUND = 0
DO
-- PRINT 'EMP_ID: ' + #Date --+ ' EMP_NAME '+#EMP_NAME +' EMP_SALARY ' +CONVERT(NVARCHAR(MAX),#EMP_SALARY) + ' EMP_CITY ' +#EMP_CITY
INSERT INTO TempHotel
SELECT Category AS CatID, Count(id) Cnt, CAST(ms_date as Date) as Date FROM tbltrackingrtt
WHERE heirarchy LIKE '%*145*%' AND category <> 0 AND category <> 145 AND CAST(ms_date as Date) = v_Date
GROUP BY Category, CAST(ms_date as Date)
ORDER BY CAST(ms_date as Date) ASC, Count(id) DESC LIMIT 100;
select TempHotel;
INSERT INTO TempRestaurant
SELECT Category AS CatID, Count(id) Cnt, CAST(ms_date as Date) as Date FROM tbltrackingrtt
WHERE heirarchy LIKE '%*169*%' AND category <> 0 AND category <> 169 AND CAST(ms_date as Date) = v_Date
GROUP BY Category, CAST(ms_date as Date)
ORDER BY CAST(ms_date as Date) ASC, Count(id) DESC LIMIT 100;
FETCH Track_CURSOR INTO v_Date;
END WHILE;
SELECT * FROM TempHotel;
SELECT * FROM TempRestaurant
CLOSE;
DROP table TempHotel;
DROP table TempRestaurant;
END
Anyone help me on this, I don't what I am doing wrong here, is there is any problem in MySQL cursor
Apart from the syntax error pointed out by #solarflare the only thing that appears to be wrong with this code is the lack of a handler you should review https://dev.mysql.com/doc/refman/8.0/en/cursors.html and https://dev.mysql.com/doc/refman/5.7/en/handler.html (which you should know about for error handling in mysql)
drop procedure if exists p;
drop temporary table if exists temphotel,temprestaurant;
drop table if exists tbltrackingrtt;
create table tbltrackingrtt
(id int auto_increment primary key,ms_date date,heirarchy varchar(20),category int);
insert into tbltrackingrtt (ms_date,heirarchy,category) values
('2017-11-02','*145*',10), ('2017-11-02','*145*',10),
('2017-11-02','*169*',10), ('2017-11-02','*145*',10);
delimiter $$
CREATE PROCEDURE p()
BEGIN
DECLARE NOT_FOUND INT DEFAULT 0;
DECLARE v_Date VARCHAR(15);
DECLARE Track_CURSOR
CURSOR FOR
SELECT Distinct CAST(ms_date as Date)
FROM tbltrackingrtt
WHERE ms_date > '2017-11-01' AND heirarchy LIKE '%*145*%' AND category <> 0 AND category <> 145;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET not_found = TRUE;
create temporary table TempHotel
(
CatID Varchar(50),
Cnt INT,
Date Date
);
create temporary table TempRestaurant
(
CatID Varchar(50),
Cnt INT,
Date Date
);
OPEN Track_CURSOR;
FETCH Track_CURSOR INTO v_Date;
WHILE NOT_FOUND = 0 DO
-- PRINT 'EMP_ID: ' + #Date --+ ' EMP_NAME '+#EMP_NAME +' EMP_SALARY ' +CONVERT(NVARCHAR(MAX),#EMP_SALARY) + ' EMP_CITY ' +#EMP_CITY
INSERT INTO TempHotel
SELECT Category AS CatID, Count(id) Cnt, CAST(ms_date as Date) as Date
FROM tbltrackingrtt
WHERE heirarchy LIKE '%*145*%' AND category <> 0 AND category <> 145 AND CAST(ms_date as Date) = v_Date
GROUP BY Category, CAST(ms_date as Date)
ORDER BY CAST(ms_date as Date) ASC, Count(id) DESC LIMIT 100;
select * from TempHotel;
INSERT INTO TempRestaurant
SELECT Category AS CatID, Count(id) Cnt, CAST(ms_date as Date) as Date
FROM tbltrackingrtt
WHERE heirarchy LIKE '%*169*%' AND category <> 0 AND category <> 169 AND CAST(ms_date as Date) = v_Date
GROUP BY Category, CAST(ms_date as Date)
ORDER BY CAST(ms_date as Date) ASC, Count(id) DESC LIMIT 100;
FETCH Track_CURSOR INTO v_Date;
END WHILE;
SELECT * FROM TempHotel;
SELECT * FROM TempRestaurant
CLOSE;
DROP table TempHotel;
DROP table TempRestaurant;
END $$
delimiter ;
call p();
+-------+------+------------+
| CatID | Cnt | Date |
+-------+------+------------+
| 10 | 3 | 2017-11-02 |
+-------+------+------------+
1 row in set (0.27 sec)
+-------+------+------------+
| CatID | Cnt | Date |
+-------+------+------------+
| 10 | 3 | 2017-11-02 |
+-------+------+------------+
1 row in set (0.29 sec)
+-------+------+------------+
| CatID | Cnt | Date |
+-------+------+------------+
| 10 | 1 | 2017-11-02 |
+-------+------+------------+
1 row in set (0.30 sec)

Rewrite Stored Procedure

I have the next stored procedure which inserts values into 2 tables. To the 2nd table I insert id's of 2 last inserts from 1st table
However, I would like to rewrite it with one query instead of using temp table and while.
CREATE PROCEDURE CurrencyExhange
AS
DECLARE #TmpTable Table
(
ID int IDENTITY(1,1) NOT NULL PRIMARY KEY,
BillID int,
Amount decimal,
Rate decimal,
Date date
)
INSERT INTO #TmpTable
SELECT T.[BillID]
,[Amount]
,CR.Rate
,CR.Date
FROM [FinanceLabkovich].[dbo].[Transactions] T
JOIN [FinanceLabkovich].[dbo].Bills B ON B.BillID = T.BillID
JOIN [FinanceLabkovich].[dbo].Currencies C ON C.CurrencyID=B.CurrencyID
JOIN [FinanceLabkovich].[dbo].CurrencyRates CR ON CR.CurrencyRateID=FinanceLabkovich.dbo.GetRate(T.Date)
WHERE LOWER(C.Name)='usd' AND T.Income=1
ORDER BY T.Date
DECLARE #ToBillID int = (SELECT BillID FROM [FinanceLabkovich].[dbo].Bills B WHERE B.Name='Purse')
DECLARE #i int = (SELECT MIN(Id) FROM #TmpTable)
DECLARE #maxId int = (SELECT MAX(Id) FROM #TmpTable)
DECLARE #TransactionID int, #ToTransactionID int, #Amount decimal
DECLARE #date date
WHILE (#i<=#maxId)
BEGIN
SET #date = (SELECT Date FROM #TmpTable WHERE ID=#i)
SET #Amount = (SELECT AmountUSD FROM [FinanceLabkovich].[dbo].Cashflow WHERE Date=#date)
IF #Amount > 0
BEGIN
INSERT INTO [FinanceLabkovich].[dbo].[Transactions] (Name,BillID,ToBillID,Amount,Date,Income)
SELECT "Name"='Currency exhange'
,BillID
,#ToBillID
,#Amount
,T.Date
,"Income"=0
FROM #TmpTable T
WHERE ID=#i
SET #TransactionID = ##IDENTITY
INSERT INTO [FinanceLabkovich].[dbo].[Transactions] (Name,BillID,ToBillID,Amount,Date,Income)
SELECT "Name"='Currency exhange'
,#ToBillID
,BillID
,#Amount*Rate AS Total
,Date
,"Income"=1
FROM #TmpTable WHERE ID=#i
SET #ToTransactionID = ##IDENTITY
INSERT INTO [FinanceLabkovich].[dbo].[Transfers]
SELECT #TransactionID, #ToTransactionID
END
SET #i += 1
END
Any help appreciated.

How to combine multiple rows into one row for a field in MS SQL 2008 R2 without using temp table or variable?

How do I accomplish my goal without using temp table or variable?
Table:
ID ModelNum Qty
123 ABC 4
123 DEF 4
Expected Result:
ID Models Qty
123 ABC | DEF 4
Thanks in advance!
DECLARE #T TABLE (ID INT,ModelNum CHAR(3),Qty INT)
INSERT INTO #T
VALUES
(123,'ABC',4),
(123,'DEF',4),
(123,'GLK',4)
SELECT DISTINCT ID, STUFF(C.List, 1, 2, '') Models, Qty
FROM #T t
CROSS APPLY (
SELECT '| ' + ModelNum
FROM #T
WHERE ID = t.ID
FOR XML PATH('')
)C(List)
Result Set
ID Models Qty
123 ABC| DEF| GLK 4
Hi how about this Query below:
I have did the same example with some different logic and different attribute.
I can get the expected OP, please response if you have any suggestions for me on btechit#hotmail.com.
Declare:
#ConcatTable table (Ename varchar(30), Empno int)
Insert into #ConcatTable values ('Steve', 100),('mathew', 100),('jon', 101),('tom', 101)
--select * from #ConcatTable
--select ROW_NUMBER()over(order by Empno)Row2,* from
--(select distinct Empno from #ConcatTable)C
declare #p varchar(100) = ''
select #p = #p+ ' '+Ename from (
select DENSE_RANK()over(order by Empno)as dens, * from #ConcatTable )A
where A.dens = 1
declare #q varchar(100) = ''
select #q = #q+ ' '+Ename from (
select DENSE_RANK()over(order by Empno)as dens, * from #ConcatTable )A
where A.dens = 2
--SELECT #p
--SELECT #q
declare #M table (Name varchar(30))
insert into #M
select * from(
select #p as v
union all
select #q as vv
)G
--SELECT ROW_NUMBER()over (order by Name desc)Rown1,* from #M
SELECT A.Name,CC.Empno FROM(
SELECT ROW_NUMBER()over (order by Name desc)Rown1,* FROM #M)A
inner join
(select ROW_NUMBER()over(order by Empno)Row2,* from
(select distinct Empno from #ConcatTable)C
)CC
on A.Rown1 = CC.Row2

SQL split values to multiple rows

I have table :
id | name
1 | a,b,c
2 | b
i want output like this :
id | name
1 | a
1 | b
1 | c
2 | b
If you can create a numbers table, that contains numbers from 1 to the maximum fields to split, you could use a solution like this:
select
tablename.id,
SUBSTRING_INDEX(SUBSTRING_INDEX(tablename.name, ',', numbers.n), ',', -1) name
from
numbers inner join tablename
on CHAR_LENGTH(tablename.name)
-CHAR_LENGTH(REPLACE(tablename.name, ',', ''))>=numbers.n-1
order by
id, n
Please see fiddle here.
If you cannot create a table, then a solution can be this:
select
tablename.id,
SUBSTRING_INDEX(SUBSTRING_INDEX(tablename.name, ',', numbers.n), ',', -1) name
from
(select 1 n union all
select 2 union all select 3 union all
select 4 union all select 5) numbers INNER JOIN tablename
on CHAR_LENGTH(tablename.name)
-CHAR_LENGTH(REPLACE(tablename.name, ',', ''))>=numbers.n-1
order by
id, n
an example fiddle is here.
If the name column were a JSON array (like '["a","b","c"]'), then you could extract/unpack it with JSON_TABLE() (available since MySQL 8.0.4):
select t.id, j.name
from mytable t
join json_table(
t.name,
'$[*]' columns (name varchar(50) path '$')
) j;
Result:
| id | name |
| --- | ---- |
| 1 | a |
| 1 | b |
| 1 | c |
| 2 | b |
View on DB Fiddle
If you store the values in a simple CSV format, then you would first need to convert it to JSON:
select t.id, j.name
from mytable t
join json_table(
replace(json_array(t.name), ',', '","'),
'$[*]' columns (name varchar(50) path '$')
) j
Result:
| id | name |
| --- | ---- |
| 1 | a |
| 1 | b |
| 1 | c |
| 2 | b |
View on DB Fiddle
I have take the reference from here with changed column name.
DELIMITER $$
CREATE FUNCTION strSplit(x VARCHAR(65000), delim VARCHAR(12), pos INTEGER)
RETURNS VARCHAR(65000)
BEGIN
DECLARE output VARCHAR(65000);
SET output = REPLACE(SUBSTRING(SUBSTRING_INDEX(x, delim, pos)
, LENGTH(SUBSTRING_INDEX(x, delim, pos - 1)) + 1)
, delim
, '');
IF output = '' THEN SET output = null; END IF;
RETURN output;
END $$
CREATE PROCEDURE BadTableToGoodTable()
BEGIN
DECLARE i INTEGER;
SET i = 1;
REPEAT
INSERT INTO GoodTable (id, name)
SELECT id, strSplit(name, ',', i) FROM BadTable
WHERE strSplit(name, ',', i) IS NOT NULL;
SET i = i + 1;
UNTIL ROW_COUNT() = 0
END REPEAT;
END $$
DELIMITER ;
Here is my attempt:
The first select presents the csv field to the split.
Using recursive CTE, we can create a list of numbers that are limited to the number of terms in the csv field.
The number of terms is just the difference in the length of the csv field and itself with all the delimiters removed.
Then joining with this numbers, substring_index extracts that term.
with recursive
T as ( select 'a,b,c,d,e,f' as items),
N as ( select 1 as n union select n + 1 from N, T
where n <= length(items) - length(replace(items, ',', '')))
select distinct substring_index(substring_index(items, ',', n), ',', -1)
group_name from N, T
My variant: stored procedure that takes table name, field names and delimiter as arguments. Inspired by post http://www.marcogoncalves.com/2011/03/mysql-split-column-string-into-rows/
delimiter $$
DROP PROCEDURE IF EXISTS split_value_into_multiple_rows $$
CREATE PROCEDURE split_value_into_multiple_rows(tablename VARCHAR(20),
id_column VARCHAR(20), value_column VARCHAR(20), delim CHAR(1))
BEGIN
DECLARE id INT DEFAULT 0;
DECLARE value VARCHAR(255);
DECLARE occurrences INT DEFAULT 0;
DECLARE i INT DEFAULT 0;
DECLARE splitted_value VARCHAR(255);
DECLARE done INT DEFAULT 0;
DECLARE cur CURSOR FOR SELECT tmp_table1.id, tmp_table1.value FROM
tmp_table1 WHERE tmp_table1.value IS NOT NULL AND tmp_table1.value != '';
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1;
SET #expr = CONCAT('CREATE TEMPORARY TABLE tmp_table1 (id INT NOT NULL, value VARCHAR(255)) ENGINE=Memory SELECT ',
id_column,' id, ', value_column,' value FROM ',tablename);
PREPARE stmt FROM #expr;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
DROP TEMPORARY TABLE IF EXISTS tmp_table2;
CREATE TEMPORARY TABLE tmp_table2 (id INT NOT NULL, value VARCHAR(255) NOT NULL) ENGINE=Memory;
OPEN cur;
read_loop: LOOP
FETCH cur INTO id, value;
IF done THEN
LEAVE read_loop;
END IF;
SET occurrences = (SELECT CHAR_LENGTH(value) -
CHAR_LENGTH(REPLACE(value, delim, '')) + 1);
SET i=1;
WHILE i <= occurrences DO
SET splitted_value = (SELECT TRIM(SUBSTRING_INDEX(
SUBSTRING_INDEX(value, delim, i), delim, -1)));
INSERT INTO tmp_table2 VALUES (id, splitted_value);
SET i = i + 1;
END WHILE;
END LOOP;
SELECT * FROM tmp_table2;
CLOSE cur;
DROP TEMPORARY TABLE tmp_table1;
END; $$
delimiter ;
Usage example (normalization):
CALL split_value_into_multiple_rows('my_contacts', 'contact_id', 'interests', ',');
CREATE TABLE interests (
interest_id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
interest VARCHAR(30) NOT NULL
) SELECT DISTINCT value interest FROM tmp_table2;
CREATE TABLE contact_interest (
contact_id INT NOT NULL,
interest_id INT NOT NULL,
CONSTRAINT fk_contact_interest_my_contacts_contact_id FOREIGN KEY (contact_id) REFERENCES my_contacts (contact_id),
CONSTRAINT fk_contact_interest_interests_interest_id FOREIGN KEY (interest_id) REFERENCES interests (interest_id)
) SELECT my_contacts.contact_id, interests.interest_id
FROM my_contacts, tmp_table2, interests
WHERE my_contacts.contact_id = tmp_table2.id AND interests.interest = tmp_table2.value;
Because you have to keep adding "select number union all" in the example above which can be a issue if you need a large number of splits.
select
tablename.id,
SUBSTRING_INDEX(SUBSTRING_INDEX(tablename.name, ',', numbers.n), ',', -1) name
from
(select 1 n union all
select 2 union all select 3 union all
select 4 union all select 5) numbers INNER JOIN tablename
on CHAR_LENGTH(tablename.name)
-CHAR_LENGTH(REPLACE(tablename.name, ',', ''))>=numbers.n-1
order by
id, n
I decided a better way was this which only adds a number row for each digit. Example below is good for 1-1000 add another row makes it good for 1-10000 and so on.
select
tablename.id,
SUBSTRING_INDEX(SUBSTRING_INDEX(tablename.name, ',', numbers.n), ',', -1) name
from(SELECT #row := #row + 1 AS n FROM
(select 0 union all select 1 union all select 2 union all select 3 union all select 4 union all select 5 union all select 6 union all select 7 union all select 8 union all select 9) as t,
(select 0 union all select 1 union all select 2 union all select 3 union all select 4 union all select 5 union all select 6 union all select 7 union all select 8 union all select 9) as t2,
(select 0 union all select 1 union all select 2 union all select 3 union all select 4 union all select 5 union all select 6 union all select 7 union all select 8 union all select 9) as t3,
(SELECT #row:=0) as numbers)as numbers INNER JOIN tablename
on CHAR_LENGTH(tablename.name)
-CHAR_LENGTH(REPLACE(tablename.name, ',', ''))>=numbers.n-1
order by
id, n
CREATE PROCEDURE `getVal`()
BEGIN
declare r_len integer;
declare r_id integer;
declare r_val varchar(20);
declare i integer;
DECLARE found_row int(10);
DECLARE row CURSOR FOR select length(replace(val,"|","")),id,val from split;
create table x(id int,name varchar(20));
open row;
select FOUND_ROWS() into found_row ;
read_loop: LOOP
IF found_row = 0 THEN
LEAVE read_loop;
END IF;
set i = 1;
FETCH row INTO r_len,r_id,r_val;
label1: LOOP
IF i <= r_len THEN
insert into x values( r_id,SUBSTRING(replace(r_val,"|",""),i,1));
SET i = i + 1;
ITERATE label1;
END IF;
LEAVE label1;
END LOOP label1;
set found_row = found_row - 1;
END LOOP;
close row;
select * from x;
drop table x;
END
The original question was for MySQL and SQL in general. The example below is for the new versions of MySQL. Unfortunately, a generic query that would work on any SQL server is not possible. Some servers do no support CTE, others do not have substring_index, yet others have built-in functions for splitting a string into multiple rows.
--- the answer follows ---
Recursive queries are convenient when the server does not provide built-in functionality. They can also be the bottleneck.
The following query was written and tested on MySQL version 8.0.16. It will not work on version 5.7-. The old versions do not support Common Table Expression (CTE) and thus recursive queries.
with recursive
input as (
select 1 as id, 'a,b,c' as names
union
select 2, 'b'
),
recurs as (
select id, 1 as pos, names as remain, substring_index( names, ',', 1 ) as name
from input
union all
select id, pos + 1, substring( remain, char_length( name ) + 2 ),
substring_index( substring( remain, char_length( name ) + 2 ), ',', 1 )
from recurs
where char_length( remain ) > char_length( name )
)
select id, name
from recurs
order by id, pos;
Here is another trick. The number 20 is the maximum number of values in comma separated list.
We use single query, no procedures.
If tbl has more rows than the maximum number of values in single comma separated list you may remove 'inner join tbl a inner join tbl c' part from query. I added this because there are only 2 rows.
CREATE TABLE tbl(id int NOT NULL,name varchar(50),PRIMARY KEY (`id`));
insert into tbl values(1, 'a,b,c'), (2, 'd');
select id ,SUBSTRING_INDEX(SUBSTRING_INDEX(name, ',', k.n), ',', -1) as name
from tbl
INNER JOIN (
SELECT *
FROM (
SELECT #n:=#n+1 AS n
FROM tbl inner join tbl a inner join tbl c
INNER JOIN (SELECT #n:=0) AS _a
) AS _a WHERE _a.n <= 20
)AS k ON k.n <= LENGTH(name) - LENGTH(replace(name, ',','')) + 1
order by id
This is a trick to extract nth value in comma separated list:
SUBSTRING_INDEX(SUBSTRING_INDEX(name, ',', k.n), ',', -1)
Here is my solution
-- Create the maximum number of words we want to pick (indexes in n)
with recursive n(i) as (
select
1 i
union all
select i+1 from n where i < 1000
)
select distinct
s.id,
s.oaddress,
-- n.i,
-- use the index to pick the nth word, the last words will always repeat. Remove the duplicates with distinct
if(instr(reverse(trim(substring_index(s.oaddress,' ',n.i))),' ') > 0,
reverse(substr(reverse(trim(substring_index(s.oaddress,' ',n.i))),1,
instr(reverse(trim(substring_index(s.oaddress,' ',n.i))),' '))),
trim(substring_index(s.oaddress,' ',n.i))) oth
from
app_schools s,
n
Best Practice.
Result:
SELECT
SUBSTRING_INDEX(SUBSTRING_INDEX('ab,bc,cd',',',help_id+1),',',-1) AS oid
FROM
(
SELECT #xi:=#xi+1 as help_id from
(SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5) xc1,
(SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5) xc2,
(SELECT #xi:=-1) xc0
) a
WHERE
help_id < LENGTH('ab,bc,cd')-LENGTH(REPLACE('ab,bc,cd',',',''))+1
First, create a numbers table:
SELECT #xi:=#xi+1 as help_id from
(SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5) xc1,
(SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5) xc2,
(SELECT #xi:=-1) xc0;
| help_id |
| --- |
| 0 |
| 1 |
| 2 |
| 3 |
| ... |
| 24 |
Second, just split the str:
SELECT SUBSTRING_INDEX(SUBSTRING_INDEX('ab,bc,cd',',',help_id+1),',',-1) AS oid
FROM
numbers_table
WHERE
help_id < LENGTH('ab,bc,cd')-LENGTH(REPLACE('ab,bc,cd',',',''))+1
| oid |
| --- |
| ab |
| bc |
| cd |
SELECT id, unnest(string_to_array(name, ',')) AS names
FROM datatable
Hope this helps :D

SQL SERVER generating row number based on criteria

I have a table which has columns UserID,DIFFTIME. when I select these columns from the table, I also want to have a derived column which is : If the DiffTime is > 20 I want to increment the count per user id.
For example if the table has:
User ID DIFF TIME
1 0
1 5
1 10
2 0
2 21
2 5
I want a result set that is something like this:
User ID DIFF TIME SESSION NUMBER
1 0 1
1 5 1
1 10 1
2 0 1
2 21 2
2 5 2
How do I accomplish this.
Ideas and suggestions are much appreciated!
Use this statement:
select t1.User_Id, t1.Diff_Time,
isnull(count(t2.User_Id), 0) + 1 as Session_Number
from #table t1
left join #table t2
on t1.User_Id = t2.User_Id
and t1.eventTime >= t2.eventTime
and t2.Diff_Time > 20
group by t1.User_Id, t1.Diff_Time, t1.eventTime
order by t1.User_Id, t1.eventTime
(replace #table with your actual table name)
Note: I assume that the fifth row of your table has the value 21 in the Diff_Time column, and there's a typo in the question, as #AaronBertrand pointed out in the comments
create table #t
(
id int,
Diff int,
SessionNumber int
)
insert into #t(id, diff)values(1, 0)
insert into #t(id, diff)values(1, 5)
insert into #t(id, diff)values(1, 10)
insert into #t(id, diff)values(2, 0)
insert into #t(id, diff)values(2, 21)
insert into #t(id, diff)values(2, 5)
Select ROW_NUMBER() over(order by Id) as RowID, * into #Temp1 from #t
Declare #diff int
Declare #RowId int
Declare #Previous int
Declare #NewValue int
DECLARE #Cur CURSOR SET #Cur = CURSOR FOR select RowId, diff from #Temp1
OPEN #Cur
FETCH NEXT FROM #Cur INTO #RowId, #diff
WHILE ##FETCH_STATUS = 0
BEGIN
if(#RowId = 1)
Begin
Update #Temp1 Set SessionNumber = 1 Where rowid = 1
Set #Previous = #Diff
Set #NewValue = 1
End
Else
Begin
if(#Diff - #Previous > 20)
Begin
Set #Previous = #Diff
Set #NewValue = #NewValue + 1
Update #Temp1 Set SessionNumber = #NewValue Where rowid = #RowId
End
else
Update #Temp1 Set SessionNumber = #NewValue Where rowid = #RowId
End
FETCH NEXT FROM #Cur INTO #RowId, #diff
END
CLOSE #Cur
DEALLOCATE #Cur
select * from #temp1
drop table #t
drop table #temp1