SQLite convert single row to multiple rows - sqlite

SQLite
I want to convert single row value seperate by ',' to multiple rows
Example :
Single_Row
6,7,8,9,10,11,12,13,14,15,16
Result must be :
MultipleRows
6
7
8
9
10
12
13
14
15
16
I tried doing it with substr function but getting unexpected result
select
numbers.n,
substr(CbahiHSSpecialtyUnits.units,numbers.n,1)
from
numbers inner join CbahiHSSpecialtyUnits
on LENGTH(CbahiHSSpecialtyUnits.units)
- LENGTH(REPLACE(CbahiHSSpecialtyUnits.units, ',', ''))>=numbers.n-1
WHERE HsSubStandardID=22 and SpecialtyID=2 and numbers.n>0
order by numbers.n;
One good thing is I'm getting number of rows correct.. But the values that should be separated is wrong ..
Please note numbers table is I have created for indexing purpose, with the help of this post.
SQL split values to multiple rows

You can do it with a recursive CTE:
WITH cte AS (
SELECT SUBSTR(Units, 1, INSTR(Units || ',', ',') - 1) col,
SUBSTR(Units, INSTR(Units || ',', ',') + 1) value
FROM CbahiHSSpecialtyUnits
WHERE HsSubStandardID=22 AND SpecialtyID = 2
UNION ALL
SELECT SUBSTR(value, 1, INSTR(value || ',', ',') - 1),
SUBSTR(value, INSTR(value || ',', ',') + 1)
FROM cte
WHERE LENGTH(value) > 0
)
SELECT col
FROM cte
WHERE col + 0 > 0
Or, if you know the upper limit of the numbers is, say 20 and there are no duplicates among the numbers:
WITH cte AS (SELECT 1 col UNION ALL SELECT col + 1 FROM cte WHERE col < 20)
SELECT c.col
FROM cte c INNER JOIN CbahiHSSpecialtyUnits u
ON ',' || u.Units || ',' LIKE '%,' || c.col || ',%'
WHERE HsSubStandardID=22 AND SpecialtyID = 2
See the demo.
Results:
col
6
7
8
9
10
11
12
13
14
15
16

I got the solution
http://www.samuelbosch.com/2018/02/split-into-rows-sqlite.html
WITH RECURSIVE split(predictorset_id, predictor_name, rest) AS (
SELECT CbahiHSSpecialtyUnits.SpclUnitSerial, '', units || ',' FROM CbahiHSSpecialtyUnits WHERE HsSubStandardID=22 and SpecialtyID=2
UNION ALL
SELECT predictorset_id,
substr(rest, 0, instr(rest, ',')),
substr(rest, instr(rest, ',')+1)
FROM split
WHERE rest <> ''

Related

How to return all rows in a column when using cross apply and DelimitedSplit8K_LEAD together in SQL Server?

This code works to return the info that I need from the column but IT IS ONLY RETURNING ONE ROW and I need it to return them all.
You can leverage the DelimitedSplit8K_LEAD function which you can read more about here. https://www.sqlservercentral.com/articles/reaping-the-benefits-of-the-window-functions-in-t-sql-2 You will also find the code for the function there.
select FirstValue = max(case when s.ItemNumber = 12 then s.Item end)
, SecondValue = max(case when s.ItemNumber = 45 then try_convert(datetime, stuff(stuff(stuff(s.Item, 9, 0, ' '), 12, 0, ':'), 15, 0, '.')) end)
from myDatabase x
cross apply DelimitedSplit8K_LEAD(x.myColumn, ',') s
where s.ItemNumber = 12
or s.ItemNumber = 45
Here is an example of the data in the column that I'm trying to return.
,505611,XXXXXXX,,,,,,,,,13M2,,,,,,,,,,,03294961,,,,,,,,,,,,,,,,,,,,,XXXXX,20220216183348,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,US,,,0000,,,,,,,,,,,,,,,,,,,,
Here is an example of it working, just not with using it on the table column https://dbfiddle.uk/?rdbms=sqlserver_2019&fiddle=ca74e807853eb1dea445a8ffb7209b70
Okay so here is an example. Create a table in sql server database...
CREATE TABLE honda
(
user1 nvarchar(max)
);
INSERT INTO honda
(user1)
VALUES
(',523869,HXMFG-01,,,,,,,,,11M2,,,,,,,,,,,03311141,,,,,,,,,,,,,,,,,,,,,EAGLE,20220323082041,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,US,,,0000,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,'),
(',523869,HXMFG-01,,,,,,,,,12M2,,,,,,,,,,,03311148,,,,,,,,,,,,,,,,,,,,,EAGLE,20220323093049,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,US,,,0000,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,'),
(',523869,HXMFG-01,,,,,,,,,13M2,,,,,,,,,,,03311216,,,,,,,,,,,,,,,,,,,,,EAGLE,20220323100350,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,US,,,0000,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,'),
(',523869,HXMFG-01,,,,,,,,,14M2,,,,,,,,,,,03311242,,,,,,,,,,,,,,,,,,,,,EAGLE,20220323103854,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,US,,,0000,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,'),
(',523869,HXMFG-01,,,,,,,,,15M2,,,,,,,,,,,03311267,,,,,,,,,,,,,,,,,,,,,EAGLE,20220323112420,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,US,,,0000,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,'),
(',527040,HXMFG-01,,,,,,,,,16M2,,,,,,,,,,,03311352,,,,,,,,,,,,,,,,,,,,,EAGLE,20220323122930,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,US,,,0000,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,'),
(',527040,HXMFG-01,,,,,,,,,17M2,,,,,,,,,,,03311395,,,,,,,,,,,,,,,,,,,,,EAGLE,20220323130347,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,US,,,0000,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,');
If using sql server should only have to run this middle block of code once.
CREATE FUNCTION [dbo].[DelimitedSplit8K_LEAD]
--===== Define I/O parameters
(#pString VARCHAR(8000), #pDelimiter CHAR(1))
RETURNS TABLE WITH SCHEMABINDING AS
RETURN
--===== "Inline" CTE Driven "Tally Tableā€ produces values from 0 up to 10,000...
-- enough to cover VARCHAR(8000)
WITH E1(N) AS (
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1
), --10E+1 or 10 rows
E2(N) AS (SELECT 1 FROM E1 a, E1 b), --10E+2 or 100 rows
E4(N) AS (SELECT 1 FROM E2 a, E2 b), --10E+4 or 10,000 rows max
cteTally(N) AS (--==== This provides the "zero base" and limits the number of rows right up front
-- for both a performance gain and prevention of accidental "overruns"
SELECT 0 UNION ALL
SELECT TOP (DATALENGTH(ISNULL(#pString,1))) ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) FROM E4
),
cteStart(N1) AS (--==== This returns N+1 (starting position of each "element" just once for each delimiter)
SELECT t.N+1
FROM cteTally t
WHERE (SUBSTRING(#pString,t.N,1) = #pDelimiter OR t.N = 0)
)
--===== Do the actual split. The ISNULL/NULLIF combo handles the length for the final element when no delimiter is found.
SELECT ItemNumber = ROW_NUMBER() OVER(ORDER BY s.N1),
Item = SUBSTRING(#pString,s.N1,ISNULL(NULLIF((LEAD(s.N1,1,1) OVER (ORDER BY s.N1) - 1),0)-s.N1,8000))
FROM cteStart s
;
GO
If using dbfiddle take the GO off of the end in the above block.
select ECI_Level = max(case when s.ItemNumber = 12 then s.Item end)
, 'DateTime' = max(case when s.ItemNumber = 45 then try_convert(datetime, stuff(stuff(stuff(s.Item, 9, 0, ' '), 12, 0, ':'), 15, 0, '.')) end)
from honda x
cross apply DelimitedSplit8K_LEAD(x.user1, ',') s
where s.ItemNumber = 12
or s.ItemNumber = 45
I was able to get it to work by adding GROUP BY like this...
select ECI_Level = max(case when s.ItemNumber = 12 then s.Item end)
, 'DateTime' = max(case when s.ItemNumber = 45 then try_convert(datetime, stuff(stuff(stuff(s.Item, 9, 0, ' '), 12, 0, ':'), 15, 0, '.')) end)
from honda x
cross apply DelimitedSplit8K_LEAD(x.user1, ',') s
where s.ItemNumber = 12
or s.ItemNumber = 45
GROUP BY user1;
The problem is that you are aggregating over the whole honda table, rather than just over the split data.
You need to place the aggregation into an APPLY
select s.*
from honda x
CROSS apply (
SELECT ECI_Level = max(case when s.ItemNumber = 12 then s.Item end)
, DateTime = max(case when s.ItemNumber = 45 then try_convert(datetime, stuff(stuff(stuff(s.Item, 9, 0, ' '), 12, 0, ':'), 15, 0, '.')) end)
FROM DelimitedSplit8K_LEAD(x.user1, ',') s
where s.ItemNumber = 12
or s.ItemNumber = 45
) s;
db<>fiddle
Personally, for just two values I wouldn't bother with a split function. Instead you can use CHARINDEX, feeding each one into the next to get the next , location.
And I guess this just shows why you shouldn't store data in a database like this in the first place.

Recursing through a SQLite table to find a matching subset of records

I have a table with 3 text fields column (A, B & C) imported from a flat file comprising many thousands of lines. None of these fields have a UNIQUE constraint and there is no primary key combination. As a result one or more records may have the same values and there will even be records with the same values across all fields. In many records, columns A, B and C should be the same but due to data quality issues, column C has many variant where column A and B are the same. Where column A and B are the same the corresponding value in column C may be subsets of the value of column C in another record having the same values as other records for column A and B.
To illustrate a subset arrived at by using GROUP BY gives:
enter image description here
I now need to narrow down that subset further to find all records where the value in column C is INSTR the values of the other grouped results i.e. i'd like to return:
enter image description here
because "Buckingham" and "Lindsey" are both INSTR the records that contain "Lindsey Buckingham" in column C
With EXISTS and INSTR():
select t.* from tablename t
where exists (
select 1 from tablename
where a = t.a and b = t.b and c <> t.c and instr(c, t.c) > 0
)
or with LIKE:
select t.* from tablename t
where exists (
select 1 from tablename
where a = t.a and b = t.b and c <> t.c and c like '%' || t.c || '%'
)
or with a self join:
select distinct t.*
from tablename t inner join tablename tt
on tt.a = t.a and tt.b = t.b and tt.c <> t.c and tt.c like '%' || t.c || '%'

Split values in parts with sqlite

I'm struggling to convert
a | a1,a2,a3
b | b1,b3
c | c2,c1
to:
a | a1
a | a2
a | a3
b | b1
b | b2
c | c2
c | c1
Here are data in sql format:
CREATE TABLE data(
"one" TEXT,
"many" TEXT
);
INSERT INTO "data" VALUES('a','a1,a2,a3');
INSERT INTO "data" VALUES('b','b1,b3');
INSERT INTO "data" VALUES('c','c2,c1');
The solution is probably recursive Common Table Expression.
Here's an example which does something similar to a single row:
WITH RECURSIVE list( element, remainder ) AS (
SELECT NULL AS element, '1,2,3,4,5' AS remainder
UNION ALL
SELECT
CASE
WHEN INSTR( remainder, ',' )>0 THEN
SUBSTR( remainder, 0, INSTR( remainder, ',' ) )
ELSE
remainder
END AS element,
CASE
WHEN INSTR( remainder, ',' )>0 THEN
SUBSTR( remainder, INSTR( remainder, ',' )+1 )
ELSE
NULL
END AS remainder
FROM list
WHERE remainder IS NOT NULL
)
SELECT * FROM list;
(originally from this blog post: https://blog.expensify.com/2015/09/25/the-simplest-sqlite-common-table-expression-tutorial)
It produces:
element | remainder
-------------------
NULL | 1,2,3,4,5
1 | 2,3,4,5
2 | 3,4,5
3 | 4,5
4 | 5
5 | NULL
the problem is thus to apply this to each row in a table.
Yes, a recursive common table expression is the solution:
with x(one, firstone, rest) as
(select one, substr(many, 1, instr(many, ',')-1) as firstone, substr(many, instr(many, ',')+1) as rest from data where many like "%,%"
UNION ALL
select one, substr(rest, 1, instr(rest, ',')-1) as firstone, substr(rest, instr(rest, ',')+1) as rest from x where rest like "%,%" LIMIT 200
)
select one, firstone from x UNION ALL select one, rest from x where rest not like "%,%"
ORDER by one;
Output:
a|a1
a|a2
a|a3
b|b1
b|b3
c|c2
c|c1
Check my answer in How to split comma-separated value in SQLite?.
This will give you the transformation in a single query rather than having to apply to each row.
-- using your data table assuming that b3 is suppose to be b2
WITH split(one, many, str) AS (
SELECT one, '', many||',' FROM data
UNION ALL SELECT one,
substr(str, 0, instr(str, ',')),
substr(str, instr(str, ',')+1)
FROM split WHERE str !=''
) SELECT one, many FROM split WHERE many!='' ORDER BY one;
a|a1
a|a2
a|a3
b|b1
b|b2
c|c2
c|c1

Select multiple columns using decode

I have 6 columns with same value as either of 0,1,2,3. I want to display the result such as 0 represents SUCCESS, 1 or 2 represent failure and 3 represents NOT APPLICABLE. So if in DB the values are :
col A | col B | col C | col D | col E | col F
0 | 1 | 2 | 0 | 3 | 2
Output should be :
col A | col B | col C | col D | col E | col F
S | F | F | S | NA | F
Is it possible to do it through decode by selecting all the columns at once rather than selecting them individually?
If I understand your question correctly, it sounds like you just need a case expression (or decode, if you prefer, but that's less self-documenting than a case expression), along the lines of:
case when some_col = 0 then 'S'
when some_col in (1, 2) then 'F'
...
else some_col -- replace with whatever you want the output to be if none of the above conditions are met
end
or maybe:
case some_col
when 0 then 'S'
when 1 then 'F'
...
else some_col -- replace with whatever you want the output to be if none of the above conditions are met
end
So your query would look something like:
select case ...
end col_a,
...
case ...
end col_f
from your_table;
Is it possible to do it through decode by selecting all the columns at once rather than selecting them individually?
No
However, besides using pivot, the only solution I see would be using PL/SQL:
1.This is how I simulated your table
SELECT *
FROM (WITH tb1 (col_a, col_b, col_c, col_d, col_e, col_f) AS
(SELECT 0, 1, 2, 0, 3, 2 FROM DUAL)
SELECT *
FROM tb1)
2.I would append the columns together with a comma between them and save them into a table of strings
SELECT col_a || ',' || col_b || ',' || col_c || ',' || col_d || '.' || col_e || ',' || col_f
FROM (WITH tb1 (col_a, col_b, col_c, col_d, col_e, col_f) AS (SELECT 0, 1, 2, 0, 3, 2 FROM DUAL)
SELECT *
FROM tb1)
3.Then I would use REGEXP_REPLACE to replace your values one row at a time
SELECT REPLACE (REGEXP_REPLACE (REPLACE ('0,1,2,0,3,2', 0, 'S'), '[1-2]', 'F'), 3, 'NA') COL_STR
FROM DUAL
4. Using dynamic SQL I would update the table using rowid or whatever you intend to do. I made this SQL which will separate the string into columns
SELECT REGEXP_SUBSTR (COL_STR, '[^,]+', 1, 1) AS COL_A,
REGEXP_SUBSTR (COL_STR, '[^,]+', 1, 2) AS COL_B,
REGEXP_SUBSTR (COL_STR, '[^,]+', 1, 3) AS COL_C,
REGEXP_SUBSTR (COL_STR, '[^,]+', 1, 4) AS COL_D,
REGEXP_SUBSTR (COL_STR, '[^,]+', 1, 5) AS COL_E,
REGEXP_SUBSTR (COL_STR, '[^,]+', 1, 6) AS COL_F
FROM tst1)
All of this is very tedious and it could take some time. Using DECODE or CASE would be easier to look at and interpret and thus easier to maintain.

How to use sum and joins within group_concat

I have two tables, CustomerCategories and Articles. Both tables have a column called VatPercentage and VatPrice.
Scenarios:
In case of same vatpercentage exists in both tables i want to sum the vatprice from both tables and add it to the string.
In case of vatpercentage only exists in CustomerCategories table i want to sum the vatprice from here and add it to the string.
In case of vatpercentage only exists in Articles table i want to sum the vatprice from here and add it to the string.
Example tables:
Articles:
ArticleId TotalPrice VatPercentage VatPrice
1 100 25.0000000000 25
2 80 25.0000000000 20
3 50 8.0000000000 4
4 70 8.0000000000 5.6
5 20 0 0
6 0 0 0
CustomerCategories:
CustomerCategoryId TotalPrice VatPercentage VatPrice
2 163 8.0000000000 13
2 163 13.0000000000 13
2 163 0 0
2 150 25.0000000000 37.5
The result i want back from the Query in this case:
{esc}{40h}25 %{esc}{41h}82.5 NOK{lf}{esc}{40h}8 %{esc}{41h}22.6 NOK{lf}{esc}{40h}13 %{esc}{41h}13 NOK{lf}
The code i've trying without any positive results is:
SELECT GROUP_CONCAT(Result, '|') Results
FROM (
select case when cc.VatPercentage = a.VatPercentage
then
SELECT '{esc}{40}' || CAST(cc.VatPercentage AS INTEGER) || '% ' ||
(SUM(cc.VatPrice) + SUM(a.VatPrice)) || ' NOK' || '{lf}' Result end
else
(
case when cc.VatPercentage <> a.VatPercentage
then
SELECT '{esc}{40}' || CAST(cc.VatPercentage AS INTEGER) || '% ' ||
(SUM(cc.VatPrice) + SUM(a.VatPrice)) || ' NOK' || '{lf}' ||
SELECT '{esc}{40}' || CAST(a.VatPercentage AS INTEGER) || '% ' ||
(SUM(a.VatPrice)) || ' NOK' || '{lf}' Result
end
)
FROM CustomerCategories cc
LEFT JOIN Articles a
on cc.VatPercentage = a.VatPercentage
WHERE
cc.VatPercentage != '0'
AND a.VatPercentage != '0'
AND cc.TotalPrice != '0'
AND a.TotalPrice != '0'
GROUP BY
cc.VatPercentage OR a.VatPercentage) x
Help would be appreciated.
Fiddle
First, combine both tables:
SELECT VatPercentage, VatPrice FROM CustomerCategories
UNION ALL
SELECT VatPercentage, VatPrice FROM Articles
VatPercentage VatPrice
8.0000000000 13
13.0000000000 13
0 0
25.0000000000 37.5
25.0000000000 25
25.0000000000 20
8.0000000000 4
8.0000000000 5.6
0 0
0 0
Then do a simple GROUP BY over that:
SELECT VatPercentage,
SUM(VatPrice) AS PriceSum
FROM (SELECT VatPercentage, VatPrice FROM CustomerCategories
UNION ALL
SELECT VatPercentage, VatPrice FROM Articles)
WHERE VatPercentage != '0'
GROUP BY VatPercentage
Then do the escape characters mess over the result of that:
SELECT GROUP_CONCAT('{esc}{40h}' || VatPercentage || ' %' ||
'{esc}{41h}' || VatPrice || ' NOK{lf}',
'')
FROM (SELECT VatPercentage,
SUM(VatPrice) AS PriceSum
FROM (SELECT VatPercentage, VatPrice FROM CustomerCategories
UNION ALL
SELECT VatPercentage, VatPrice FROM Articles)
WHERE VatPercentage != '0'
GROUP BY VatPercentage)

Resources