Find the specific number of specific value in SQL - 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

Related

Extract Data from a large string to table using 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.

Selecting data from table where sum of values in a column equal to the value in another column

Sample data:
create table #temp (id int, qty int, checkvalue int)
insert into #temp values (1,1,3)
insert into #temp values (2,2,3)
insert into #temp values (3,1,3)
insert into #temp values (4,1,3)
According to data above, I would like to show exact number of lines from top to bottom where sum(qty) = checkvalue. Note that checkvalue is same for all the records all the time. Regarding the sample data above, the desired output is:
Id Qty checkValue
1 1 3
2 2 3
Because 1+2=3 and no more data is needed to show. If checkvalue was 4, we would show the third record: Id:3 Qty:1 checkValue:4 as well.
This is the code I am handling this problem. The code is working very well.
declare #checkValue int = (select top 1 checkvalue from #temp);
declare #counter int = 0, #sumValue int = 0;
while #sumValue < #checkValue
begin
set #counter = #counter + 1;
set #sumValue = #sumValue + (
select t.qty from
(
SELECT * FROM (
SELECT
ROW_NUMBER() OVER (ORDER BY id ASC) AS rownumber,
id,qty,checkvalue
FROM #temp
) AS foo
WHERE rownumber = #counter
) t
)
end
declare #sql nvarchar(255) = 'select top '+cast(#counter as varchar(5))+' * from #temp'
EXECUTE sp_executesql #sql, N'#counter int', #counter = #counter;
However, I am not sure if this is the best way to deal with it and wonder if there is a better approach. There are many professionals here and I'd like to hear from them about what they think about my approach and how we can improve it. Any advice would be appreciated!
Try this:
select id, qty, checkvalue from (
select t1.*,
sum(t1.qty) over (partition by t2.id) [sum]
from #temp [t1] join #temp [t2] on t1.id <= t2.id
) a where checkvalue = [sum]
Smart self-join is all you need :)
For SQL Server 2012, and onwards, you can easily achieve this using ROWS BETWEEN in your OVER clause and the use of a CTE:
WITH Running AS(
SELECT *,
SUM(qty) OVER (ORDER BY id
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS RunningQty
FROM #temp t)
SELECT id, qty, checkvalue
FROM Running
WHERE RunningQty <= checkvalue;
One basic improvement is to try & reduce the no. of iterations. You're incrementing by 1, but if you repurpose the logic behind binary searching, you'd get something close to this:
DECLARE #RoughAverage int = 1 -- Some arbitrary value. The closer it is to the real average, the faster things should be.
DECLARE #CheckValue int = (SELECT TOP 1 checkvalue FROM #temp)
DECLARE #Sum int = 0
WHILE 1 = 1 -- Refer to BREAK below.
BEGIN
SELECT TOP (#RoughAverage) #Sum = SUM(qty) OVER(ORDER BY id)
FROM #temp
ORDER BY id
IF #Sum = #CheckValue
BREAK -- Indicating you reached your objective.
ELSE
SET #RoughAverage = #CheckValue - #Sum -- Most likely incomplete like this.
END
For SQL 2008 you can use recursive cte. Top 1 with ties limits result with first combination. Remove it to see all combinations
with cte as (
select
*, rn = row_number() over (order by id)
from
#temp
)
, rcte as (
select
i = id, id, qty, sumV = qty, checkvalue, rn
from
cte
union all
select
a.id, b.id, b.qty, a.sumV + b.qty, a.checkvalue, b.rn
from
rcte a
join cte b on a.rn + 1 = b.rn
where
a.sumV < b.checkvalue
)
select
top 1 with ties id, qty, checkvalue
from (
select
*, needed = max(case when sumV = checkvalue then 1 else 0 end) over (partition by i)
from
rcte
) t
where
needed = 1
order by dense_rank() over (order by i)

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

SQL Server : find break in dates to show unique rows

I have developed a solution to a problem (I think), and I am keen to see if there is a better way around this, as I can't help but feel there is a better way.
The problem: a company name, and a move in date are shown. The company could leave, another company come in and then the original company could come back. To make this problem a bit tricky, there may be rogue dates for a company in there. Best way to explain it is via the table:
Table example
What I need to extract, is only the first time a company moved in, until it is broken by a different company and so on.
The code I have is:
IF OBJECT_ID('tempdb..#tmpData') IS NOT NULL
DROP TABLE #tmpData
GO
CREATE TABLE #tmpData
(
COMPANY_NAME NVARCHAR(30),
DATE_MOVED_IN DATETIME,
ID INT IDENTITY(1,1),
UNIQUE_ID INT
)
INSERT INTO #tmpData(COMPANY_NAME, DATE_MOVED_IN)
SELECT 'ABC LTD','01/01/2017' UNION ALL
SELECT 'ABC LTD','01/04/2017' UNION ALL
SELECT 'XYZ LTD','01/10/2017' UNION ALL
SELECT 'ABC LTD','01/12/2017';
DECLARE #intMinID INT,
#intMaxID INT,
#strNextComp NVARCHAR(50),
#strCurrentComp NVARCHAR(50),
#strPreviousComp NVARCHAR(50),
#intMaxUID INT;
SELECT
#intMinID = MIN(TD.ID),
#intMaxID = MAX(TD.ID)
FROM
#tmpData AS TD
UPDATE TD
SET TD.UNIQUE_ID = 1
FROM #tmpData AS TD
WHERE TD.ID = #intMinID;
WHILE #intMinID <= #intMaxID
BEGIN
SELECT
#strCurrentComp = TD.COMPANY_NAME
FROM
#tmpData AS TD
WHERE
TD.ID = #intMinID;
SELECT
#strNextComp = TD.COMPANY_NAME
FROM
#tmpData AS TD
WHERE
TD.ID = (#intMinID + 1)
SELECT
#strPreviousComp = CASE WHEN EXISTS (SELECT 1
FROM #tmpData AS TD
WHERE TD.ID = (#intMinID - 1))
THEN TD.COMPANY_NAME
ELSE 'No Company Exists'
END
FROM
#tmpData AS TD
WHERE
TD.ID = (#intMinID - 1)
SELECT
#intMaxUID = MAX(TD.UNIQUE_ID)
FROM
#tmpData AS TD
IF(#strPreviousComp IS NULL)
PRINT 'Nothing to do'
ELSE IF((#strCurrentComp <> #strNextComp) AND (#strCurrentComp = #strPreviousComp))
BEGIN
UPDATE TD
SET TD.UNIQUE_ID = #intMaxUID
FROM #tmpData AS TD
WHERE TD.ID = #intMinID;
END
ELSE
BEGIN
UPDATE TD
SET TD.UNIQUE_ID = #intMaxUID + 1
FROM #tmpData AS TD
WHERE TD.ID = #intMinID;
END
SET #intMinID = #intMinID + 1;
END
SELECT
COMPANY_NAME, MIN(DATE_MOVED_IN) AS DATE_MOVED_IN
FROM
#tmpData
GROUP BY
COMPANY_NAME, UNIQUE_ID
ORDER BY
UNIQUE_ID ASC
Any suggestions on how to do this in a more efficient way, or if any errors are spotted, feedback is very much appreciated.
Thanks,
Leo
Lag() should do it...
with CTE as
(
select Company_Name, Date_Moved_in, lag(Company_Name) over (order by Date_Moved_In) as PrevComp
from #TempTable
)
select Company_Name, Date_Moved_In
from CTE
where PrevComp <> Company_Name
or PrevComp is null
You can use the difference in row number logic to classify continuous dates by company into one group. Run the inner query alone to see how groups are assigned.
Thereafter, just group by the company and previously classified group to get the first date moved in.
select company_name,min(date_moved_in)
from (
select t.*,
row_number() over(order by date_moved_in)
-row_number() over(partition by company_name order by date_moved_in) as grp
from #tmpData t
) x
group by company_name,grp

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