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

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

Related

Insert into one table from another two table

I have 3 tables
declare #approval_request table(req_id int, file_id int, req_type varchar(100))
insert into #approval_request
values (1, 1001, 'bulk'),
(2, 1002, 'demo'),
(3, 1003, 'bulk'),
(4, 1004, 'test');
declare #bulk_account table (file_id int, account varchar(50));
insert into #bulk_account
values (1001, '501'), (1002, '401'),
(1001, '502'), (1002, '402'),
(1001, '503'), (1002, '403');
I want to get all file_id from approval_request table where type='bulk' then
all accounts for corresponding file_id from second table(bulk_account ) and insert into third table as below.
declare #approval_bulk_account table
(
req_id int,
account varchar(50)
);
so the new table data will be
(req_id, account)
1 501
1 502
1 503
insert into #approval_bulk_account
select a.req_id,b.account
from #approval_request a
inner join #bulk_account b on a.file_id = b.file_id
and a.req_type = 'bulk';
Try to insert into the third table with inner join on first 2 tables, something like this
insert into approval_bulk_account
(req_id,account)
select a.req_id , b.account
from approval_request a inner join bulk_account b
on a.file_id =b.file_id
where a.req_type ='bulk'

SQL Server : how do do HAVING criteria on bit value

The below code demonstrates the problem I'm trying to solve. Basically I only want to select transactions where a customer has never purchased a product where isActive is now 0.
I tried using GROUP BY and HAVING MIN(p.isActive) = 1 to get only the customers associated with active products. This query is close but MIN does not like bit type. How can I do this?
The goal is to only see transactions associated with just Lisa and Fred. Bill should be removed from the results since one of his transactions is currently associated with a InActive product.
CustomerName ProductName (No column name)
Bill Cheerios 1
Lisa Cheerios 1
Bill Corn Flakes 1
Fred Corn Flakes 1
Bill Granola 0
ERROR: Operand data type bit is invalid for min operator.
CREATE TABLE #Customer (
CustomerId int,
CustomerName nvarchar(100),
Address nvarchar(100),
)
INSERT INTO #Customer
VALUES (1, 'Bill', '123 1st St'),
(2, 'Fred', '111 Market St'),
(3, 'Lisa', '01 Boulevard')
CREATE TABLE #Product (
ProductId int,
ProductName nvarchar(100),
isActive bit
)
INSERT INTO #Product
VALUES (1, 'Corn Flakes', 1),
(2, 'Cheerios', 1),
(3, 'Granola', 0)
CREATE TABLE #TransactionLog (
LogId int,
ProductId int,
CustomerId int,
Amount float
)
INSERT INTO #TransactionLog
VALUES (1, 1, 1, 2.00),
(2, 2, 1, 2.40),
(3, 3, 1, 1.80),
(4, 1, 1, 2.00),
(5, 1, 2, 2.00),
(6, 2, 3, 2.40)
SELECT * from #Customer
SELECT * from #Product
SELECT * from #TransactionLog
SELECT
c.CustomerName,
p.ProductName,
MIN(p.isActive)
FROM #TransactionLog t
join #Product p on t.ProductId = p.ProductId
join #Customer c on t.CustomerId = c.CustomerId
GROUP BY
c.CustomerName,
p.ProductName
HAVING
MIN(p.isActive) = 1
DROP TABLE #Customer
DROP TABLE #Product
DROP TABLE #TransactionLog
I'm not sure why but when I try HAVING MIN(CAST(p.isActive AS INT)) = 1, I still get 2 rows of data associated with Bill. I'd like to eliminate Bill from the result.
Bill Cheerios 1
Lisa Cheerios 1
Bill Corn Flakes 1
Fred Corn Flakes 1
with data as (
SELECT
c.CustomerName, p.ProductName,
count(case when p.isActive = 0 then 1 end)
over (partition by c.CustomerID) as countInactive
FROM #TransactionLog t
inner join #Product p on t.ProductId = p.ProductId
inner join #Customer c on t.CustomerId = c.CustomerId
)
select * from data where countInactive = 0;
You can accomplish it with min() over () as well if you prefer that logic.
You can always convert to a number:
HAVING MIN(CONVERT(tinyint, p.isActive)) = 1
This works for me - casting to INT as suggested in my comment:
CREATE TABLE #Customer (
CustomerId int,
CustomerName nvarchar(100),
Address nvarchar(100),
)
INSERT INTO #Customer
VALUES (1, 'Bill', '123 1st St'),
(2, 'Fred', '111 Market St'),
(3, 'Lisa', '01 Boulevard')
CREATE TABLE #Product (
ProductId int,
ProductName nvarchar(100),
isActive bit
)
INSERT INTO #Product
VALUES (1, 'Corn Flakes', 1),
(2, 'Cheerios', 1),
(3, 'Granoloa', 0)
CREATE TABLE #TransactionLog (
LogId int,
ProductId int,
CustomerId int,
Amount float
)
INSERT INTO #TransactionLog
VALUES (1, 1, 1, 2.00),
(2, 2, 1, 2.40),
(3, 3, 1, 1.80),
(4, 1, 1, 2.00),
(5, 1, 2, 2.00),
(6, 2, 3, 2.40)
SELECT * from #Customer
SELECT * from #Product
SELECT * from #TransactionLog
SELECT
c.CustomerName,
p.ProductName,
MIN(CAST(p.isActive AS int))
FROM #TransactionLog t
join #Product p on t.ProductId = p.ProductId
join #Customer c on t.CustomerId = c.CustomerId
GROUP BY
c.CustomerName,
p.ProductName
HAVING
MIN(CAST(p.isActive AS INT)) = 1
DROP TABLE #Customer
DROP TABLE #Product
DROP TABLE #TransactionLog
I would propose a join instead:
CREATE TABLE #Customer (
CustomerId int,
CustomerName nvarchar(100),
Address nvarchar(100),
)
INSERT INTO #Customer ( CustomerId, CustomerName, Address )
VALUES (1, 'Bill', '123 1st St'),
(2, 'Fred', '111 Market St'),
(3, 'Lisa', '01 Boulevard')
CREATE TABLE #Product (
ProductId int,
ProductName nvarchar(100),
isActive bit
)
INSERT INTO #Product (ProductId, ProductName, isActive)
VALUES (1, 'Corn Flakes', 1),
(2, 'Cheerios', 1),
(3, 'Granoloa', 0)
CREATE TABLE #TransactionLog (
LogId int,
ProductId int,
CustomerId int,
Amount float
)
INSERT INTO #TransactionLog (LogId, ProductId ,CustomerId ,Amount )
VALUES (1, 1, 1, 2.00),
(2, 2, 1, 2.40),
(3, 3, 1, 1.80),
(4, 1, 1, 2.00),
(5, 1, 2, 2.00),
(6, 2, 3, 2.40)
--SELECT * from #Customer
--SELECT * from #Product
--SELECT * from #TransactionLog
SELECT
c.CustomerName,
p.ProductName,
p.isActive
FROM #TransactionLog t
INNER JOIN #Product p on t.ProductId = p.ProductId AND p.isActive = 1
INNER JOIN #Customer c on t.CustomerId = c.CustomerId
GROUP BY
c.CustomerName,
p.ProductName
DROP TABLE #Customer
DROP TABLE #Product
DROP TABLE #TransactionLog
EDIT: PER NOTE, Get rid of Bill if he every ordered an inactive product
CREATE TABLE #Customer (
CustomerId int,
CustomerName nvarchar(100),
Address nvarchar(100),
)
INSERT INTO #Customer ( CustomerId, CustomerName, Address )
VALUES (1, 'Bill', '123 1st St'),
(2, 'Fred', '111 Market St'),
(3, 'Lisa', '01 Boulevard')
CREATE TABLE #Product (
ProductId int,
ProductName nvarchar(100),
isActive bit
)
INSERT INTO #Product (ProductId, ProductName, isActive)
VALUES (1, 'Corn Flakes', 1),
(2, 'Cheerios', 1),
(3, 'Granoloa', 0)
CREATE TABLE #TransactionLog (
LogId int,
ProductId int,
CustomerId int,
Amount float
)
INSERT INTO #TransactionLog (LogId, ProductId ,CustomerId ,Amount )
VALUES (1, 1, 1, 2.00),
(2, 2, 1, 2.40),
(3, 3, 1, 1.80),
(4, 1, 1, 2.00),
(5, 1, 2, 2.00),
(6, 2, 3, 2.40)
--SELECT * from #Customer
--SELECT * from #Product
--SELECT * from #TransactionLog
SELECT
c.CustomerName,
p.ProductName,
p.isActive
FROM #TransactionLog AS t
INNER JOIN #Product AS p on t.ProductId = p.ProductId
AND NOT EXISTS (
SELECT 1
FROM #TransactionLog AS ts
INNER JOIN #Product AS ps
ON ts.ProductId = ps.ProductId
AND ps.isActive = 0
WHERE ts.CustomerId = t.CustomerId
)
INNER JOIN #Customer c on t.CustomerId = c.CustomerId
GROUP BY
c.CustomerName,
p.ProductName,
p.isActive
DROP TABLE #Customer
DROP TABLE #Product
DROP TABLE #TransactionLog

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.

Using SQL - How to aggregate Payment Type

How can I aggregate the PaymentType field so that if a customer has used all payment types previously, the payment type screen displays 'All' instead of 'Cash', 'Check', 'Credit/Debit Card', or 'Corporate Account'? Below is the script necessary to demonstrate the issue.
CREATE TABLE #Customer (
CustomerId int,
CustomerName nvarchar(100),
Address nvarchar(100),
)
INSERT INTO #Customer
VALUES (1, 'Bill', '123 1st St'),
(2, 'Fred', '111 Market St'),
(3, 'Lisa', '01 Boulevard')
CREATE TABLE #Product (
ProductId int,
ProductName nvarchar(100)
)
INSERT INTO #Product
VALUES (1, 'Corn Flakes'),
(2, 'Cheerios'),
(3, 'Granoloa')
CREATE TABLE #PaymentType (
PaymentId int,
PaymentTypeName nvarchar(100)
)
INSERT INTO #PaymentType
VALUES (1, 'Cash'),
(2, 'Check'),
(3, 'Credit/Debit Card'),
(4, 'Corporate Account')
CREATE TABLE #TransactionLog (
LogId int,
ProductId int,
CustomerId int,
PaymentId int,
Amount float
)
INSERT INTO #TransactionLog
VALUES (1, 1, 1, 1, 2.00),
(2, 2, 1, 2, 2.40),
(3, 3, 1, 3, 1.80),
(4, 1, 1, 4, 2.00),
(5, 1, 2, 4, 2.00),
(6, 2, 3, 2, 2.40)
SELECT * from #Customer
SELECT * from #Product
SELECT * from #PaymentType
SELECT * from #TransactionLog
SELECT
c.CustomerName,
p.ProductName,
pt.PaymentTypeName as PaymentType
FROM #TransactionLog t
join #Product p on t.ProductId = p.ProductId
join #Customer c on t.CustomerId = c.CustomerId
join #PaymentType pt on t.PaymentId = pt.PaymentId
DROP TABLE #Customer
DROP TABLE #Product
DROP TABLE #PaymentType
DROP TABLE #TransactionLog
Something like this will work. You might need to tune it if you have a lot of data or an underpowered database instance though.
;WITH
Data AS(
SELECT
c.CustomerName,
p.ProductName,
pt.PaymentTypeName as PaymentType
FROM #TransactionLog t
join #Product p on t.ProductId = p.ProductId
join #Customer c on t.CustomerId = c.CustomerId
join #PaymentType pt on t.PaymentId = pt.PaymentId
)
SELECT DISTINCT
d.CustomerName,
d.ProductName,
PaymentType = IIF( ( (SELECT COUNT(DISTINCT sd.PaymentType) FROM Data sd WHERE sd.CustomerName = d.CustomerName) = 4),
'All',
d.PaymentType
)
FROM
Data d
select CustomerName,case when count(distinct(PaymentType)) = 4 then 'All' end as PaymentType from (SELECT
c.CustomerName,
p.ProductName,
pt.PaymentTypeName as PaymentType
FROM #TransactionLog t
join #Product p on t.ProductId = p.ProductId
join #Customer c on t.CustomerId = c.CustomerId
join #PaymentType pt on t.PaymentId = pt.PaymentId )
group by CustomerName
This is one way of doing this you can tweak it as per your need.

SQL Server : permutations/combinations without looping

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