SQL Server : permutations/combinations without looping - sql

I have two data sets. The first is a table of product recipes along with the products that make up the recipe. The 2nd data set contains individual pricing by product (I can have multiple prices for a single product).
What I'm trying to achieve is to output a result set that contains the unique permutations for each of my product recipes. Only recipes where ALL of the components have pricing in the 2nd data set should be in the output.
Assumption: A single recipe can have up to 5 components configured (no more).
DECLARE #ProductRecipe TABLE (ProductRecipeID INT, ComponentProductID INT)
INSERT INTO #ProductRecipe (ProductRecipeID, ComponentProductID)
VALUES (21, 130), (21, 468), (21, 500),
(22, 468), (22, 500),
(23, 130), (23, 501)
DECLARE #ComponentPricing TABLE (PricingID INT, ProductID INT)
INSERT INTO #ComponentPricing (PricingID, ProductID)
VALUES (314023, 130), (313616, 130), (313071, 130),
(312865, 130), (316323, 468), (316329, 468), (398864, 500)
I would like my output to look like this:
Output Example
I have tried CTEs and self joins but I can't even get close to my desired output.. :(
I’m using SQL Server 2012

I'm going to assume you are working with SQL Server 2008 or newer, which is required to make the dense_rank() function work.
The solution below goes through a few steps that are outlined in the comments. One call out is that I changed one of the #ProductRecipe records from (22, 130) to (22, 468) as I believe it to be the intended sample data because Component1 of the desired output includes PricingID values 316323 and 316329.
Answer:
DECLARE #ProductRecipe TABLE (ProductRecipeID INT, ComponentProductID INT)
INSERT INTO #ProductRecipe (ProductRecipeID, ComponentProductID) VALUES (21, 130)
INSERT INTO #ProductRecipe (ProductRecipeID, ComponentProductID) VALUES (21, 468)
INSERT INTO #ProductRecipe (ProductRecipeID, ComponentProductID) VALUES (21, 500)
INSERT INTO #ProductRecipe (ProductRecipeID, ComponentProductID) VALUES (22, 468) --values were (22, 130) in question
INSERT INTO #ProductRecipe (ProductRecipeID, ComponentProductID) VALUES (22, 500)
INSERT INTO #ProductRecipe (ProductRecipeID, ComponentProductID) VALUES (23, 130)
INSERT INTO #ProductRecipe (ProductRecipeID, ComponentProductID) VALUES (23, 501)
DECLARE #ComponentPricing TABLE (PricingID INT, ProductID INT)
INSERT INTO #ComponentPricing (PricingID, ProductID)
VALUES (314023, 130)
, (313616, 130)
, (313071, 130)
, (312865, 130)
, (316323, 468)
, (316329, 468)
, (398864, 500)
; with base as
(
--Joining the two datasets together.
select pr.ProductRecipeID
, pr.ComponentProductID
, cp.PricingID
from #ProductRecipe as pr
left join #ComponentPricing as cp on pr.ComponentProductID = cp.ProductID
)
, pr_exclude as
(
--Identifying that ProductRecipeID 23 should be excluded because of the 501 NULL value
select distinct b.ProductRecipeID
from base as b
where b.PricingID is null
)
, final_base as
(
--Assigning Rank to each ComponentProductID
select b.ProductRecipeID
, b.ComponentProductID
, b.PricingID
, dense_rank() over (partition by b.ProductRecipeID order by b.ComponentProductID asc) as prod_rnk
from base as b
left join pr_exclude as p on b.ProductRecipeID = p.ProductRecipeID
where 1=1
and p.ProductRecipeID is null
)
--Joining it all together
select a.ProductRecipeID
, a.PricingID as Component1
, b.PricingID as Component2
, c.PricingID as Component3
, d.PricingID as Component4
, e.PricingID as Component5
from final_base as a
left join final_base as b on a.ProductRecipeID = b.ProductRecipeID and b.prod_rnk = 2
left join final_base as c on b.ProductRecipeID = c.ProductRecipeID and c.prod_rnk = 3
left join final_base as d on c.ProductRecipeID = d.ProductRecipeID and d.prod_rnk = 4
left join final_base as e on d.ProductRecipeID = e.ProductRecipeID and e.prod_rnk = 5
where a.prod_rnk = 1
order by 1, 2, 3, 4, 5, 6

Related

SQL Server : SELECT query to get DISTINCT and MAX display order value

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

SQL to assign covid patients to hospitals

I have 2 tables:
CREATE TABLE remdesivir_inventory
(
hospital_id int,
stock int,
state varchar(2)
);
CREATE TABLE remdesivir_requests
(
patient_id int,
prescribed_qty int,
state varchar(2)
);
I want to write a SQL that inserts rows in the remdesivir_assignments table
Every patient whose request can be fulfilled (until the stock runs out) will have a representative row in
the remdesivir_assignments table.
Each patient can be assigned to only 1 hospital (ie. requests cannot be split)
The 'state' of the patient and the hospital must match
CREATE TABLE remdesivir_assignments
(
patient_id int,
hospital_id int
);
Example:
INSERT INTO remdesivir_inventory VALUES (1, 200, 'CA');
INSERT INTO remdesivir_inventory VALUES (2, 100, 'FL');
INSERT INTO remdesivir_inventory VALUES (3, 500, 'TX');
INSERT INTO remdesivir_requests VALUES (10, 100, 'CA');
INSERT INTO remdesivir_requests VALUES (20, 200, 'FL');
INSERT INTO remdesivir_requests VALUES (30, 300, 'TX');
INSERT INTO remdesivir_requests VALUES (40, 100, 'AL');
INSERT INTO remdesivir_requests VALUES (50, 200, 'CA');
In this scenario, the following rows will be inserted to the remdesivir_assignments table
(10, 1)
(30, 3)
You can use a cumulative sum and join:
select rr.*, ri.hospital_id
from (select rr.*,
sum(prescribed_qty) over (partition by state order by patient_id) as running_pq
from remdesivir_requests rr
) rr join
remdesivir_inventory ri
on ri.state = rr.state and
rr.running_pq <= ri.stock
Here is a db<>fiddle.

MSSQL Permutations/Combinations - Finding all possible matches using data subset

In MSSQL, I have a table (ProductRecipe) that contains up to 5 different components products. I then have a dataset that contains the individual component products and their costs.
What I'm trying to do is find all the different permutations/combinations that will satisfy my recipe.
CREATE TABLE #ProductRecipe (ProductRecipeID INT, Component1 INT, Component2 INT, Component3 INT, Component4 INT, Component5 INT)
CREATE TABLE #ComponentPricing (RowID INT, PricingID INT, ProductID INT, ProductDescription VARCHAR(50), Cost DECIMAL(18,6))
INSERT INTO #ProductRecipe (ProductRecipeID, Component1, Component2) VALUES (21, 130, 468)
INSERT INTO #ComponentPricing (RowID, PricingID, ProductID, ProductDescription, Cost)
VALUES (1, 314023, 130, 'ULS2', 1.783800)
, (2, 313616, 130, 'ULS2', 1.783800)
, (3, 313071, 130, 'ULS2', 1.794000)
, (4, 312865, 130, 'ULS2', 1.789500)
, (5, 316323, 468, 'B100', 1.550500)
SELECT * FROM #ProductRecipe
SELECT * FROM #ComponentPricing
DROP TABLE #ProductRecipe
DROP TABLE #ComponentPricing
The result I'm trying to achieve is that I end up with 4 different variations of the recipe since the first 4 records (for ProductID 130) can be blended with the last record (ProductID 468). Only these two products can be blended because those are the two component products defined in my ProductRecipe table.
Desired Result:
Row 1+5 go together, Row 2+5 go together, Row 3+5 go together, Row 4+5 go together; Returning the PricingID column.
ProductRecipeID Component1 Component2 Component3 Component4 Component5
21 314023 316323
21 313616 316323
21 313071 316323
21 312865 316323
See if this works.
DECLARE #ProductRecipe TABLE (ProductRecipeID INT, Component1 INT, Component2 INT, Component3 INT, Component4 INT, Component5 INT)
DECLARE #ComponentPricing TABLE (RowID INT, PricingID INT, ProductID INT, ProductDescription VARCHAR(50), Cost DECIMAL(18,6))
INSERT INTO #ProductRecipe (ProductRecipeID, Component1, Component2,Component3) VALUES (21, 130, 468,221)
INSERT INTO #ComponentPricing (RowID, PricingID, ProductID, ProductDescription, Cost)
VALUES (1, 314023, 130, 'ULS2', 1.783800)
, (2, 313616, 130, 'ULS2', 1.783800)
, (3, 313071, 130, 'ULS2', 1.794000)
, (4, 312865, 130, 'ULS2', 1.789500)
, (5, 316323, 468, 'B100', 1.550500)
, (6, 316322, 221, 'B1110', 1.5250500)
;WITH UnpivotedRecipe AS
(
SELECT
ProductRecipeID, ComponentID
FROM
(SELECT * FROM #ProductRecipe) AS P
UNPIVOT(ComponentID FOR V IN(Component1,Component2,Component3,Component4,Component5))AS UP
)
, JoinedData AS
(
SELECT
ProductRecipeID, ComponentID, RowID
FROM
UnpivotedRecipe R
INNER JOIN #ComponentPricing C ON C.ProductID = R.ComponentID
)
SELECT DISTINCT J1.ComponentID,J1.RowID,J2.ComponentID FROM JoinedData J1
CROSS JOIN JoinedData J2
WHERE
J1.ComponentID<>J2.ComponentID
Consider multiple LEFT JOIN on self-joining tables:
SELECT p.ProductRecipeID, c1.PricingID AS Component1, c2.PricingID AS Component2,
c3.PricingID AS Component3, c4.PricingID AS Component4
FROM #ProductRecipe p
LEFT JOIN #ComponentPricing c1
ON p.Component1 = c1.ProductID
LEFT JOIN #ComponentPricing c2
ON p.Component2 = c2.ProductID
LEFT JOIN #ComponentPricing c3
ON p.Component3 = c3.ProductID
LEFT JOIN #ComponentPricing c4
ON p.Component4 = c4.ProductID
Rextester Demo

SQL Query by using with

I have the following query...
------ create table
create table test222
(
sid bigint,
scode nvarchar(50),
parentid bigint,
sname nvarchar(50)
)
insert into test222 values (1, '11', 0, 'iam a boy')
insert into test222 values (2, '111', 1, 'boy')
insert into test222 values (3, '1111', 1, 'bo')
insert into test222 values (4, '11111', 3, 'girl')
insert into test222 values (5, '111111', 0, 'boyy')
insert into test222 values (6, '1111111', 5, 'gril')
insert into test222 values (7, '22', 0, 'body')
insert into test222 values (8, '222', 7, 'girll')
following is my code,,,
;WITH SInfo AS
(
SELECT
t.sId,
t.scode,
t.ParentId,
t.sName,
CONVERT(nvarchar(800), t.scode) AS Hierarchy,
t.ParentId as HParentId
FROM test222 as t
WHERE
t.sname like '%bo%'
UNION ALL
SELECT
si.sId,
si.scode,
si.ParentId,
si.sName,
CONVERT(nvarchar(800), TH.scode + '\' + si.Hierarchy),
th.parentid
FROM SInfo as si
INNER JOIN test222 TH
ON TH.sId = si.HParentId
)
Select t.sId, t.scode, t.ParentId, t.sName, t.Hierarchy
from SInfo as t
where
HParentId = 0 and
not exists (select 1 from SInfo as s
where
s.sid <> t.sid and
s.Hierarchy like t.Hierarchy + '%')
the op generated is shown below
5 111111 0 boyy 111111
7 22 0 body 22
3 1111 1 bo 11\1111
the third row is not correct
It should be
3 111111 1 bo 11\111\1111.
How can i do that???
All you need to do is change the parent id of the record - sid=3 to its chronological parent instead of its grand parent :-) . Check below.
Change
insert into #test222 values (3, '1111', 1, 'bo')
to
insert into #test222 values (3, '1111', 2, 'bo')
The reason you are not seeing the middle portion (record - sid:2) is because record - sid=2 and record - sid=3 essentially share the same "FROM" criteria. sname= '%bo%' (or rather LIKE '%bo%') and parentid=1. The record - sid=3 is the last record in the set with this shared "FROM" criteria and hence is the record being returned.
Order of SQL query execution (FROM, WHERE, GROUP BY, HAVING, SELECT, ORDER BY)
Here is a link to recursive CTE queries
Hope this helps.

Complex SQL query for inventory app

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