SQL query to get Map Id on passing Id value - sql-server

Id InvID InvDate OriginalInvID FilterDesc
1 1 2017-03-01 00:00:00.000 0 First Inv Created
2 2 2017-03-06 00:00:00.000 1 Link ID
3 3 2017-03-01 00:00:00.000 0 First Inv Created
4 4 2017-03-03 00:00:00.000 3 Link ID
5 5 2017-03-06 00:00:00.000 4 Second Inv Created on top of MapID
6 6 2017-03-01 00:00:00.000 0 First Inv Created
7 7 2017-03-03 00:00:00.000 6 Link ID
8 8 2017-03-05 00:00:00.000 7 Second In Created on top of Map Id
9 9 2017-03-06 00:00:00.000 8 Second Inv Created on top of Map Id
Hi, in above table I have to pass invId and I need result with map Id who is in OriginalInvID column.
For example:
If I pass 1 then I get 1 & 2 invId
If i pass 4 then I get 3,4,5
If i pass 8 then I get 6,7,8,9 like this way

If I understood what you want, I think you wanted a query like this:
declare #InvId int = 8;
with cte(Id, InvID, InvDate, OriginalInvID) as (
select *
from yourTable
where OriginalInvID = #InvId
union all
select t.*
from yourTable t join cte on t.InvId = cte.OriginalInvID
)
select *
from cte
order by Id;

You can use a while loop to fetch father and children nodes storing them in temp tables:
declare #myInvId int
declare #father_counter int
declare #children_counter int
declare #temp table (Id int, InvID int, InvDate datetime, OriginalInvID int, FilterDesc nvarchar(100))
declare #fathers table(id int, status int)
declare #children table(id int, status int)
insert into #temp
select 1, 1, '2017-03-01 00:00:00.000', 0, 'First Inv Created'
union all select 2, 2, '2017-03-06 00:00:00.000', 1, 'Link ID'
union all select 3, 3, '2017-03-01 00:00:00.000', 0, 'First Inv Created'
union all select 4, 4, '2017-03-03 00:00:00.000', 3, 'Link ID'
union all select 5, 5, '2017-03-06 00:00:00.000', 4, 'Second Inv Created on top of MapID'
union all select 6, 6, '2017-03-01 00:00:00.000', 0, 'First Inv Created'
union all select 7, 7, '2017-03-03 00:00:00.000', 6, 'Link ID'
union all select 8, 8, '2017-03-05 00:00:00.000', 7, 'Second In Created on top of Map Id'
union all select 9, 9, '2017-03-06 00:00:00.000', 8, 'Second Inv Created on top of Map Id'
--set input parameter
set #myInvId = 8
--init counters
set #father_counter = 1
set #children_counter = 1
--insert starting node
insert into #fathers select InvID,0 from #temp where OriginalInvID = #myInvId
insert into #children select OriginalInvID,0 from #temp where InvID = #myInvId
--loop on table
while (#father_counter + #children_counter) > 0
begin
--fetch father nodes
insert into #fathers select InvID, 1 from #temp where OriginalInvID in (select id from #fathers where status=0)
--update fathers' status
update #fathers set status = 2 where status = 0
update #fathers set status = 0 where status = 1
--fetch children nodes
insert into #children select OriginalInvID, 1 from #temp where InvID in (select id from #children where status=0)
--update children's status
update #children set status = 2 where status = 0
update #children set status = 0 where status = 1
--update counters
select #father_counter = count(*) from #fathers where status = 0
select #children_counter = count(*) from #children where status = 0
end
select #myInvId
union
select id from #fathers
union
select c.id from #children c inner join #temp t on c.id = t.InvID

Related

SQL Query to calculate inventory on the first of each month over a date range

SQL Server 2012-2017. Can this be done with a CTE? Trying to avoid using a cursor.
I have a report request to return the inventory for the first of each month between dateFrom and dateTo, excluding the inventory on the dateFrom and dateTo.
Inventory is tracked by status history for each item. Each status history is coded as either in-inventory or out-of-inventory.
There could be many status history entries with an in-inventory status to track process steps. There will be another status history entry when the item is shipped, broken, lost, etc, and those are coded as out-of-inventory.
For reporting, an item is in inventory if the most recent status in status history before the reporting date is one that we code as in-inventory.
DECLARE #dateFrom dateTime, #dateTo dateTime
SET #dateFrom = '2-Nov-2017'
SET #dateTo = '20-Feb-2018 23:59:59.9'
--this proves out the dates are calculating correctly
;WITH cteDateTest AS
(
SELECT
1 roundCount,
DATEADD(M, DATEDIFF(M, 0, #dateFrom), 31) invDate --returns first of the month following dateFrom
UNION ALL
SELECT
roundCount + 1,
DATEADD(M, 1, invDate) --this one would go into the row_number join
FROM
cteDateTest
WHERE
DATEADD(M, 1, invDate) < #DateTo
)
SELECT * FROM cteDateTest
I've simplified the tables, using temp tables to be explicit that these are NOT the issue but created for ease in others understanding the problem.
CREATE TABLE #tempItems
(
id INT PRIMARY KEY,
itemDesc NVARCHAR (15) NULL,
isActive BIT
)
INSERT INTO #tempItems (id, itemDesc, isActive)
SELECT 1, 'widget 1',1 UNION ALL
SELECT 2, 'toy 2',1 UNION ALL
SELECT 3, 'something 3',1 UNION ALL
SELECT 4, 'prize 4',1
CREATE TABLE #tempStatusHistory
(
historyID INT PRIMARY KEY,
itemId INT,
itemStatus NVARCHAR (25) NULL,
statusDate DATETIME,
statusIsInInventory BIT,
)
INSERT INTO #tempStatusHistory (historyID, itemId, itemStatus, statusDate, statusIsInInventory)
SELECT 1, 1, 'in receiving', '2017-10-10',1 UNION ALL
SELECT 2, 1, 'in test', '2017-10-11',1 UNION ALL
SELECT 3, 1, 'on shelves', '2017-10-31',1 UNION ALL
SELECT 4, 2, 'in receiving', '2017-11-15',1 UNION ALL
SELECT 5, 2, 'in test', '2017-11-16',1 UNION ALL
SELECT 6, 2, 'on shelves', '2017-12-17',1 UNION ALL
SELECT 7, 2, 'sold', '2017-12-24',0 UNION ALL
SELECT 8, 3, 'in test', '2017-11-18',1 UNION ALL
SELECT 9, 3, 'in repair', '2017-12-19',1 UNION
SELECT 10, 3, 'returned to vendor', '2018-02-03',0 UNION ALL
SELECT 11, 4, 'in receiving', '2018-01-20',1 UNION ALL
SELECT 12, 4, 'on shelves', '2018-01-21',1 UNION ALL
SELECT 13, 4, 'sold', '2018-03-20',0
--select * from #tempStatusHistory
/* Per above data:
widget 1 in inventory all these months. Toy 2 in Dec 1 inventory. something 3 in Dec1, Jan 1, Feb 1 inventory, prize 4 in Feb 1 and Mar 1 inventory
Dec 1 inventory = 3 (widget 1, toy 2, something 3)
Jan 1 inventory = 2 (widget 1, something 3)
Feb 1 inventory = 3 (widget 1, something 3, prize 4)
Mar 1 inventory = 2 (widget 1, prize 4)
*/
--Our normal way of getting inventory for #dateFrom
SELECT
SH.historyId historyId, I.itemDesc
FROM
#tempItems I
JOIN
(SELECT
ROW_NUMBER () OVER (PARTITION BY itemId ORDER BY statusDate DESC) AS [Index],
itemId, historyId, statusDate, statusIsInInventory
FROM
#tempStatusHistory
WHERE
statusDate < #dateFrom) SH ON I.id = SH.itemId AND SH.[Index] = 1
WHERE
SH.statusIsInInventory = 1
--trying to pull inventory for each month between #dateFrom and #dateTo (exclusive of the end dates)
--anchor part of cte
;WITH cteInv AS
(
SELECT
1 roundCount,
DATEADD(M, DATEDIFF(M, 0, #dateFrom), 31) invDate,
DATEADD(M, 1, #dateFrom) staticDate,
1 linkField,
SH.historyId historyId,
I.itemDesc
FROM
#tempItems I
JOIN
(SELECT
ROW_NUMBER () OVER (PARTITION BY itemId ORDER BY statusDate DESC) AS [Index],
itemId, historyId, statusDate, statusIsInInventory
FROM
#tempStatusHistory
WHERE
statusDate < DATEADD(M, DATEDIFF(M, 0, #dateFrom), 31)) SH ON I.id = SH.itemId AND SH.[Index] = 1
WHERE
SH.statusIsInInventory = 1
--recursive part
UNION ALL
SELECT
roundCount + 1
, DATEADD(M,1,invDate)
, DATEADD(M,1,#dateFrom) staticDate
, 1
, SH.historyId
, I.itemDesc
FROM #tempItems I
--invDate not happy below
JOIN (SELECT ROW_NUMBER () OVER (PARTITION BY itemId ORDER BY statusDate DESC) AS [Index], itemId, historyId, statusDate, statusIsInInventory
FROM #tempStatusHistory WHERE statusDate < DATEADD(M,1,invDate)) SH ON I.id = SH.itemId AND SH.[Index] = 1
JOIN cteInv C ON I.isActive = C.linkField
WHERE DATEADD(M,1,invDate)< #dateTo AND SH.statusIsInInventory = 1
)
SELECT * from cteInv order by roundCount, invDate, itemDesc
drop table #tempItems
drop table #tempStatusHistory
the reference to invDate in the status history link shows as an error "invalid column name invDate". I can't figure a way around this. I also suspect an issue because if I replace invDate with #dateFrom in the same spot, I had expected the same inventory result for each month calculation, but it started multiplying itself.
Is cte a good solution for this? Is there a better way?
Thanks for anyone helping me on my first post here.
Addition: Expected output would be:
roundCount invDate linkField historyId itemDesc
-----------------------------------------------------------
1 2017-12-01 1 8 something 3
1 2017-12-01 1 5 toy 2
1 2017-12-01 1 3 widget 1
2 2018-01-01 1 9 something 3
2 2018-01-01 1 5 toy 2
3 2018-02-01 1 12 prize 4
3 2018-02-01 1 9 something 3
3 2018-02-01 1 3 widget 1
You are actually very very closed. Just need one OUTER APPLY
-- this is your cteDateTest query
;WITH
cteDateTest AS
(
SELECT
1 roundCount
,DATEADD(M,DATEDIFF(M,0,#dateFrom),31) invDate --returns first of the month following dateFrom
UNION ALL
SELECT
roundCount + 1
,DATEADD(M,1,invDate) --this one would go into the row_number join
FROM cteDateTest
WHERE DATEADD(M,1,invDate)< #DateTo
)
SELECT *
from cteDateTest d
OUTER APPLY
(
-- this is your normal query of getting inventory for #dateFrom
SELECT SH.historyID
, I.itemDesc
FROM #tempItems I
INNER JOIN
(
SELECT ROW_NUMBER () OVER (PARTITION BY itemId ORDER BY statusDate DESC) AS [Index],
itemId, historyID, statusDate, statusIsInInventory
FROM #tempStatusHistory
WHERE statusDate < d.invDate -- change to invDate from cteDateTest
) SH ON I.id = SH.itemId
AND SH.[Index] = 1
WHERE SH.statusIsInInventory = 1
) h

Merge Segment Ranges Based on Grouped Value

I have a table of segments with a beginning point, an ending point, and a value like so:
Bmp | Emp | SomeVal
0 1 1
1 2 1
2 3 2
3 4 2
4 5 1
I would like to merge (summarize) these records so they look like so:
Bmp | Emp | SomeVal
0 2 1
2 4 2
4 5 1
I've simplified my data set for the purpose of this question. The end result is I need unique rows grouped by the SomeVal column (in my real data set, there are about 20 columns) with the segments stitched together from Bmp to Emp, but not overlapping.
I've tried the following:
DECLARE #tbl TABLE (Bmp int, Emp int, SomeVal int)
INSERT INTO #tbl
SELECT 0, 1, 1 UNION
SELECT 1, 2, 1 UNION
SELECT 2, 3, 2 UNION
SELECT 3, 4, 2 UNION
SELECT 4, 5, 1
SELECT MIN(Bmp) AS Bmp, Max(Emp) AS Emp, SomeVal FROM #tbl
GROUP BY SomeVal
Unfortunately, it comes out like so which is wrong:
Bmp | Emp | SomeVal
0 5 1
2 4 2
My query above only works if the values of SomeVal do not repeat. How can I fix my sql?
Minimum required version is SQL 2008.
You may using ROW_NUMBER() function to correlate begin group row with end group row.
DECLARE #tbl TABLE (Bmp int, Emp int, SomeVal int)
INSERT INTO #tbl
SELECT 0, 1, 1 UNION
SELECT 1, 2, 1 UNION
SELECT 2, 3, 2 UNION
SELECT 3, 4, 2 UNION
SELECT 4, 5, 1
;WITH
[Begins] AS
(
SELECT Bmp, SomeVal, ROW_NUMBER() OVER (ORDER BY Bmp) AS OrderNumber
FROM #tbl AS [Begin]
WHERE NOT EXISTS (
SELECT 1
FROM #tbl AS [Prev]
WHERE [Prev].Emp = [Begin].Bmp AND [Prev].SomeVal = [Begin].SomeVal)
),
[Ends] AS
(
SELECT Emp, SomeVal, ROW_NUMBER() OVER (ORDER BY Emp) AS OrderNumber
FROM #tbl AS [End]
WHERE NOT EXISTS (
SELECT 1
FROM #tbl AS [Next]
WHERE [Next].Bmp = [End].Emp AND [Next].SomeVal = [End].SomeVal)
)
SELECT [Begins].Bmp, [Ends].Emp, [Begins].SomeVal
FROM [Begins]
INNER JOIN [Ends]
ON [Begins].OrderNumber = [Ends].OrderNumber;
Here is another option...
IF OBJECT_ID('tempdb..#TestData', 'U') IS NOT NULL
DROP TABLE #TestData;
CREATE TABLE #TestData (
Bmp INT NOT NULL PRIMARY KEY CLUSTERED,
Emp INT NOT NULL,
SomeVal INT NOT NULL
);
INSERT #TestData (Bmp, Emp, SomeVal)
SELECT 0, 1, 1 UNION ALL
SELECT 1, 2, 1 UNION ALL
SELECT 2, 3, 2 UNION ALL
SELECT 3, 4, 2 UNION ALL
SELECT 4, 5, 1;
--==============================================
WITH
cte_GroupStart AS (
SELECT
td.Bmp,
td.Emp,
td.SomeVal,
GroupStart = CASE WHEN td.SomeVal = LAG(td.SomeVal, 1) OVER (ORDER BY td.Bmp) THEN NULL ELSE ROW_NUMBER() OVER (ORDER BY td.Bmp) END
FROM
#TestData td
),
cte_FillGroup AS (
SELECT
gs.Bmp,
gs.Emp,
gs.SomeVal,
AggGroup = MAX(gs.GroupStart) OVER (ORDER BY gs.Bmp) -- ROWS UNBOUNDED PRECEDING is implied and should work as expected 2008
FROM
cte_GroupStart gs
)
SELECT
Bmp = MIN(fg.Bmp),
Emp = MAX(fg.Emp),
fg.SomeVal
FROM
cte_FillGroup fg
GROUP BY
fg.AggGroup,
fg.SomeVal
ORDER BY
fg.AggGroup;

Update a column with sequence numbers without using ROW_NUMBER() in SQL Server

I have a table like this and I have difficulty updating it
code descd slnum
---------------------
10 a 0
10 b 0
12 c 0
12 d 0
11 e 0
12 f 0
I have to update this table like this without using ROW_NUMBER() only by using if else loops how can I do that?
code descd slnum
----------------------
10 a 1
10 b 2
12 c 1
12 d 2
11 e 1
12 f 3
For SQL 2012+
;WITH rownum(code, descd, slnum) AS (
SELECT 10, 'a', 0
UNION SELECT 10, 'b', 0
UNION SELECT 12, 'c', 0
UNION SELECT 12, 'd', 0
UNION SELECT 11, 'e', 0
UNION SELECT 12, 'f', 0
) SELECT code, descd, COUNT(*) OVER (PARTITION BY code ORDER BY code
ROWS UNBOUNDED PRECEDING) FROM rownum o ORDER BY descd
Try this
DECLARE #Tbl TABLE(code VARCHAR(5), descd VARCHAR(5), slnum int)
INSERT INTO #Tbl
( code, descd)
VALUES
('10', 'a'),
('10', 'b'),
('12', 'c'),
('12', 'd'),
('11', 'e'),
('12', 'f')
DECLARE #TempTable TABLE(code VARCHAR(5), descd VARCHAR(5), RowId int)
DECLARE #RowId INT = 1
WHILE #RowId <= (SELECT COUNT(*) FROM #Tbl)
BEGIN
INSERT INTO #TempTable
SELECT TOP 1
T.code ,
T.descd ,
#RowId
FROM
#Tbl T LEFT JOIN
#TempTable L ON L.code = T.code AND L.descd = T.descd
WHERE
L.code IS null
ORDER BY
T.code,
T.descd
UPDATE #Tbl
SET slnum = (SELECT COUNT(1) FROM #TempTable A WHERE [#Tbl].Code = A.Code)
WHERE
[#Tbl].Code = (SELECT TOP 1 Y.code FROM #TempTable Y WHERE RowId = #RowId) AND
[#Tbl].descd = (SELECT TOP 1 Y.descd FROM #TempTable Y WHERE RowId = #RowId)
SET #RowId += 1
END
SELECT * from #Tbl
Result:
code descd slnum
10 a 1
10 b 2
12 c 1
12 d 2
11 e 1
12 f 3
;WITH rownum(code, descd, slnum) AS (
SELECT 10, 'a', 0
UNION SELECT 10, 'b', 0
UNION SELECT 12, 'c', 0
UNION SELECT 12, 'd', 0
UNION SELECT 11, 'e', 0
UNION SELECT 12, 'f', 0
) SELECT code, descd, COUNT(*) OVER (PARTITION BY code ORDER BY code
) FROM rownum ORDER BY descd

Multiple rows by number in cell

I have table multiple:
CREATE TABLE multiple
(
id int,
Param1 int,
Param2 int,
Param3 int,
Param4 int
);
INSERT INTO multiple VALUES
(1, 1, 2, 3, 0),
(2, 3, 1, 0, 0),
(3, 1, 2, 2, 1);
SELECT * FROM multiple;
I want to achieve with t-SQL new table like this: (Number of rows = Value in Param attributes)
Counter ID TYPE
1 1 Param1
2 1 Param2
3 1 Param2
4 1 Param3
5 1 Param3
6 1 Param3
7 2 Param1
8 2 Param1
9 2 Param1
10 2 Param2
11 3 Param1
12 3 Param2
13 3 Param2
14 3 Param3
15 3 Param3
16 3 Param4
I adder here this text because I have error in editor that it looks like my post is mostly code;
Hey you can use following code to achieve your goal :
CREATE TABLE multiple
(
id int,
Param1 int,
Param2 int,
Param3 int,
Param4 int
);
INSERT INTO multiple VALUES
(1, 1, 2, 3, 0),
(2, 3, 1, 0, 0),
(3, 1, 2, 2, 1);
SELECT * FROM multiple;
CREATE TABLE multiple1
(
ID int,
TYPE VARCHAR(10)
)
DECLARE #id int = 0
WHILE (#id<=(SELECT Count(id) FROM multiple))
BEGIN
DECLARE #Count int = 0
DECLARE #Count1 int = 0
DECLARE #Count2 int = 0
DECLARE #Count3 int = 0
WHILE(#Count<(SELECT Param1 FROM multiple where id = #id))
BEGIN
INSERT INTO multiple1 VALUES ((Select id from multiple where id = #id),'Param1')
SET #Count = #Count + 1
END
WHILE(#Count1<(SELECT Param2 FROM multiple where id = #id))
BEGIN
INSERT INTO multiple1 VALUES ((Select id from multiple where id = #id),'Param2')
SET #Count1 = #Count1 + 1
END
WHILE(#Count2<(SELECT Param3 FROM multiple where id = #id))
BEGIN
INSERT INTO multiple1 VALUES ((Select id from multiple where id = #id),'Param3')
SET #Count2 = #Count2 + 1
END
WHILE(#Count3<(SELECT Param4 FROM multiple where id = #id))
BEGIN
INSERT INTO multiple1 VALUES ((Select id from multiple where id = #id),'Param4')
SET #Count3 = #Count3 + 1
END
SET #id = #id + 1
END
SELECT * FROM multiple1
Have you heard of "tally tables"? They'll make quick work of this. Here is an example solution that uses one. It works by joining on each column where the number is <= the value in the Param attributes, giving you the number of rows you need. Then the id and type are selected.
Note this example uses a tally table that only counts up to 256, so if you need more, you can add an extra level - just be sure to put your tally table into a temp table otherwise it will go from taking a really fast time to execute, to being a really slow time.
CREATE TABLE multiple
(
id int,
Param1 int,
Param2 int,
Param3 int,
Param4 int
);
INSERT INTO multiple VALUES
(1, 1, 2, 3, 0),
(2, 3, 1, 0, 0),
(3, 1, 2, 2, 1);
SELECT * FROM multiple;
-- Tally table known as a "Ben-Gan" style Tally
WITH lv0 AS (SELECT 0 g UNION ALL SELECT 0)
,lv1 AS (SELECT 0 g FROM lv0 a CROSS JOIN lv0 b) -- 4
,lv2 AS (SELECT 0 g FROM lv1 a CROSS JOIN lv1 b) -- 16
,lv3 AS (SELECT 0 g FROM lv2 a CROSS JOIN lv2 b) -- 256
,Tally (n) AS (SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) FROM lv3)
SELECT
ROW_NUMBER() OVER (ORDER BY Id) [Counter],
Id,
[Type]
FROM
(
SELECT Id, 'Param1' AS [Type] FROM multiple INNER JOIN Tally ON multiple.Param1 >= Tally.n UNION ALL
SELECT Id, 'Param2' AS [Type] FROM multiple INNER JOIN Tally ON multiple.Param2 >= Tally.n UNION ALL
SELECT Id, 'Param3' AS [Type] FROM multiple INNER JOIN Tally ON multiple.Param3 >= Tally.n UNION ALL
SELECT Id, 'Param4' AS [Type] FROM multiple INNER JOIN Tally ON multiple.Param4 >= Tally.n
) [single]

How to find the parent ID in a return table

I have following table:
ID ParentID
1 NULL
2 1
3 2
4 NULL
5 4
6 5
7 3
I want to find the first ID of a specific child ID.
Example: ID=7 and the result is 1
ID=6 and the result is 4
How to do it?
You need to do a bit of recursive CTE magic to solve this one.
Given the data in a table variable thusly:
declare #data table(id int, parentid int)
insert into #data
select 1, null
union select 2,1
union select 3,2
union select 4, null
union select 5,4
union select 6,5
union select 7,3
The following should do the trick:
;with recursiveTable as
(
select d.id, d.parentId, 0 as depth
from #data d
where d.id=6 -- Parameterize this
union all
select d.id, d.parentid, r.depth-1
from #data d
inner join recursiveTable r
on d.id = r.parentId
)
select top 1 id
from recursiveTable
order by depth
Plugging 6 in as above returns 4. Changing this to 7 returns 1 as requested.
Try this:
CREATE TABLE childpar(ID int,ParentID int)
INSERT INTO childpar
values(1,NULL),
(2, 1),
(3, 2),
(4, NULL),
(5, 4),
(6, 5),
(7, 3)
DECLARE #inpyID int
SET #inpyID=7
;WITH CTE1 as (
select * from childpar where id=#inpyID
union all
select c2.* from CTE1 c1 inner join childpar c2 on c1.ParentID = c2.ID
)
select top 1 id from CTE1 order by id asc

Resources