Related
I have a product table, Category table, and Mapping table. Category saved as a category tree. If a single product has mapped with the last category in a hierarchy of level three. All the levels saved in the mapping table with the same product id.
eg : Assume there is category tre like this Electronic>LapTops>DELL and when product id = 1 assigned to category 'DELL' mapping will save as [1,Electronic],[1,LapTops],[1,DELL]
When I get data with a select query all the category levels appear with the same product Id.
My problem is I need to retrieve data as [productId, ProductName, LastCategortLevel, CategoryName, CategoryId].
Refer actual result below. I just need to pick the highlighted product with the last category level which is the highest category order level.
I can't use another stored procedure or function because it's a small part of a large stored procedure.
The actual database tables are very big. But I have tried to implement the same scenario with small temp tables. see the below queries.
DECLARE #Products TABLE (ProductId INT NOT NULL)
INSERT INTO #Products(ProductId)
SELECT ProductId
FROM (VALUES (1), (2), (3), (4)) as x (ProductId)
DECLARE #Categories TABLE (CategoId INT NOT NULL,
Name VARCHAR(MAX) NOT NULL,
ParentCategoryId INT NOT NULL,
DisplayOrder INT NOT NULL)
-- 1st category tree
INSERT INTO #Categories VALUES (10, 'Electronic', 0, 1)
INSERT INTO #Categories VALUES (11, 'LapTops', 10, 2)
INSERT INTO #Categories VALUES (12, 'DELL', 11, 3)
INSERT INTO #Categories VALUES (13, 'HP', 11, 3)
-- 2st category tree
INSERT INTO #Categories VALUES (14, 'Clothes', 0, 1)
INSERT INTO #Categories VALUES (15, 'T-Shirts', 14, 2)
INSERT INTO #Categories VALUES (16, 'Red', 15, 3)
INSERT INTO #Categories VALUES (17, 'Denim', 14, 2)
INSERT INTO #Categories VALUES (18, 'Levise', 17, 3)
DECLARE #Product_Category_Mappings TABLE(MappingId INT NOT NULL,
ProductId INT NOT NULL,
CategoryId INT NOT NULL)
INSERT INTO #Product_Category_Mappings VALUES (100, 1, 10)
INSERT INTO #Product_Category_Mappings VALUES (101, 1, 11)
INSERT INTO #Product_Category_Mappings VALUES (102, 1, 12)
INSERT INTO #Product_Category_Mappings VALUES (103, 2, 10)
INSERT INTO #Product_Category_Mappings VALUES (104, 2, 11)
INSERT INTO #Product_Category_Mappings VALUES (105, 2, 12)
INSERT INTO #Product_Category_Mappings VALUES (106, 3, 14)
INSERT INTO #Product_Category_Mappings VALUES (107, 3, 15)
INSERT INTO #Product_Category_Mappings VALUES (108, 3, 16)
INSERT INTO #Product_Category_Mappings VALUES (109, 4, 14)
INSERT INTO #Product_Category_Mappings VALUES (110, 4, 17)
INSERT INTO #Product_Category_Mappings VALUES (111, 4, 18)
SELECT *
FROM #Products P
INNER JOIN #Product_Category_Mappings M ON M.ProductId = P.ProductId
INNER JOIN #Categories C ON C.CategoId = M.CategoryId
WHERE M.ProductId = P.ProductId
ORDER BY P.ProductId, C.DisplayOrder
Result of the above script. How I get highlighted rows?
For each ProductId, you want the row with highest DisplayOrder. You can use window functions:
SELECT *
FROM (
SELECT *, ROW_NUMBER() OVER(PARTITION BY P.ProductId ORDER BY C.DisplayOrder DESC) rn
FROM #Products P
INNER JOIN #Product_Category_Mappings M ON M.ProductId = P.ProductId
INNER JOIN #Categories C ON C.CategoId = M.CategoryId
WHERE M.ProductId = P.ProductId
) t
WHERE rn = 1
ORDER BY P.ProductId, C.DisplayOrder
This is a variation of the normal CTE where you find all parents of a node. Only difference is:
The greatest descendant does not have a null child. Instead, it's simply not present in the child column.
Here is my attempt:
insert into t (parent, child) values (1, 2)
insert into t (parent, child) values (1, 3)
insert into t (parent, child) values (4, 2)
insert into t (parent, child) values (2, 5)
insert into t (parent, child) values (2, 6)
insert into t (parent, child) values (9, 6)
insert into t (parent, child) values (6, 7)
insert into t (parent, child) values (6, 8)
with cte as
(
select child, parent, 0 as level
from t
where parent = 5
union all
select q.child, q.parent, level+1
from t q
inner join cte as c on c.parent= q.child
)
select distinct parent from cte
where parent <> 5
In this case, when I try to get all parents for 5, nothing is found because 5 isn't a parent to anyone. If I try to find all parents for 2, it succeeds however because 2 is a parent to 5 and 6.
If 5 is not a parent to anyone, then your filter parent=5 will never give any output. 5 is a child and you want to find all parent above it:
declare #t table (parent int, child int)
insert into #t (parent, child)
values (1, 2),
(1, 3),
(4, 2),
(2, 5),
(2, 6),
(9, 6),
(6, 7),
(6, 8)
;with cte as
(
select child, parent, 0 as level
from #t
where child = 5 ---<<<
union all
select q.child, q.parent, level+1
from #t q
inner join cte as c on c.parent= q.child
)
select distinct parent from cte
where parent <> 5
I have a situation where I need to configure existing client data to address a problem where our application was not correctly updating IDs in a table when it should have been.
Here's the scenario. We have a parent table, where rows can be inserted that effectively replace existing rows; the replacement can be recursive. We also have a child table, which has a field that points to the parent table. In existing data, the child table could be pointing at rows that have been replaced, and I need to correct that. I can't simply update each row to the replacing row, however, because that row could have been replaced as well, and I need the latest row to be reflected.
I was trying to find a way to write a CTE that would accomplish this for me, but I'm struggling to find a query that finds what I'm actually looking for. Here's a sample of the tables that I'm working with; the 'ShouldBe' column is what I'd like my update query to end up with, taking into account the recursive replacement of some of the rows.
DECLARE #parent TABLE (SampleID int,
SampleIDReplace int,
GroupID char(1))
INSERT INTO #parent (SampleID, SampleIDReplace, GroupID)
VALUES (1, -1, 'A'), (2, 1, 'A'), (3, -1, 'A'),
(4, -1, 'A'), (5, 4, 'A'), (6, 5, 'A'),
(7, -1, 'B'), (8, 7, 'B'), (9, 8, 'B')
DECLARE #child TABLE (ChildID int, ParentID int)
INSERT INTO #child (ChildID, ParentID)
VALUES (1, 4), (2, 7), (3, 1), (4, 3)
Desired results in child table, after the update script has been applied:
ChildID ParentID ParentID_ShouldBe
1 4 6 (4 replaced by 5, 5 replaced by 6)
2 7 9 (7 replaced by 8, 8 replaced by 9)
3 1 2 (1 replaced by 2)
4 3 3 (unchanged, never replaced)
The following returns what you are looking for:
with cte as (
select sampleid, sampleidreplace, 1 as num
from #parent
where sampleidreplace <> -1
union all
select p.sampleid, cte.sampleidreplace, cte.num+1
from #parent p join
cte
on p.sampleidreplace = cte.sampleId
)
select c.*, coalesce(p.sampleid, c.parentid)
from #child c left outer join
(select ROW_NUMBER() over (partition by sampleidreplace order by num desc) as seqnum, *
from cte
) p
on c.ParentID = p.SampleIDReplace and p.seqnum = 1
The recursive part keeps track of every correspondence (4-->5, 4-->6). The addition number is a "generation" count. We actually want the last generation. This is identified by using the row_number() function, ordering by the num in decreasing order -- hence the p.seqnum = 1.
Ok, so it took me a while and there are probably better ways to do it, but here is one option.
DECLARE #parent TABLE (SampleID int,
SampleIDReplace int,
GroupID char(1))
INSERT INTO #parent (SampleID, SampleIDReplace, GroupID)
VALUES (1, -1, 'A'), (2, 1, 'A'), (3, -1, 'A'),
(4, -1, 'A'), (5, 4, 'A'), (6, 5, 'A'),
(7, -1, 'B'), (8, 7, 'B'), (9, 8, 'B')
DECLARE #child TABLE (ChildID int, ParentID int)
INSERT INTO #child (ChildID, ParentID)
VALUES (1, 4), (2, 7), (3, 1), (4, 3)
;WITH RecursiveParent1 AS
(
SELECT SampleIDReplace, SampleID, 1 RecursionLevel
FROM #parent
WHERE SampleIDReplace != -1
UNION ALL
SELECT A.SampleIDReplace, B.SampleID, RecursionLevel + 1
FROM RecursiveParent1 A
INNER JOIN #parent B
ON A.SampleId = B.SampleIDReplace
),RecursiveParent2 AS
(
SELECT *,
ROW_NUMBER() OVER(PARTITION BY SampleIdReplace ORDER BY RecursionLevel DESC) RN
FROM RecursiveParent1
)
SELECT A.ChildID, ISNULL(B.ParentID,A.ParentID) ParentID
FROM #child A
LEFT JOIN ( SELECT SampleIDReplace, SampleID ParentID
FROM RecursiveParent2
WHERE RN = 1) B
ON A.ParentID = B.SampleIDReplace
OPTION(MAXRECURSION 500)
I've got a iterative SQL loop that I think sorts this out as follows:
WHILE EXISTS (SELECT * FROM #child C INNER JOIN #parent P ON C.ParentID = P.SampleIDReplace WHERE P.SampleIDReplace > -1)
BEGIN
UPDATE #child
SET ParentID = SampleID
FROM #parent
WHERE #child.ParentID = SampleIDReplace
END
Basically, the while condition compares the contents of the parent ID column in the child table and sees if there is a matching value in the SampleIDReplace column of the parent table. If there is, it goes and gets the SampleID of that record. It only stops when the join results in every SampleIDReplace being -1, meaning we have nothing else to do.
On your sample data, the above results in the expected output.
Note that I had to use temp tables rather than table variables here in order for the table to be accessible within the loop. If you have to use table variables then there would need to be a bit more surgery done.
Clearly if you have deep replacement hierarchies then you'll do quite a few updates, which may be a consideration when looking to perform the query against a production database.
Given the following 2 tables, I need to find the warehouses that have all the parts in the right quantity to build a particular kit, or more appropriately, how many kits each can warehouse can build.
Inventory table: Warehouse, Part, and QuantityOnHand
Kit table: Kit, Part, QuantityForKit
For example: Kit1 requires 1 of Part1, 2 of Part2, and 1 of Part3. Warehouse A has 20 Part1, 5 Part2 and 3 Part3. Warehouse B has 5 Part1, 10 Part2, and no Part3.
Warehouse A can only build 2 of Kit1 because it doesn't have enough Part2 to make more than 2 kits. Warehouse B can't build any Kit1 because it doesn't have all the necessary parts.
I've got the following demo that works, but it seems really cumbersome and uses mostly table/index scans. Our inventory table is large and this just runs too slow. I'm looking for a better way to accomplish the same thing. In the demo there's an unbounded cross join, but in the actual app, it's limited to a single kit.
CREATE TABLE #warehouse
(
Warehouse CHAR(1) NOT NULL PRIMARY KEY
)
INSERT INTO #warehouse VALUES ('A')
INSERT INTO #warehouse VALUES ('B')
INSERT INTO #warehouse VALUES ('C')
INSERT INTO #warehouse VALUES ('D')
CREATE TABLE #inventory
(
Warehouse CHAR(1) NOT NULL ,
Part INT NOT NULL ,
OnHand INT NOT NULL ,
CONSTRAINT pk_inventory PRIMARY KEY CLUSTERED (Part, Warehouse)
)
INSERT INTO #inventory VALUES ('A', 1, 20)
INSERT INTO #inventory VALUES ('A', 2, 5)
INSERT INTO #inventory VALUES ('A', 3, 3)
INSERT INTO #inventory VALUES ('B', 1, 5)
INSERT INTO #inventory VALUES ('B', 2, 10)
INSERT INTO #inventory VALUES ('C', 1, 1)
INSERT INTO #inventory VALUES ('C', 3, 1)
INSERT INTO #inventory VALUES ('D', 1, 1)
INSERT INTO #inventory VALUES ('D', 2, 2)
INSERT INTO #inventory VALUES ('D', 3, 1)
CREATE TABLE #kit
(
Kit INT NOT NULL ,
Part INT NOT NULL ,
Quantity INT NOT NULL ,
CONSTRAINT pk_kit PRIMARY KEY CLUSTERED (Kit, Part)
)
INSERT INTO #kit VALUES (1, 1, 1)
INSERT INTO #kit VALUES (1, 2, 2)
INSERT INTO #kit VALUES (1, 3, 1)
INSERT INTO #kit VALUES (2, 1, 1)
INSERT INTO #kit VALUES (2, 2, 1)
-- Here's the statement I need to optimize
SELECT
Warehouse,
Kit,
MIN(Capacity) AS [Capacity]
FROM
(
SELECT
A.Warehouse,
A.Kit,
A.Part,
ISNULL(B.OnHand, 0) AS [Quantity],
ISNULL(B.OnHand, 0) / A.Quantity AS Capacity
FROM
(
SELECT *
FROM
#warehouse
CROSS JOIN
-- (SELECT * FROM
#kit
-- WHERE #kit.Kit = #Kit) K
) A
LEFT OUTER JOIN
#inventory B
ON A.Warehouse = B.Warehouse
AND A.Part = B.Part
) C
GROUP BY
Warehouse,
Kit
;
Suggestions appreciated.
Try this:
SELECT warehouse, MIN(capacity) FROM (
SELECT i.warehouse, i.onhand / k.quantity as capacity
FROM #kit k
JOIN #inventory i
ON k.part = i.part AND k.quantity <= i.onhand
WHERE k.kit = #kit) c
GROUP BY warehouse
HAVING COUNT(*) = (SELECT COUNT(*) FROM #kit WHERE kit = #kit)
sqlfiddle here
I'm trying to query a hierarchy of data in a single database table from the bottom up (I don't want to include parents that don't have a particular type of child due to authorities). The schema and sample data are as follows:
create table Users(
id int,
name varchar(100));
insert into Users values (1, 'Jill');
create table nodes(
id int,
name varchar(100),
parent int,
nodetype int);
insert into nodes values (1, 'A', 0, 1);
insert into nodes values (2, 'B', 0, 1);
insert into nodes values (3, 'C', 1, 1);
insert into nodes values (4, 'D', 3, 2);
insert into nodes values (5, 'E', 1, 1);
insert into nodes values (6, 'F', 5, 2);
insert into nodes values (7, 'G', 5, 2);
create table nodeAccess(
userid int,
nodeid int,
access int);
insert into nodeAccess values (1, 1, 1);
insert into nodeAccess values (1, 2, 1);
insert into nodeAccess values (1, 3, 1);
insert into nodeAccess values (1, 4, 1);
insert into nodeAccess values (1, 5, 1);
insert into nodeAccess values (1, 6, 0);
insert into nodeAccess values (1, 7, 1);
with Tree(id, name, nodetype, parent)
as
(
select n.id, n.name, n.nodetype, n.parent
from nodes as n
inner join nodeAccess as na on na.nodeid = n.id
where na.access =1 and na.userid=1 and n.nodetype=2
union all
select n.id, n.name, n.nodetype, n.parent
from nodes as n
inner join Tree as t on t.parent = n.id
inner join nodeAccess as na on na.nodeid = n.id
where na.access =1 and na.userid=1 and n.nodetype=1
)
select * from Tree
Yields:
id name nodetype parent
4 D 2 3
7 G 2 5
5 E 1 1
1 A 1 0
3 C 1 1
1 A 1 0
How can I not include the duplicates in the result set? The queries against the real tables have many more nodes at the lowest levels and hence many more duplicates of the parent nodes. The solution needs to work with at least SQL Server 2005.
Thanks in advance!
The simplest (not necessarily the most efficient) solution:
...
)
SELECT DISTINCT id,name,nodetype,parent FROM Tree;
This changes the order from your sample output because the DISTINCT operator implements a sort. If there is some intentional ordering there I cannot detect it but you can add an ORDER BY if you know the order you want.