updating a record based on parent id - database

i have a table
intProductID vchProductName intParentCategory intCategoryId
1 Post Cards NULL 3
2 Packaging Boxe NULL 5
3 12- Page Booklets 1 NULL
4 16- Page Booklets 12 NULL
i want to update intcategory id of which rows which have intcategory id is null also i want to update intCategoryId with a value which its parent (intParentCategory) have.
for example intproductid 3 have intparentid 1 so i want intcategoryid 3 for intproductid 3 which its parent have.

update t1
set intcategoryID = t2.intCategoryId
from <table> t1
join <table> t2
on t1.intParentCategory = t2.intProductID
where t1.intCategoryId is null
Here is a solution with test table that will update the entire tree for parent hierarchies
declare #t table(intProductID int, vchProductName varchar(20), intParentCategory int, intCategoryId int)
insert #t values(1, 'Post Cards',NULL,3),
(2,'Packaging Boxe', NULL,5),
(3,'12- Page Booklets', 1,NULL),
(4,'16- Page Booklets',12, NULL),
(5,'tst', 3, null)
--select intCategoryId, intProductID
--from #t where intCategoryId is not null and intProductID is not null
;with cte as
(
select intCategoryId, intProductID
from #t where intCategoryId is not null and intProductID is not null
union all
select cte.intCategoryId, t.intProductID
from #t t
join cte
on t.intParentCategory = cte.intProductID
and t.intCategoryId is null
)
update t
set t.intCategoryId = cte.intCategoryId
from #t t
join cte
on t.intProductID = cte.intProductID
option (maxrecursion 5000)
select * from #t

Related

Update sequence based on pattern SQL Server

I was not able to quote correct tile for the question.
Below is my table.
Expected Output : With ID and SequenceNo column. Was unable to upload image.
ID Act SequenceNo1
1 1 1
2 2 NULL
3 3 NULL
4 4 NULL
5 1 5
6 2 NULL
7 3 NULL
8 4 NULL
9 5 NULL
10 6 NULL
11 5 11
12 6 NULL
13 1 13
14 2 NULL
15 3 NULL
16 4 NULL
17 5 NULL
18 6 NULL
19 1 19
20 2 NULL
21 3 NULL
22 4 NULL
23 5 NULL
24 6 NULL
At the start last column SequenceNo is NULL.`
My requirement is to update value of ID column to SequenceNo column whenever new series of Act column is started.
Act column has value of 1 to 6. There can be case where any number from 1 to 6 is missing from Act.
Example1 : ID 1 to 4 - Act is correct but, in next row (ID=5) Act is restarted. Hence need to update SequenceNo column.
Example2 : ID 5 to 10 are correct. But next row (ID=11;Act=5) has new sequence hence need to update SequenceNo column.
CREATE TABLE #tmp
(
ID int
, ScheduleID varchar(50)
,DCNumber VARCHAR(50)
, BuildingID varchar(10)
, StoreNumber int
, [DayOfWeek] int
, [Tm] varchar(10)
,[Act] int
, SequenceNo int
)
INSERT INTO #tmp SELECT 1,'WAS',9003,900301,254,1,'00:00',1,NULL
INSERT INTO #tmp SELECT 2,'WAS',9003,900301,254,1,'00:00',2,NULL
INSERT INTO #tmp SELECT 3,'WAS',9003,900301,254,1,'00:00',3,NULL
INSERT INTO #tmp SELECT 4,'WAS',9003,900301,254,1,'00:00',4,NULL
INSERT INTO #tmp SELECT 5,'WAS',9003,900301,254,2,'00:00',1,NULL
INSERT INTO #tmp SELECT 6,'WAS',9003,900301,254,2,'00:00',2,NULL
INSERT INTO #tmp SELECT 7,'WAS',9003,900301,254,2,'00:00',3,NULL
INSERT INTO #tmp SELECT 8,'WAS',9003,900301,254,2,'00:00',4,NULL
INSERT INTO #tmp SELECT 9,'WAS',9003,900301,254,2,'00:00',5,NULL
INSERT INTO #tmp SELECT 10,'WAS',9003,900301,254,2,'00:00',6,NULL
INSERT INTO #tmp SELECT 11,'WAS',9003,900301,254,3,'00:00',5,NULL
INSERT INTO #tmp SELECT 12,'WAS',9003,900301,254,3,'00:00',6,NULL
INSERT INTO #tmp SELECT 13,'WAS',9003,900301,254,4,'00:00',1,NULL
INSERT INTO #tmp SELECT 14,'WAS',9003,900301,254,4,'00:00',2,NULL
INSERT INTO #tmp SELECT 15,'WAS',9003,900301,254,4,'00:00',3,NULL
INSERT INTO #tmp SELECT 16,'WAS',9003,900301,254,4,'00:00',4,NULL
INSERT INTO #tmp SELECT 17,'WAS',9003,900301,254,5,'00:00',5,NULL
INSERT INTO #tmp SELECT 18,'WAS',9003,900301,254,5,'00:00',6,NULL
INSERT INTO #tmp SELECT 19,'WAS',9003,900301,254,6,'00:00',1,NULL
INSERT INTO #tmp SELECT 20,'WAS',9003,900301,254,6,'00:00',2,NULL
INSERT INTO #tmp SELECT 21,'WAS',9003,900301,254,6,'00:00',3,NULL
INSERT INTO #tmp SELECT 22,'WAS',9003,900301,254,6,'00:00',4,NULL
INSERT INTO #tmp SELECT 23,'WAS',9003,900301,254,7,'00:00',5,NULL
INSERT INTO #tmp SELECT 24,'WAS',9003,900301,254,7,'00:00',6,NULL
I have build one logic but that is time consuming.
DECLARE #Act INT, #iStart INT, #iMax INT, #iFirst INT
SET #iFirst = 7
SET #iMax = (SELECT MAX(ID) FROM #tmp)
SET #iStart = 1
WHILE(#iStart <= #iMax)
BEGIN
SET #Act = (SELECT Act FROM #tmp WHERE ID = #iStart)
IF(#iFirst > #Act )
BEGIN
UPDATE #tmp SET SequenceNo = #iStart WHERE ID = #iStart
END
SET #iFirst = #Act
SET #iStart = #iStart + 1
END
I am looking out for any alternative optimized solution.
Is this what you wanted?
update tupd
set
SequenceNo = tupd.ID
from (
select
*
, LAG(t.Act, 1, null) over(order by t.ID) as LagAct
from #tmp t
) tt
inner join #tmp tupd on tt.ID = tupd.ID
where tt.Act < tt.LagAct
select
*
from #tmp tt
order by tt.ID
You can find breaks in the Act sequence if you compare the current row with the previous. In current SQL Server versions, ie 2012+ you can do that with the LAG() method. In previous versions, you have to perform a self join on the current ID value and its previous value. If the difference between the current and previous Act values isn't 1, there is a break in the sequence. This only works if there are no gaps in ID.
This query will return 1 in the SeqBreak column for each row where Act restarts
select
t1.ID,
t1.SequenceNo ,
case when t1.act = t2.act+1 then '0' else '1' end as SeqBreak
from #tmp t1 left join #tmp t2 on t1.id=t2.id+1
You can use this in a CTE to select only the rows where Act breaks. You can update the CTE directly, at least in SQL Server 2014.
with x as (
select
t1.ID,
t1.SequenceNo ,
case when t1.act = t2.act+1 then '0' else '1' end as SeqBreak
from #tmp t1 left join #tmp t2 on t1.id=t2.id+1 )
update x
set SequenceNo=x.ID
where seqbreak=1 and id>1
If that doesn't work with SQL Server 2008, you'll have to join between the cte and the table:
with x as (
select
t1.ID,
t1.SequenceNo ,
case when t1.act = t2.act+1 then '0' else '1' end as SeqBreak
from #tmp t1 left join #tmp t2 on t1.id=t2.id+1 )
update #tmp
set SequenceNo=x.ID
from #tmp inner join x on x.ID=#tmp.ID
where seqbreak=1 and #tmp.id>1
The equivalent query using LAG(), one of the windowing functions in SQL Server 2012 is simpler and twice as fast, since it avoids the self join:
with x as (
select
ID,
SequenceNo ,
case when act = 1 +LAG(act,1) OVER (ORDER BY ID) then '0' else '1' end as SeqBreak
from #tmp)
update x
set SequenceNo=x.ID
where seqbreak=1 and id>1
;WITH CTE
AS
(
SELECT ID,Act,RANK() OVER (PARTITION BY [DayOfWeek] ORDER BY Act) RN
FROM #tmp
)
UPDATE t
SET t.SequenceNo = t.id
FROM #tmp t
inner join CTE C
ON t.ID = C.ID
WHERE C.RN = 1
If I'm getting your question correctly below query should fetch the desired result.
update t3 set sequenceno = t3.id
from
#temp t1
cross apply (select id+1 as idsum
from #temp
tmp where t1.id = tmp.id)t2
inner join #temp t3
on t3.id = t2.idsum
where t1.act>t3.act

SQL Server: How to select missing rows in table from another table?

I have two tables like below:
table1:
StoreId SKU
------------
1 abc
2 abc
3 abc
1 xyz
4 xyz
table2:
StoreId
--------
1
2
3
4
5
I want to select missing storeid from the table1 which are in table 2. But condition is that in above example for SKU abc storeid 4 and 5 are missing and for sku xyz 2,3,5 are missing. So I want below table as output
SKU,ID
------
abc 4
abc 5
xyz 2
xyz 3
xyz 5
I am able to pull only the overall missing store which is 5 using below query.
SELECT
SKU, t2.StoreId
FROM
#table1 t1
FULL OUTER JOIN
#table2 t2 ON t1.StoreId = t2.StoreId
WHERE
t1.StoreId IS NULL
Below is test create and insert query.
Declare #table1 As table
(
StoreId varchar(4),
SKU varchar(5)
)
Declare #table2 As table
(
StoreId int
)
BEGIN
Insert Into #table1(SKU,StoreId) values('abc',1)
Insert Into #table1(SKU,StoreId) values('abc',2)
Insert Into #table1(SKU,StoreId) values('abc',3)
Insert Into #table1(SKU,StoreId) values('xyz',1)
Insert Into #table1(SKU,StoreId) values('xyz',4)
Insert Into #table2(StoreId) values(1)
Insert Into #table2(StoreId) values(2)
Insert Into #table2(StoreId) values(3)
Insert Into #table2(StoreId) values(4)
Insert Into #table2(StoreId) values(5)
END
Thank you
You need to get a list of all skus and tables, and then show only rows which do not appear in table1:
select SKU, StoreID
from #table2 t2
cross join (select distinct sku from #table1) t1
where not exists (select 1 from #table1 table1
where table1.SKU = t1.SKU
and table1.StoreId = t2.StoreId)
Here is an alternative solution with the same result.
Syntax is very similar to the answer from #BeanFrog:
SELECT
t3.SKU, t2.StoreID
FROM
#table2 t2
CROSS JOIN
(SELECT distinct SKU
FROM #table1) t3
LEFT JOIN
#table1 t1
ON
t1.SKU = t3.SKU
and t1.StoreId = t2.StoreId
WHERE
t1.sku is null

SQL Server: How to select top rows of a group based on value of the column of that group?

I have two tables like below.
table 1
id rem
1 2
2 1
table 2
id value
1 abc
1 xyz
1 mno
2 mnk
2 mjd
EDIT:
#output
id value
1 abc
1 xyz
2 mnk
What i want to do is select top 2 rows of table2 with id one as rem value is 2 for id 1 and top 1 row with id 2 as its rem value is 1 and so on. I am using MS sqlserver 2012 My whole scenario is more complex than this. Please help.
Thank you.
EDIT : I know that i should have given what i have done and how i am doing it but for this particular part i don't have idea for starting. I could do this by using while loop for each unique id but i want to do it in one go if possible.
First, SQL tables represent unordered sets. There is no specification of which values you get, unless you include an order by.
For this purpose, I would go with row_number():
select t2.*
from table1 t1 join
(select t2.*,
row_number() over (partition by id order by id) as seqnum
from table2 t2
) t2
on t1.id = t2.id and t2.seqnum <= t1.rem;
Note: The order by id in the windows clause should be based on which rows you want. If you don't care which rows, then order by id or order by (select null) is fine.
Try This:
DECLARE #tbl1 TABLE (id INT, rem INT)
INSERT INTO #tbl1 VALUES (1, 2), (2, 1)
DECLARE #tbl2 TABLE (id INT, value VARCHAR(10))
INSERT INTO #tbl2 VALUES (1, 'abc'), (1, 'xyz'),
(1, 'mno'), (2, 'mnk'), (2, 'mjd')
SELECT * FROM #tbl1 -- your table 1
SELECT * FROM #tbl2 -- your table 2
SELECT id,value,rem FROM ( SELECT ROW_NUMBER() OVER (PARTITION BY T.ID ORDER BY T.ID) rowid,
T.id,T.value,F.rem FROM #tbl2 T LEFT JOIN #tbl1 F ON T.id = F.id ) A WHERE rowid = 1
-- your required output
Hope it helps.

Rewriting function to conditional CTE

Considering following table:
SELECT [ItemID]
,[ParentID]
,[PolicyID]
,[PolicyRoot]
FROM [AdventureWorks2008R2].[dbo].[Example]
ItemID ParentID PolicyID PolicyRoot
----------- ----------- ---------- ----------
1 NULL default 1
2 1 b 1
3 1 c 0
4 NULL d 1
5 3 e 0
6 3 f 1
7 NULL g 0
I'm trying to select the PolicyID from each item where PolicyRoot = 1, in case PolicyRoot = 0 I need to use PolicyID from its ParentID. This is recursive...
Working with a function:
CREATE FUNCTION dbo.Policies(#ItemID INT) RETURNS VARCHAR(10)
AS
BEGIN
DECLARE #ParentID INT, #PolicyRoot BIT, #PolicyID VARCHAR(10)
SELECT #ParentID = ParentID
, #PolicyRoot = PolicyRoot
, #PolicyID = PolicyID
FROM [dbo].[Example]
WHERE ItemID = #ItemID
IF #PolicyRoot != 1
SELECT #PolicyID = dbo.Policies(#ParentID)
RETURN #PolicyID
END;
GO
SELECT ItemID
, dbo.Policies(ItemID) AS Policy
FROM [dbo].[Example];
ItemID Policy
----------- ----------
1 default
2 b
3 default
4 d
5 default
6 f
7 NULL
I'm trying to rewrite this function to a CTE, but I don't have any CTE knowledge yet. I've read into multiple CTE's but I don't have a single clue how to manage a conditional CTE. This is as far as I've gotten, I'm not familiar (enough) with the UNION ALL.
WITH Policies (ItemID, PolicyID) AS (
SELECT ItemID
, PolicyID
FROM dbo.Example
UNION ALL
...
)
SELECT ItemID
, PolicyID
FROM Policies;
Can someone explain me in plain steps how such a CTE works and push me in the right direction?
A recursive CTE works by joining to itself, using a UNION ALL to collate the results.
You start with yourtable to populate the initial dataset of the recursive query
select * from yourtable
and you add to that with the UNION ALL, further results
select c.ItemID, t2.ParentID, t2.PolicyID, t2.PolicyRoot
from yourtable t2
inner join c on c.ParentID = t2.ItemID
where c.PolicyRoot=0
and the recursion occurs in this - where the results of this query are fed through this query again and again, up to the MAXRECURSION limit, or when no more results are added.
;with c as
(
select * from yourtable
union all
select c.ItemID, t2.ParentID, t2.PolicyID, t2.PolicyRoot
from yourtable t2
inner join c on c.ParentID = t2.ItemID
where c.PolicyRoot=0
)
select t.ItemID, c.PolicyID
from yourtable t
left join c on t.ItemID = c.ItemID
and c.PolicyRoot=1

Order of Recursion (SQL Server CTE)

I can achieve recursion by using SQL Server's With command (CTE).
WITH MyCTE(ParentID,ID,Name,Level)
AS
(
SELECT ManagerID AS ParentID, UserID AS ID, UserName AS Name, 0 AS Level
FROM USERS U
WHERE U.ManagerID IS NULL
UNION ALL
SELECT U.ManagerID AS ParentID, U.UserID AS ID, U.UserName AS Name, H.Level+1 AS Level
FROM USERS U
INNER JOIN MyCTE H ON H.ID = U.ManagerID
)
SELECT ParentID,ID FROM MyCTE
returns
ParentID ID
NULL 1
1 2
1 3
2 4
What I want to achieve is to reverse this result set. Namely,reversing the root node and the deepest child node as,
ParentID ID
NULL 4
4 2
2 1
3 1
Couldn't figure out how to programmatically implement this (preferably by using CTE), like by using a parameter to determine the recursion order etc. Any help is greatly appreciated, thanks.
Edit :
Modified this a bit inserting my first CTE's results into a temp table, then using another recursion I reverse the order as (I know "WHERE T.ID = (SELECT MAX(ID) FROM #tmp)" wont work in a real situation, I also gotta determine the deepest node with the "Level" column, just tried to simplify this for this example),
INSERT INTO #tmp
SELECT ParentID,ID,Level FROM MyCTE
WITH MyCTE2(ParentID,ID,Level)
AS
(
SELECT NULL AS ParentID, ID AS ID, 0 AS Level FROM #tmp T
WHERE T.ID = (SELECT MAX(ID) FROM #tmp)
UNION ALL
SELECT R2.ID AS ParentID, T.ParentID AS ID, R2.Level+1 FROM #tmp T
INNER JOIN MyCTE2 R2 ON R2.ID = T.ID
WHERE T.ParentID IS NOT NULL
)
Original Results (removed the 1,3 pair)
ParentID ID Level
NULL 1 0
1 2 1
2 4 2
Reversed results,
ParentID ID Level
NULL 4 0
4 2 1
2 1 2
Edit 2:
I did something like this,
SELECT TTT.ParentID,TTT.ID,TTT.Level FROM
(
SELECT ParentID,ID,Level FROM MyCTE2
UNION ALL
SELECT TT.ID AS ParentID,TT.ParentID AS ID,(SELECT Level+1 FROM #tmp WHERE ID=TT.ID)
AS Level FROM
(
SELECT ID FROM #tmp
EXCEPT
SELECT ID FROM MyCTE2
)T INNER JOIN #tmp TT ON TT.ID = T.ID
)TTT
ORDER BY TTT.Level
gives,
ParentID ID Level
NULL 4 0
4 2 1
2 1 2
3 1 2
This may contain errors, im not sure yet, just wanted to show to make sure that pair (3,1) is whther correct with level 2 ? Been thinking on this for quite a while now, I might make some silly mistakes.
Sample data
declare #T table
(
ParentID int,
ID int
)
insert into #T values
(NULL, 1),
(1 , 2),
(1 , 3),
(2 , 4)
Recursion from root:
;with C as
(
select ParentID, ID
from #T
where ParentID is null
union all
select T.ParentID, T.ID
from #T as T
inner join C
on T.ParentID = C.ID
)
select *
from C
Result
ParentID ID
----------- -----------
NULL 1
1 2
1 3
2 4
Recursion from leafs:
;with C as
(
select null as PParentID, ID, ParentID
from #T
where ID not in (select ParentID
from #T
where ParentID is not null)
union all
select C.ID, T.ID, T.ParentID
from #T as T
inner join C
on T.ID = C.ParentID
)
select distinct
PParentID as ParentID,
ID
from C
Result:
ParentID ID
----------- -----------
NULL 3
NULL 4
4 2
2 1
3 1
If you have many branches you will have duplicate rows as merge together. Using distinct takes care of that.
To get the levels correct you need to first calculate the level from top down. Store that in a table variable (or temp table) and then use that as the source for leaf->root recursion.
-- Primary key and unique is in there to get the indexes used in the recursion
declare #T2 table
(
ParentID int,
ID int,
Level int,
primary key (ID),
unique(ParentID, ID)
)
;with C as
(
select ParentID, ID, 0 as Level
from #T
where ParentID is null
union all
select T.ParentID, T.ID, Level + 1
from #T as T
inner join C
on T.ParentID = C.ID
)
insert into #T2
select ParentID, ID, Level
from C
;with C as
(
select null as PParentID, ID, ParentID, Level
from #T2
where ID not in (select ParentID
from #T2
where ParentID is not null)
union all
select C.ID, T.ID, T.ParentID, T.Level
from #T2 as T
inner join C
on T.ID = C.ParentID
)
select distinct
PParentID as ParentID,
ID,
max(Level) over() - Level as level
from C
Result:
ParentID ID level
----------- ----------- -----------
NULL 3 1
NULL 4 0
2 1 2
3 1 2
4 2 1
It is possible but a really bad idea to replace #T2 with a multi CTE query. It will kill performance because to first CTE will be rebuilt for each recursion. At least that is my guess of what is happening but believe me it is not fast.

Resources