How to get records that between m records back and n records forward from a reference row - Non-sequential data - sql

My scenario is as follows:
I have a reference record, say, ProductId = 1
The records each have a non-unique ItemTypeId
I would like to fetch records that exists between the following points
START POINT being 2 records BACKWARDS of type ItemTypeId = 1, from record of ProductId =1
END POINT being 3 records FORWARDS of type ItemTypeId = 1, from record of ProductId = 1
The query should get ALL data between the two points, inclusively
Here's a picture that illustrates this better than my words:
How would I structure my query to do this?
Any better way to do it without temp tables?
Thank-you!

Note that for this to work at all, you need that record ID to be an actual column in the table. Rows have no inherent order in a table.
With that in place, you can use LAG and LEAD to get what you want:
CREATE TABLE #t
(
RecordId INT IDENTITY(1,1),
ProductId INT,
ItemType INT
);
INSERT INTO #t(ProductId, ItemType)
VALUES
(5,1),(3,1),(7,3),(6,1),(2,7),
(1,1),(7,3),(8,1),(10,3),(9,5),
(11,1),(19,1),(17,4),(13,3);
WITH c1 AS
(
SELECT ProductId,
RecordId,
LAG(RecordId,2) OVER (ORDER BY RecordId) AS Back2,
LEAD(RecordId,3) OVER (ORDER BY RecordId) AS Forward3
FROM #t
WHERE ItemType = (SELECT ItemType FROM #t WHERE ProductId = 1)
),c2 AS
(
SELECT c1.Back2, c1.Forward3 FROM c1
WHERE c1.ProductId = 1
)
SELECT #t.*
FROM #t
INNER JOIN c2 ON #t.RecordId BETWEEN c2.Back2 AND c2.Forward3;

If you wanna do without using temp tables as you ask, the following solution work.
But it is not very nice i agree.
Well this is what i done :
CREATE DATABASE TEST;
USE TEST
CREATE TABLE PRODUCT
(
ProductId INT,
ItemType INT
)
INSERT INTO PRODUCT
VALUES
(5,1),
(3,1),
(7,3),
(6,1),
(2,7),
(1,1),
(7,3),
(8,1),
(10,3),
(9,5),
(11,1),
(19,1),
(17,4),
(13,3)
DECLARE product_cursor CURSOR FOR
SELECT * FROM PRODUCT;
OPEN product_cursor
DECLARE
#ProductId INT,
#ItemId INT,
#END_FETCH INT,
#countFrom INT,
#countTo INT
DECLARE #TableResult TABLE
(
RProductId INT,
RItemId INT
)
FETCH NEXT FROM product_cursor
INTO #ProductId, #ItemId
SET #END_FETCH = 0
SET #countFrom = 0
SET #countTo = 0
WHILE ##FETCH_STATUS = 0 AND #END_FETCH = 0
BEGIN
IF #ItemId = 1 AND (#countFrom = 0 AND #countTo = 0)
BEGIN
SET #countFrom = 3
SET #countTo = 3
END
ELSE
BEGIN
IF #countFrom > 0
BEGIN
--SELECT 'INSERTION : ' ,#ProductId,#ItemId
INSERT INTO #TableResult VALUES(#ProductId, #ItemId)
IF #ItemId = 1
BEGIN
SET #countFrom -= 1
--SELECT 'CountFrom : ', #countFrom
END
END
ELSE
BEGIN
IF #countTo > 0
BEGIN
--SELECT 'INSERTION : ' ,#ProductId,#ItemId
INSERT INTO #TableResult VALUES(#ProductId, #ItemId)
IF #ItemId = 1
BEGIN
SET #countTo -= 1
--SELECT 'CountTO : ', #countTo
END
END
ELSE
BEGIN
SET #END_FETCH = 1
END
END
END
FETCH NEXT FROM product_cursor
INTO #ProductId, #ItemId
END
CLOSE product_cursor
DEALLOCATE product_cursor
SELECT * FROM #TableResult
And this is the result i got :
RProductId RItemId
3 1
7 3
6 1
2 7
1 1
7 3
8 1
10 3
9 5
11 1
19 1
But i prefer the solution of #James Casey.
By the way, why won't you use temp table ?

Related

How to create loop based on value of row?

I have problem when I use my query bellow to have a looping inside the cursor.
data in table1 will be like this:
id | data
----|---------
A | 4
B | 2
C | 5
the result in table2 should be like this:
id | data
----|---------
A | 1
A | 1
A | 1
A | 1
B | 1
B | 1
C | 1
C | 1
C | 1
C | 1
C | 1
I have SQL query with cursor like this:
DECLARE #table2 table ( id VARCHAR(500), data INTEGER)
DECLARE Cur CURSOR FOR
SELECT id, data FROM table1
OPEN Cur
WHILE ( ##FETCH_STATUS = 0 )
BEGIN
DECLARE #LoopNum INTEGER
DECLARE #tempID VARCHAR(255)
DECLARE #tempDATA INTEGER
FETCH NEXT FROM Cur INTO #tempID, #tempDATA
set #LoopNum = 0
WHILE #LoopNum < #tempDATA
BEGIN
INSERT INTO table2 (id, data)
VALUES( #tempID, 1)
SET #LoopNum = #LoopNum + 1
END
END
CLOSE Cur
DEALLOCATE Cur
SELECT * FROM table2
but the query didn't work. is there something wrong with my query?
Thank you.
Use this query to the expected result.
CREATE TABLE #test
(id CHAR(1),data INT)
INSERT #test VALUES ('A',4)
INSERT #test VALUES('B',2)
INSERT #test VALUES('C',5);
SELECT s.id, 1 AS data
FROM #test s
INNER JOIN
master.dbo.spt_values t ON t.type='P'
AND t.number BETWEEN 1 AND s.data
Note: Refer this Why (and how) to split column using master..spt_values?
You actually don't need a loop
IF OBJECT_ID('TEMPDB..#TEMP') IS NOT NULL
DROP TABLE #TEMP
SELECT 'A' AS ID, 4 AS DATA
INTO #TEMP UNION
SELECT 'B', 2 UNION
SELECT 'C', 5
;WITH CTE AS
(
SELECT 1 AS NUMBER
UNION ALL
SELECT NUMBER + 1
FROM CTE
WHERE NUMBER < 100
)
SELECT T.ID, 1
FROM CTE C
INNER JOIN #TEMP T
ON C.NUMBER <= T.DATA
ORDER BY T.ID
Carefull that if you want ot generate a large set of numbers in the CTE it may become slower.
Use a Recursive CTE which will help you to loop through the records.
CREATE TABLE #test
(id CHAR(1),data INT)
INSERT #test
VALUES ('A',4),('B',2),('C',5);
WITH cte
AS (SELECT 1 AS da,id,data
FROM #test a
UNION ALL
SELECT da + 1,id,data
FROM cte a
WHERE da < (SELECT data
FROM #test b
WHERE a.id = b.id))
SELECT id,
1 AS data
FROM cte
ORDER BY id
i used two loops
1. for each row
2. for number for duplicate insert
SET NOCOUNT on;
DECLARE #t table(row int IDENTITY(1,1),id varchar(10),data int)
INSERT INTO #t
SELECT * from xyz
DECLARE #x table(id varchar(10),data int) --table to hold the new data
DECLARE #i int=(SELECT count (*) from xyz) --number of rows main table
DECLARE #y int --number of duplicate
DECLARE #p int=1 --number of rows
WHILE #i!=0 --loop until last row of main table
BEGIN
SET #y=(SELECT data FROM #t WHERE row=#p) --set #y for number of 'row duplicate'
WHILE #y!=0
BEGIN
INSERT INTO #x
SELECT id,1
FROM #t
WHERE row=#p
SET #y=#y-1
END
SET #p=#p+1
SET #i=#i-1
END
SELECT * FROM #x

sql how to get consecutive appearance of value

suppose I have a column 'value', which can appear multiple times in a table with another column 'result' which can be either 1 or 0. I would like to search for consecutive 1s (ie result = 1) until the count reaches 4, then I can select value. given the result sets below:
-result set a)
value Result
----- ------
A 1
A 1
A 1
A 0
-result set b)
value Result
----- ------
A 1
A 1
A 1
A 1
result set b meets the condition and therefore value A is selected. How do I go about this ? Thanks.
This is the query: (usually this query is to detect double record in a table, but probably meet your demand).
select value, result, count(value) as [Result Sum]
from #temp
where result = 1
group by value, result
having count(value) >3
This is the Result
value result Result Sum
----- ----------- -----------
A 1 4
UPDATED:
This is the data example in my temporary table (#temp)
value result
----- -----------
A 1
A 1
A 1
A 0
A 1
D 1
D 1
D 1
D 1
B 1
B 1
C 1
C 1
C 1
C 1
From The example data C and D are the valid values
Declare #temp2 table
(
value nvarchar(5)
)
declare #value nvarchar(5), #result int, #total int, #flag bit, #tempValue nvarchar(5)
DECLARE myCursor CURSOR FOR
SELECT value, result
FROM #temp
set #flag = 1
set #tempValue = ''
OPEN myCursor;
FETCH NEXT FROM myCursor into #value, #result;
WHILE ##FETCH_STATUS = 0
BEGIN
--logic here
if (#tempValue <> #value and #result = 1) or #flag = 1
begin
set #tempValue = #value
set #total = 1
set #flag = 0
end
else --#tempvalue = #value
begin
if #result = 1
set #total = #total + 1
else --#result = 0
set #flag = 1
if #total >3 --valid value has reached 4 consecutive result =1
begin
set #flag = 1
insert into #temp2 values (#value)
end
end
FETCH NEXT FROM myCursor into #value, #result;
END;
CLOSE myCursor;
DEALLOCATE myCursor;
select * from #temp2
This is the Result of the loop (table #temp2)
value
-----
D
C
(2 row(s) affected)
You can do this in a select statement. You can find groups of items in a row by using row_number() assuming you have an id. SQL tables are inherently unordered, so you need an id or creation date or something to specify the ordering. Here is the SQL:
select value
from (select t,
(row_number() over (partition by value order by id) -
row_number() over (partition by value, results order by id)
) as grp
from table t
) t
group by value, result, grp
having count(*) > 3 and result = 1;

Need help to write a SQL query?

I have a SQL table with column which contain string like 'type|type1|type2|type3|type4'.
I need to select the string, id. Split string and insert to another table. First item should be default and I need to get identity of it and insert to another table with the type value.
Please help me to create T-SQL query to accomplish desirable result.
Example
Step 1 To select Items from Table 1.
Step 2 Split to array
Step 3 Insert to Table 2 (first item will be default)
Step 4 Update Table 1 with default Type Value based on TypeID and Default True
Table 1
ID Items Default
--------------------------------------------------
1 type|type1|type2|type3|type4
2 type|type1|type2|type3|type4
Table 2
ID TypeID Type Default(bool)
--------------------------------------------------
1 1 type1 1
2 1 type2 0
Using the split function from Arnold Fribble's answer on this thread
Create FUNCTION dbo.Split (#sep char(1), #s varchar(512))
RETURNS table
AS
RETURN (
WITH Pieces(pn, start, stop) AS (
SELECT 1, 1, CHARINDEX(#sep, #s)
UNION ALL
SELECT pn + 1, stop + 1, CHARINDEX(#sep, #s, stop + 1)
FROM Pieces
WHERE stop > 0
)
SELECT pn,
SUBSTRING(#s, start, CASE WHEN stop > 0 THEN stop-start ELSE 512 END) AS s
FROM Pieces
)
GO
You can write the following (I made some guesses about the ID field in table2 and what the defaultTypeID should be in table1 but you should be able to adjust that)
CREATE TABLE #table1 (
id INT,
items VARCHAR(MAX),
defaulttypeid INT
)
CREATE TABLE #table2 (
id INT IDENTITY,
typeid INT,
TYPE VARCHAR(5),
isdefault BIT
)
INSERT INTO #table1
VALUES (1,
'type|type1|type2|type3|type4',
NULL),
(2,
'type|type1|type2|type3|type4',
NULL)
INSERT INTO #table2
(typeid,
TYPE,
isdefault)
SELECT id typeid,
Rtrim(split.s) AS item,
CASE
WHEN ( split.pn = 1 ) THEN 1
ELSE 0
END AS isdefault
FROM #table1
CROSS APPLY test.dbo.Split('|', items) AS split
UPDATE #table1
SET defaulttypeid = t2.ID
FROM #table1 t1
INNER JOIN #table2 t2
ON t1.id = t2.typeid
AND t2.isdefault = 1
DROP TABLE #table1
DROP TABLE #table2
This outputs
ID Items DefaultTypeID
----------- ------------------------------ -------------
1 type|type1|type2|type3|type4 1
2 type|type1|type2|type3|type4 6
ID TypeID Type IsDefault
----------- ----------- ----- ---------
1 1 type 1
2 1 type1 0
3 1 type2 0
4 1 type3 0
5 1 type4 0
6 2 type 1
7 2 type1 0
8 2 type2 0
9 2 type3 0
10 2 type4 0
Though I totally disagree with the use of cursors I can't think of another way. This solution isn't tested but It looks like it should be ok.
DECLARE #pos INT
DECLARE #id INT
DECLARE #string VARCHAR(MAX)
DECLARE #default INT
DECLARE #substring VARCHAR(MAX)
DECLARE tempCursor CURSOR FOR
SELECT id, string
FROM table_name
OPEN tempCursor;
FETCH NEXT FROM tempCursor
INTO #id, #string
WHILE ##FETCH_STATUS = 0
BEGIN
SET #default = 1
SET #pos = CHARINDEX('|', #string)
WHILE (#pos <> 0)
BEGIN
SET #substring = SUBSTRING(#string, 1, #pos - 1)
INSERT INTO table_name2(typeid, type, default) VALUES (#id, #substring, #default)
SET #string = substring(#string, #pos+1, LEN(#string))
SET #pos = charindex('|', #string)
SET #default = 0
END
FETCH NEXT FROM tempCursor
INTO #id, #string
END
CLOSE EWSCursor;
DEALLOCATE EWSCursor;
Hope this helps.

TSQL - While in While code

Can someone please tell me why this wont work. I want to loop inside a loop .....
BEGIN
SET NOCOUNT ON;
Declare #TempLocations Table (PK int Identity(1,1) not null Primary key, LocationID Int)
Declare #TempItems Table (PK1 int Identity(1,1) not null Primary key, ItemID int)
Declare #TempTable Table (ID Int Identity(1,1), LocationID int, ItemID int)
Declare #MaxLocationID int,
#MaxItemID Int,
#LocationID int,
#ItemID int
-- Load "Can be sold from" Locations into Temp Table
Insert Into #TempLocations (LocationID)
Select LocationID from WMS.Locations
Where CanBeSoldFrom = 'Checked'
Set #MaxItemID = (Select MAX(PK1) From #TempItems)
Set #LocationID = 1
-- Load "IsActive" Items into Temp Table
Insert Into #TempItems (ItemID)
Select ItemID from IMS.ItemDetails
Where IsActive = 'Checked'
Set #MaxLocationID = (Select MAX(PK) From #TempLocations)
Set #ItemID = 1
--Main Code
While #LocationID <= #MaxLocationID
Begin
While #ItemID <= #MaxItemID
Begin
Insert into #TempTable (LocationID, ItemID)
Values (#LocationID, #ItemID)
Set #ItemID = #ItemID + 1
end
Set #LocationID = #LocationID + 1
End
Select * from #TempTable
END
The result I am Tryinig to get is this
#tempTable =
LocationID = 1
ItemID = 1
ItemID = 2
ItemID = 3
ItemID = 4
LocationID = 2
ItemID = 1
ItemID = 2
ItemID = 3
ItemID = 4
and so on ......
This shouldn't be done in a procedural code at all. Use pure SQL and let the DB engine do it's job, it will perform much better, and less code = less bugs. I'm not sure I completely understand what results you want, but I think this does it:
select
LocationID,
ItemID
from
(
Select LocationID from WMS.Locations
Where CanBeSoldFrom = 'Checked'
)
cross join
(
Select ItemID from IMS.ItemDetails
Where IsActive = 'Checked'
)
order by
LocationID,
ItemID
Your query selects the #MaxItemID before anything is filled into #TempItems. Therefor #MaxItemID is null. You have to switch the Statements Set #MaxLocationID = (Select MAX(PK) From #TempLocations) and Set #MaxItemID = (Select MAX(PK1) From #TempItems).
I agree with Jeremy though, it would be better to do that with set-based-programming.

How to Split Sql Int Value into Multiple Rows

Lets say I have the following table in MS SQL 2000
Id | description | quantity |
-------------------------------
1 my desc 3
2 desc 2 2
I need to display multiple rows based on the quantity, so I need the following output:
Id | description | quantity |
-----------------------------
1 my desc 1
1 my desc 1
1 my desc 1
2 desc 2 1
2 desc 2 1
Any ideas how to accomplish this?
This works just fine, no need for any cursors on this one. It may be possible to fangle something out without a number table as well.
Note if going for this kind of solution I would keep a number table around and not recreate it every time I ran the query.
create table #splitme (Id int, description varchar(255), quantity int)
insert #splitme values (1 ,'my desc', 3)
insert #splitme values (2 ,'desc 2', 2)
create table #numbers (num int identity primary key)
declare #i int
select #i = max(quantity) from #splitme
while #i > 0
begin
insert #numbers default values
set #i = #i - 1
end
select Id, description, 1 from #splitme
join #numbers on num <= quantity
DECLARE #Id INT
DECLARE #Description VARCHAR(32)
DECLARE #Quantity INT
DECLARE #Results TABLE (Id INT, [description] VARCHAR(32), quantity INT)
DECLARE MyCursor CURSOR FOR
SELECT Id, [description], quantity
FROM
MyTable
OPEN MyCursor
FETCH NEXT FROM MyCursor INTO #Id, #Description, #Quantity
WHILE ##FETCH_STATUS = 0
BEGIN
WHILE #Quantity > 0
BEGIN
INSERT INTO #Results (
Id,
[description],
quantity
) VALUES (
#Id,
#Description,
1
)
SET #Quantity = #Quantity - 1
END
FETCH NEXT FROM MyCursor INTO #Id, #Description, #Quantity
END
CLOSE MyCursor
DEALLOCATE MyCursor
SELECT *
FROM
#Results
By the way, cursors are generally considered evil. So I will both recommend against something like this, and thank everyone in advance for their flames ;) (But it should work)
Any one looking for a CTE based solution, here is one:
create table test_splitme (Id int, description varchar(255), quantity int)
insert test_splitme values (1 ,'my desc', 3)
insert test_splitme values (2 ,'desc 2', 2)
;
--TSQL solution
with splitcte as(
select Id,description, 1 as quantity, quantity as orig_quantity, 1 as cnt
from test_splitme
union all
select Id, description, quantity, orig_quantity,cnt+1
from splitcte
where cnt < orig_quantity
)
select Id, description, quantity
from splitcte
order by 1