Extract Data from a large string to table using SQL - sql

I'm trying to extract data strings from a long string using SQL query.
the string "'35522':{'item_id':'35522','sku':'deded','RowTotal':37.5,'qty':2"
I am trying to create a loop query that extracts data from the string.
The desired output is a table with columns (item_id,sku, RowTotal,qty) and each line will be extracted from the string above in the relevant column.
Im trying to create a function that will do that but currentley im not close.
Can you please assist me with ideas?
DECLARE #String VARCHAR(4000) =
'{:{item_id:35522,sku:deep-line-elixir,RowTotal:37.5,qty:2},
:{item_id:35527,sku:self-care-pamper-pack,RowTotal:158,qty:2},
:{item_id:35531,sku:neck-chest-rejuvenating-serum,RowTotal:21.87,qty:1},
:{item_id:35534,sku:pm-recovery-night-cream,RowTotal:23.75,qty:1},couponCode:,itemsQty:6}"'
DECLARE #b VARBINARY(4000) = CONVERT(varbinary(4000),#string)
DECLARE #StartPos int=9
DECLARE #Len tinyint=13
;WITH C (Orig,Startpos,Value) AS (
SELECT #b,#StartPos,CONVERT(VARCHAR,SUBSTRING(#b,#StartPos,#Len))
UNION ALL
SELECT #b,C.Startpos+#Len,CONVERT(VARCHAR,SUBSTRING(#b,C.StartPos+#Len,#Len)) FROM C
WHERE C.Startpos+#Len < = LEN(#b)
)
select C.Value from c where c.value like 'item%'
This is my code so far

The following approach splits the data into rows using STRING_SPLIT before replacing the additional characters and using case expressions to match field values. The temporary table is used to generate a row_num which can be used to order values when generating the group_num used to group the related values across multiple rows into a single related row
DECLARE #SampleString VARCHAR(4000) =
'{:{item_id:35522,sku:deep-line-elixir,RowTotal:37.5,qty:2},
:{item_id:35527,sku:self-care-pamper-pack,RowTotal:158,qty:2},
:{item_id:35531,sku:neck-chest-rejuvenating-serum,RowTotal:21.87,qty:1},
:{item_id:35534,sku:pm-recovery-night-cream,RowTotal:23.75,qty:1},couponCode:,itemsQty:6}"';
create table #temp_values (id int identity(1,1), value VARCHAR(200) );
insert into #temp_values (value) SELECT value FROM STRING_SPLIT(REPLACE(REPLACE(#SampleString,'{',''),'}',''),',');
WITH split_data AS (
SELECT id,value FROM #temp_values
),
extracted_data_raw AS (
SELECT
id as row_num,
CASE
WHEN value LIKE '%item_id%' THEN 1
WHEN value LIKE '%sku:%' THEN 2
WHEN value LIKE '%RowTotal:%' THEN 3
WHEN value LIKE '%qty:%' AND value NOT LIKE '%itemsQty%' THEN 4
END as type_num,
CASE
WHEN value LIKE '%item_id%' THEN TRIM(REPLACE(value,':item_id:',''))
END as item_id,
CASE WHEN value LIKE '%sku:%' THEN TRIM(REPLACE(value,'sku:','')) END as sku,
CASE WHEN value LIKE '%RowTotal:%' THEN TRIM(REPLACE(value,'RowTotal:','')) END as RowTotal,
CASE WHEN value LIKE '%qty:%' AND value NOT LIKE '%itemsQty%' THEN TRIM(REPLACE(value,'qty:','')) END as qty,
value
FROM split_data
)
,extracted_data_clean AS (
SELECT
MAX(item_id) as item_id,
MAX(sku) as sku,
MAX(RowTotal) as RowTotal,
MAX(qty) as qty
FROM (
SELECT
ROW_NUMBER() OVER (PARTITION BY type_num ORDER BY row_num) group_num,
item_id,
sku,
RowTotal,
qty
FROM
extracted_data_raw
WHERE
type_num IS NOT NULL
) t
GROUP BY group_num
)
select * from extracted_data_clean
Outputs:
item_id
sku
RowTotal
qty
35522
deep-line-elixir
37.5
2
35527
self-care-pamper-pack
158
2
35531
neck-chest-rejuvenating-serum
21.87
1
35534
pm-recovery-night-cream
23.75
1
Working Demo DB Fiddle
Edit 1:
You also referenced "'35522':{'item_id':'35522','sku':'deded','RowTotal':37.5,'qty':2" in your question. The following should work for both samples while also converting to respective data types:
DECLARE #SampleString VARCHAR(4000) =
'{"35522":{item_id:35522,sku:deep-line-elixir,RowTotal:37.5,qty:2},
"35522":{item_id:35527,sku:self-care-pamper-pack,RowTotal:158,qty:2},
"35522":{item_id:35531,sku:neck-chest-rejuvenating-serum,RowTotal:21.87,qty:1},
"35522":{item_id:35534,sku:pm-recovery-night-cream,RowTotal:23.75,qty:1},couponCode:,itemsQty:6}"';
create table #temp_values (id int identity(1,1), value VARCHAR(200) );
insert into #temp_values (value) SELECT value FROM STRING_SPLIT(REPLACE(REPLACE(#SampleString,'{',''),'}',''),',');
WITH split_data AS (
SELECT id,value FROM #temp_values
),
extracted_data_raw AS (
SELECT
id as row_num,
CASE
WHEN value LIKE '%item_id%' THEN 1
WHEN value LIKE '%sku:%' THEN 2
WHEN value LIKE '%RowTotal:%' THEN 3
WHEN value LIKE '%qty:%' AND value NOT LIKE '%itemsQty%' THEN 4
END as type_num,
CASE
WHEN value LIKE '%item_id%' THEN CONVERT(INT,TRIM(
REPLACE(SUBSTRING(
value,CHARINDEX('item_id',value),LEN(value)
),'item_id:','')
))
END as item_id,
CASE WHEN value LIKE '%sku:%' THEN TRIM(REPLACE(value,'sku:','')) END as sku,
CASE WHEN value LIKE '%RowTotal:%' THEN CONVERT(NUMERIC(10,2),TRIM(REPLACE(value,'RowTotal:',''))) END as RowTotal,
CASE WHEN value LIKE '%qty:%' AND value NOT LIKE '%itemsQty%' THEN CONVERT(NUMERIC(10,2),TRIM(REPLACE(value,'qty:',''))) END as qty,
value
FROM split_data
)
,extracted_data_clean AS (
SELECT
MAX(item_id) as item_id,
MAX(sku) as sku,
MAX(RowTotal) as RowTotal,
MAX(qty) as qty
FROM (
SELECT
ROW_NUMBER() OVER (PARTITION BY type_num ORDER BY row_num) group_num,
item_id,
sku,
RowTotal,
qty
FROM
extracted_data_raw
WHERE
type_num IS NOT NULL
) t
GROUP BY group_num
)
select * from extracted_data_clean;
drop table #temp_values;
Working Demo DB Fiddle
Let me know if this works for you.

Related

Find the specific number of specific value in SQL

I have to show the specific value in a table that I have to get that from user, for example user write 2 and 'toilet paper' than I have to show the second date of toilet paper in my table:
I wrote this but I know I doesn't work:
CREATE PROC BuyCount(#NInput INT,
#TitleInput nvarchar(50))
AS
BEGIN
DECLARE #RecordItemCount INT
SET #RecordItemCount = (SELECT COUNT(FID) FROM Buy_tbl WHERE Bname = #TitleInput)
IF (#Input <= #RecordItemCount)
BEGIN
SELECT BuyDate , #Input
FROM Buy_tbl
WHERE Bname = #TitleInput AND ...
END
ELSE
BEGIN
PRINT 'Out of range'
SELECT BuyDate , #Input
FROM Buy_tbl
WHERE Bname = #TitleInput
END
END
PS: Also I should mention, If the number was out of range it returns the last value of the buy items
Following query should work in both the scenarios
;WITH cte
AS (
SELECT BuyDate
,row_number() OVER (ORDER BY BuyDate) rn
,count(*) OVER () ct
FROM Buy_tbl
WHERE Bname = #TitleInput
)
SELECT TOP 1 BuyDate
,#Input
FROM cte
WHERE rn = #Input
OR rn = ct
ORDER BY BuyDate
Example Demo

SQL - populate new column according to data in row above

I need to populate a new column in a table known as RowType, where if the ID column contains the same ID value as the one above RowType is populated with 'D', if the value is new then RowType is populate with 'H', how would the SQL code look to be able to do this?
I.e should look something like below:
RowType (to be populated), ID (already there)
H, 1
D, 1
D, 1
H, 2
D, 2
H, 3
D, 3
D, 3
Thanks
You can use Row_Number and case
select *, RowType = case when Row_Number() over (partition by id order by id) = 1 then 'H' else 'D' End from #yourid
Your input table:
create table #yourId (id int)
insert into #yourid (id) values
(1)
,(1)
,(1)
,(2)
,(2)
,(3)
,(3)
,(3)
Use ROW_NUMER concept :
CREATE TABLE #table(Id INT)
INSERT INTO #table(Id)
SELECT 1 UNION ALL
SELECT 1 UNION ALL
SELECT 1 UNION ALL
SELECT 2 UNION ALL
SELECT 2 UNION ALL
SELECT 3 UNION ALL
SELECT 3 UNION ALL
SELECT 3
SELECT CASE WHEN RowType = 1 THEN 'H' ELSE 'D' END RowType , Id
FROM
(
SELECT ROW_NUMBER() OVER (PARTITION BY Id ORDER BY id) RowType , Id
FROM #table
) A
Please try...
UPDATE tableName
SET RowType = CASE
WHEN ( ID = LAG( ID ) OVER ( ORDER BY ID ) ) THEN 'D'
ELSE 'H'
END
If you have any questions or comments, then please feel free to post a Comment accordingly.
Further Reading
https://learn.microsoft.com/en-us/sql/t-sql/functions/lag-transact-sql (for information on LAG()).
It may not be the best solution, however it can point you somewhere, and it works.
Go through the code carfuly and make sure you understand this.
create table yourTable (RowType char, id int)
insert into yourTable (RowType, id) values
('',1)
,('',1)
,('',1)
,('',2)
,('',2)
,('',3)
,('',3)
,('',3)
select
row_number() over (order by id) as rowNumber,
RowType,
id
into #tempTable
from yourTable
declare #maxRow int = (select max(rowNumber) from #tempTable)
declare #currentRow int = 1
while (#currentRow <= #maxRow)
begin
if (#currentRow = 1)
begin
update #tempTable
set RowType = 'H'
where rowNumber = #currentRow
end
else
begin
if (select id from #tempTable where rowNumber = #currentRow) = (select id from #tempTable where rowNumber = #currentRow - 1)
begin
update #tempTable
set RowType = 'D'
where rowNumber = #currentRow
end
else
begin
update #tempTable
set RowType = 'H'
where rowNumber = #currentRow
end
end
set #currentRow = #currentRow +1
end
-- update data in actual table, you can do below if only those two columns exist in table !!!
delete from yourTable
-- insert into table from updated temp table
insert into yourTable
select RowType, ID
from #tempTable
select * from yourTable
select * from #tempTable
-- drop temp table
drop table #tempTable

Case when rowcount

SELECT distinct ID from table
Result
Letter
1 A
2 B
How can I make the select display the following when the result is as above:
What I was thinking but not working yet:
SELECT CASE WHEN ##ROWCOUNT(ID) = 2 THEN 'AB'
ELSE ID END AS ID
FROM table
Result would be
Letter
1 AB
Your requirements really aren't clear.
Based on various assumptions, this might give you what you want.
SELECT Count(*) OVER (PARTITION BY 937)
, CASE WHEN Count(*) OVER (PARTITION BY 937) = 2 THEN
'AB'
ELSE
id
END As id
, id
FROM your_table
Try:
Select
CASE WHEN (ROW_NUMBER() OVER (ORDER BY [ID])) = 2 THEN 'AB'
ELSE [ID] END AS [ID]
FROM table
group by
[ID]
What's above will also only work if [ID] and 'AB' are the same data type. If not, then you'll need to cast [ID] to a varchar in your ELSE statement. That'd be: ELSE cast([ID] as varchar(8)) END AS [ID]
Am not sure what you are trying to achieve. But to me it looks like this is what you are trying..
Declare a Variable and initialize it with Distinct Count of ID and use it in select
Declare #cnt int
SELECT #cnt=count(distinct ID) from table
SELECT CASE WHEN #cnt = 2 THEN 'AB'
ELSE ID END AS ID
FROM table

T-Sql count string sequences over multiple rows

How can I find subsets of data over multiple rows in sql?
I want to count the number of occurrences of a string (or number) before another string is found and then count the number of times this string occurs before another one is found.
All these strings can be in random order.
This is what I want to achieve:
I have one table with one column (columnx) with data like this:
A
A
B
C
A
B
B
The result I want from the query should be like this:
2 A
1 B
1 C
1 A
2 B
Is this even possible in sql or would it be easier just to write a little C# app to do this?
Since, as per your comment, you can add a column that will unambiguously define the order in which the columnx values go, you can try the following query (provided the SQL product you are using supports CTEs and ranking functions):
WITH marked AS (
SELECT
columnx,
sortcolumn,
grp = ROW_NUMBER() OVER ( ORDER BY sortcolumn)
- ROW_NUMBER() OVER (PARTITION BY columnx ORDER BY sortcolumn)
FROM data
)
SELECT
columnx,
COUNT(*)
FROM marked
GROUP BY
columnx,
grp
ORDER BY
MIN(sortcolumn)
;
You can see the method in work on SQL Fiddle.
If sortcolumn is an auto-increment integer column that is guaranteed to have no gaps, you can replace the first ROW_NUMBER() expression with just sortcolumn. But, I guess, that cannot be guaranteed in general. Besides, you might indeed want to sort on a timestamp instead of an integer.
I dont think you can do it with a single select.
You can use AdventureWorks cursor:
create table my_Strings
(
my_string varchar(50)
)
insert into my_strings values('A'),('A'),('B'),('C'),('A'),('B'),('B') -- this method will only work on SQL Server 2008
--select my_String from my_strings
declare #temp_result table(
string varchar(50),
nr int)
declare #myString varchar(50)
declare #myLastString varchar(50)
declare #nr int
set #myLastString='A' --set this with the value of your FIRST string on the table
set #nr=0
DECLARE string_cursor CURSOR
FOR
SELECT my_string as aux_column FROM my_strings
OPEN string_cursor
FETCH NEXT FROM string_cursor into #myString
WHILE ##FETCH_STATUS = 0 BEGIN
if (#myString = #myLastString) begin
set #nr=#nr+1
set #myLastString=#myString
end else begin
insert into #temp_result values (#myLastString, #nr)
set #myLastString=#myString
set #nr=1
end
FETCH NEXT FROM string_cursor into #myString
END
insert into #temp_result values (#myLastString, #nr)
CLOSE string_cursor;
DEALLOCATE string_cursor;
select * from #temp_result
Result:
A 2
B 1
C 1
A 1
B 2
Try this :
;with sample as (
select 'A' as columnx
union all
select 'A'
union all
select 'B'
union all
select 'C'
union all
select 'A'
union all
select 'B'
union all
select 'B'
), data
as (
select columnx,
Row_Number() over(order by (select 0)) id
from sample
) , CTE as (
select * ,
Row_Number() over(order by (select 0)) rno from data
) , result as (
SELECT d.*
, ( SELECT MAX(ID)
FROM CTE c
WHERE NOT EXISTS (SELECT * FROM CTE
WHERE rno = c.rno-1 and columnx = c.columnx)
AND c.ID <= d.ID) AS g
FROM data d
)
SELECT columnx,
COUNT(1) cnt
FROM result
GROUP BY columnx,
g
Result :
columnx cnt
A 2
B 1
C 1
A 1
B 2

Get N th row value in sql server

DECLARE #ActionNumber varchar(20)='EHPL-DES-SQ-1021'
set #ActionNumber=(select top 1 * from dbo.ANOSplit(#ActionNumber,'-')
order by ROW_NUMBER() OVER (ORDER BY items))
select #ActionNumber
from above query i need to return the 2ND and 3RD index from initial #ActionNumber
'EHPL-DES-SQ-1021' after Split().
format of the ActionNumber is exactly as above but DES, SQ and 1021 can change.
so i can not use ORDER BY items ASC or ORDER BY items DESC because it will order alphabetically.
above query returns 'EHPL'.how can i get DES and SQ.
You can do it with the ANOSplit function, but I would insert the result into a temp table or table variable.
As you said yourself, you can't just ORDER BY the values returned by the ANOSplit function because it will order alphabetically.
--> So you can use a temp table with an IDENTITY column, and use this for sorting:
DECLARE #ActionNumber varchar(20)='EHPL-DES-SQ-1021'
declare #tmp table
(
id int identity(1,1),
item varchar(20)
)
insert into #tmp (item)
select * from dbo.ANOSplit(#ActionNumber,'-')
select * from #tmp where id in (2,3)
The items will be inserted into the table in the exact order returned by the function, so after inserting you know that the lines with id 2 and 3 are the ones you want.
Try to use Substring with CharIndex >>>
DECLARE #ActionNumber varchar(20)='EHPL-DES-SQ-1021'
select SUBSTRING (#ActionNumber,CHARINDEX ('-',#ActionNumber,0) + 1, 3)
This isn't tested, but I think it will work:
DECLARE #ActionNumber varchar(20)='EHPL-DES-SQ-1021'
WITH nCTE AS
(
SELECT
ROW_NUMBER() OVER (ORDER BY items) AS RNum
FROM dbo.ANOSplit(#ActionNumber,'-')
)
SELECT * FROM nCTE WHERE RNum = 2 --put n here