Group Order Line Items in Product Bundles - sql

This is my schema:
CREATE TABLE SampleProducts
(
ProductId INT,
Name NVARCHAR(20)
)
INSERT INTO SampleProducts
VALUES (1, 'Product 1'), (2, 'Product 2'), (3, 'Product 3'),
(4, 'Product 4')
CREATE TABLE Bundle
(
BundleId INT,
Name NVARCHAR(20)
)
INSERT INTO Bundle
VALUES (1, 'Bundle 1'), (2, 'Bundle 2')
CREATE TABLE BundleProduct
(
BundleId INT,
ProductId INT
)
INSERT INTO BundleProduct
VALUES (1, 1), (1, 2), (2, 3), (2, 4)
CREATE TABLE SaleOrder
(
OrderId INT,
OrderNumber NVARCHAR(20)
)
INSERT INTO SaleOrder
VALUES (1, 'SO0001'), (2, 'SO0002'), (3, 'SO0003')
CREATE TABLE SaleOrderLine
(
OrderLineId INT,
OrderId INT,
ProductId INT
)
INSERT INTO SaleOrderLine
VALUES (1, 1, 1), (2, 1, 2), (3, 2, 1),
(4, 3, 3), (5, 3, 4)
I need to find orders where customers purchase products that could be grouped in a bundle. For example, in order SO0001, products 1 and 2 were sold, this order must be in the result. In SO0002 only were sold Product 1. SO0003 contains products from Bundle2. This is the set result I need to get:
Result
| OrderId | BundleId |
+---------+----------+
| 1 | 1 |
| 3 | 2 |
How can I get the result?

This is my best attempt to solve this problem:
SELECT o.[OrderId], bp.[BundleId]
FROM [SaleOrder] o
INNER JOIN [SaleOrderLine] l ON l.[OrderId] = o.[OrderId]
INNER JOIN [SampleProducts] p ON p.[ProductId] = l.[ProductId]
INNER JOIN [BundleProduct] bp ON bp.[ProductId] = l.[ProductId]
GROUP BY o.[OrderId], bp.[BundleId]
HAVING COUNT(*) = (
SELECT COUNT(*)
FROM [Bundle] b
INNER JOIN [BundleProduct] bp2 ON bp.[BundleId] = b.[BundleId]
WHERE bp2.[BundleId] = bp.[BundleId]
GROUP BY b.[BundleId]
)
First I join Sale Order Lines with Bundle Products, group them and then count them and compare against the number of Products in Bundle, if the result is equal, it means that a bundle can be created. The idea is similar to the solution proposed by Wolfgang Kais, but without the use of Common Table Expressions
*** Update
This is the new query removing unnecesary joins. Thank you #MatBailie
SELECT l.[OrderId], bp.[BundleId]
FROM [SaleOrderLine] l
INNER JOIN [BundleProduct] bp ON bp.[ProductId] = l.[ProductId]
GROUP BY l.[OrderId], bp.[BundleId]
HAVING COUNT(*) = (
SELECT COUNT(*)
FROM [BundleProduct] bp2
WHERE bp2.[BundleId] = bp.[BundleId]
GROUP BY bp2.[BundleId]
)

This query joins the SaleOrderLines with the Bundles to count the distinct products from each SaleOrder that are contained in a specific Bundle. If this number is the total number of products in that Bundle, we have a match:
WITH
BundleProductCount (BundleID, ProductCount) AS (
SELECT BundleId, COUNT(ProductId)
FROM BundleProduct
GROUP BY BundleId
),
OrderBundleProductCount (OrderId, BundleId, ProductCount) AS (
SELECT sol.OrderId, bp.BundleId, COUNT(DISTINCT sol.ProductId)
FROM SaleOrderLine sol
INNER JOIN BundleProduct bp ON sol.ProductId = bp.ProductId
GROUP BY sol.OrderId, bp.BundleId
)
SELECT ob.OrderId, ob.BundleId
FROM OrderBundleProductCount ob
INNER JOIN BundleProductCount b ON ob.BundleID = b.BundleID
WHERE ob.ProductCount = b.ProductCount;
Added:
A shorter version of what you came up with yourself, respecting products appearing multiple times on the same order:
SELECT l.OrderId, bp.BundleId
FROM SaleOrderLine l
INNER JOIN BundleProduct bp ON l.ProductId = bp.ProductId
GROUP BY l.OrderId, bp.BundleId
HAVING COUNT(DISTINCT l.ProductId) = (
SELECT COUNT(*)
FROM BundleProduct
WHERE BundleId = bp.BundleId
);

Row_Number is the key function here. The query can be written as:
select distinct
OrderId ,
BundleId
From
(
select SO.OrderId,B.BundleId ,
ROW_NUMBER() OVER(PARTITION BY SO.OrderId ORDER BY SO.OrderId ASC) AS Row#
from SaleOrderLine SOL
join SaleOrder SO on SOL.OrderId = SO.OrderId
join BundleProduct BP on BP.ProductId = SOL.ProductId
join Bundle B on B.BundleId = BP.BundleId
) As Test
where Row# > 1
Full Query here..

Related

How to sum columns when table is joined?

Trying to sum from a table that is joined to another (resulting in multiple rows per row of the first table) it is counting the amount for each of the rows in second table.
Tables:
create table t_orders (
oid int,
cartlink nvarchar(3),
ordertotal float,
ordertax float
);
create table t_cart (
cartlink nvarchar(3),
productid int
);
insert into t_orders (oid,cartlink,ordertotal,ordertax) values
(1,'abc',10, 2),
(2,'cdf',9, 1),
(3,'zxc',11, 3)
;
insert into t_cart (cartlink,productid) values
('abc', 123),('abc', 321),('abc', 987),
('cdf', 123),('cdf', 321),('cdf', 987),
('zxc', 123),('zxc', 321),('zxc', 987)
;
Using following values for t_orders table is more accurate to the problem. Using distinct only counts order 2 and 3 once because both of their totals are 9.
insert into t_orders (oid,cartlink,ordertotal,ordertax) values
(1,'abc',10, 2),
(2,'cdf',9, 1),
(3,'zxc',9, 3)
;
Query and result:
SELECT
SUM(t_orders.ordertotal) AS SumOfTotal,
SUM(t_orders.ordertax) AS SumOfTax
FROM
t_orders
JOIN t_cart ON t_orders.cartlink = t_cart.cartlink
;
SumOfTotal
SumOfTax
90
18
What I want :
SumOfTotal
SumOfTax
30
6
I have to join t_orders -> t_cart -> t_products -> t_manufacturer because I'm trying to sum from t_orders WHERE t_manufacturer.type = 'some value'.
Try this
SELECT SUM(DISTINCT t_orders.ordertotal) AS SumOfTotal
, SUM(DISTINCT t_orders.ordertax) AS SumOfTax
FROM t_orders
JOIN t_cart ON t_orders.cartlink = t_cart.cartlink
"… I'm trying to sum from t_orders WHERE t_manufacturer.type = 'some value'."
Using sub-query:
SELECT
SUM(t1.Total) AS SumOfTotal,
SUM(t1.Tax) AS SumOfTax
FROM
(
SELECT
MAX(t_orders.ordertotal) AS Total,
MAX(t_orders.ordertax) AS Tax
FROM t_orders
JOIN t_cart ON t_orders.cartlink = t_cart.cartlink
JOIN t_products ON t_cart.productid = t_products.pid
JOIN t_manufacturer ON t_products.manufacturerid = t_manufacturer.mid
WHERE t_manufacturer.type = 'some value'
GROUP BY t_orders.oid
) AS t1
Or:
SELECT
SUM(t1.Total) AS SumOfTotal,
SUM(t1.Tax) AS SumOfTax
FROM
(
SELECT
MAX(t_orders.ordertotal) AS Total,
MAX(t_orders.ordertax) AS Tax
FROM t_orders
JOIN t_cart ON t_orders.cartlink = t_cart.cartlink
JOIN t_products ON t_cart.productid = t_products.pid
WHERE t_products.manufacturerid IN (
SELECT mid FROM t_manufacturer WHERE type = 'some value'
) AS t2
GROUP BY t_orders.oid
) AS t1

Combining two columns based on match in other colums in two tables

I have two tables, each containing two identical columns and one column unique for that table. What I need to do is combine those tables, with combinations of those unique columns for each matching pair of identical columns as result. Example of what I mean:
ACC ACTION PRIORITY ACC ACTION TARGET
A 1 10 A 1 i
A 2 15 A 1 j
A 3 25 A 3 k
B 3 101 B NULL l
B 4 102 B 4 m
B 5 103 B 1 n
ACC and ACTION are columns in both tables. ORDER is unique for the left one, TARGET for the right one. I need to get combinations of ORDER and TARGET on rows where ACC and ACTION match - for example when ACC is A and ACTION is 1, PRIORITY is 10, and TARGET is I or j, therefore combinations would be "10 I" and "10 j".
Also, when ACTION is null in right table, there should be row with the top PRIORITY on that TARGET.
So, expected result:
PRIORITY TARGET
10 i
10 j
25 k
102 m
103 l
Any attempt to do a correct JOIN or so failed from my side.
What I tried:
INSERT INTO #RESULT(TARGET, PRIORITY)
SELECT R.TARGET, MAX(L.PRIORITY)
FROM LEFT_TABLE L INNER JOIN RIGHT_TABLE R
ON L.ACC=R.ACC AND (L.ACTION = R.ACTION OR R.ACTION IS NULL);
But it gives an error. Grouping by TARGET does not make the right output, though.
I used UNION to solve this
SELECT priority, t.target
FROM prio p
JOIN target t ON p.acc = t.acc AND t.action = p.action
UNION
SELECT priority, t.target
FROM prio p
JOIN target t ON p.acc = t.acc AND t.action is null
AND p.priority = (SELECT MAX(priority) FROM prio)
See if this works. I did not test it.
DECLARE #L TABLE(ACC NVARCHAR(10),Action INT,Priority INT)
INSERT #L (ACC,Action,Priority) VALUES ('A',1,10),('A',2,15),('A',3,25),('B',4,101),('B',5,102),('B',6,103)
DECLARE #R TABLE(ACC NVARCHAR(10),Action INT,Target NVARCHAR(10))
INSERT #R (ACC,Action,Target) VALUES ('A',1,'i'),('A',1,'j'),('A',3,'k'),('B',NULL,'l'),('B',4,'m'),('B',1,'n')
SELECT
Target = MAX(R.Target),
Priority = MAX(L.Priority)
FROM
#L L
INNER JOIN #R R ON R.ACC=L.Acc AND (R.ACTION=L.Action OR R.Action IS NULL)
GROUP BY
L.ACC,
L.Action
ORDER BY
MAX(L.Priority)
You can do it like this:
Sample data
create table a
(
acc nvarchar(50),Action1 int, priority1 int)
create table b (acc nvarchar(50),action1 int, target1 nvarchar(50)
)
insert into a
values
('A',1, 10 ),
('A',2, 15 ),
('A',3, 25 ),
('B',3, 101),
('B',4, 102),
('B',5, 103)
insert into dbo.b
values
('A',1,'i'),
('A',1,'j'),
('A',3,'k'),
('B',null,'l'),
('B',4,'m'),
('B',1,'n')
SQL
with data1 as (
select acc,case when action1 is null then maxaction1 else action1 end as action1,target1 from (
select * from dbo.b a
cross apply (select MAX(action1) as MaxAction1 from dbo.a b where a.acc = b.acc ) x
)z
)
select priority1,target1 from data1 a inner join dbo.a b on a.acc = b.acc and a.action1 = b.Action1
Update
Just saw that you wrote you want it on Priority. Then you can do it like this:
SQL Code Update
with data1 as (
select * from (
select y.acc,y.target1,COALESCE(y.action1,c.action1) as Action1 from (
select * from dbo.b a
cross apply (select MAX(priority1) as MaxP from dbo.a b where a.acc = b.acc ) x
)y inner join dbo.a c on c.priority1 = MaxP
)z
)
select priority1,target1 from data1 a inner join dbo.a b on a.acc = b.acc and a.action1 = b.Action1
Result
You can try this:
WITH
tTable1
AS ( SELECT ACC
, [ACTION]
, [PRIORITY]
FROM ( VALUES ('A', 1, 10 )
, ('A', 2, 15 )
, ('A', 3, 25 )
, ('B', 3, 101)
, ('B', 4, 102)
, ('B', 5, 103)
) tTable1(ACC, [ACTION], [PRIORITY])
)
, tTable2
AS ( SELECT ACC
, [ACTION]
, [TARGET]
FROM ( VALUES ('A', 1 , 'i')
, ('A', 1 , 'j')
, ('A', 3 , 'k')
, ('B', NULL, 'l')
, ('B', 4 , 'm')
, ('B', 1 , 'n')
) tTable1(ACC, [ACTION], [TARGET])
)
, tTable1Max
AS ( SELECT ACC
, [PRIORITY] = MAX([PRIORITY])
FROM tTable1
GROUP BY ACC
)
, tResult
AS ( SELECT [PRIORITY] = CASE WHEN tTable2.[ACTION] IS NOT NULL
THEN tTable1.[PRIORITY]
ELSE tTable1Max.[PRIORITY]
END
, tTable2.[TARGET]
FROM tTable2
LEFT JOIN tTable1 ON tTable1.ACC = tTable2.ACC
AND tTable1.[ACTION] = tTable2.[ACTION]
OUTER APPLY ( SELECT [PRIORITY]
FROM tTable1Max
WHERE tTable1Max.ACC = tTable2.ACC
) tTable1Max
)
SELECT *
FROM tResult
WHERE [PRIORITY] IS NOT NULL
ORDER BY [PRIORITY]

Querying a column that shows [count] of [max count]

I have 3 tables:
CustomerTypes table
Customers table (has a foreign key CustomerType). A customer can only have one customer type.
CustomersCollection table (contains many customerIds)
The primary SELECT of the query will be on CustomerTypes. I will be selecting two columns: CustomerTypeName and CountInCollection
The CustomerCount column in my query needs to show something like the following:
[Total # of Customers in CustomerType that are in Customer Collection] Of [Total # of Customers in CustomerType]
How can I get the proper customer count of the CustomerType that is part of the collection?
Example:
Customer1, Customer2, and Customer3 are all of CustomerTypeA.
CustomerCollection1 has customers Customer1 and Customer2 in it. The CountInCollection column for the CustomerTypeA record should show '2 of 3'.
Here is how I am able to get each count in separate queries:
-- Total customers in customer collection of customer type
SELECT COUNT(c.Id)
FROM Customer c
INNER JOIN CustomerCollection cc ON c.Id = cc.CustomerId
WHERE cc.CollectionId = 1019 AND c.CustomerTypeId=1000
-- Total customers in customer type
SELECT COUNT(Id) FROM
Customer WHERE CustomerTypeId=1000
Since you are using SQL 2008, I would take advantage of Common Table Expressions, aka CTEs, to assemble the data.
First, we'll need some test data. NOTE: I've thrown in some 'outliers' so that you can see where this kind of logic can bite you later.
DECLARE #CustomerTypes TABLE
(
CustomerTypeID INT,
[Customer Type] VARCHAR(100)
)
INSERT INTO #CustomerTypes
SELECT 1, 'TypeA'
UNION SELECT 2, 'TypeB'
UNION SELECT 3, 'TypeC' --NOTE: An outlier (not in customers-collection)
UNION SELECT 4, 'TypeD' --NOTE: An outlier (not in customers)
DECLARE #Customers TABLE
(
CustomerID INT,
CustomerTypeID INT
)
INSERT INTO #Customers
SELECT 1, 1
UNION SELECT 2, 1
UNION SELECT 3, 1
UNION SELECT 4, 2
UNION SELECT 5, 2
UNION SELECT 6, 2
UNION SELECT 7, 3
DECLARE #CustomersCollection TABLE
(
CollectionID INT IDENTITY(1,1),
CustomerID INT
)
INSERT INTO #CustomersCollection
(CustomerID)
SELECT TOP 2 --INSERT 2 of 3
CustomerID FROM #Customers WHERE CustomerTypeID = 1 --TypeA
INSERT INTO #CustomersCollection
(CustomerID)
SELECT TOP 1 --INSERT 1 of 3
CustomerID FROM #Customers WHERE CustomerTypeID = 2 --TypeB
Second, assemble the CTE data, and generate your output
;WITH CTE_COUNT_TYPE(CustomerTypeID, TypeCount)
AS
(
SELECT CustomerTypeID, COUNT(1)
FROM #Customers
GROUP BY CustomerTypeID
)
--SELECT * FROM CTE_COUNT_TYPE --DEBUG
,
CTE_COUNT_COLLECTION(CustomerTypeID, CollectionCount)
AS
(
SELECT CustomerTypeID, COUNT(1)
FROM #CustomersCollection CC
INNER JOIN #Customers C
ON CC.CustomerID = C.CustomerID
GROUP BY CustomerTypeID
)
--SELECT * FROM CTE_COUNT_COLLECTION --DEBUG
SELECT [Customer Type],
--CONVERT is necessary to combine INT data type (i.e. Count) and VARCHAR data type (i.e. 'as')
CONVERT(VARCHAR(100), COALESCE(CCC.CollectionCount, 0)) +
' of ' +
CONVERT(VARCHAR(100), COALESCE(CCT.TypeCount, 0)) As [Count in Collection]
FROM #CustomerTypes CT
LEFT OUTER JOIN #Customers C --Left outer join assists in outliers
ON CT.CustomerTypeID = C.CustomerTypeID
LEFT OUTER JOIN CTE_COUNT_TYPE CCT --Left outer join assists in outliers
ON CCT.CustomerTypeID = CT.CustomerTypeID
LEFT OUTER JOIN CTE_COUNT_COLLECTION CCC --Left outer join assists in outliers
ON CCC.CustomerTypeID = CT.CustomerTypeID
GROUP BY CT.[Customer Type]
, CCC.CollectionCount
, CCT.TypeCount
Hope so i get the question-
select
ct.CustomerTypeName as [Customer Type],
convert(varchar(30),count(cc.CollectionId)) + ' of ' + convert(varchar(30), count(c.CustomerId)) as [Count in Collection]
from
#Customer c
inner join #CustomerType ct on ct.CustomerTypeId = c.CustomerTypeId
left join #CustomerCollection cc on cc.CustomerId = c.CustomerId
group by
CustomerTypeName
Data script-
declare #customerType table (CustomerTypeId int, CustomerTypeName varchar(100))
insert into #customerType (CustomerTypeId, CustomerTypeName)
select 30, 'TypeA'
union
select 40, 'TypeB'
declare #customer table (CustomerId int, CustomerTypeId int)
insert into #customer (CustomerId, CustomerTypeId)
select 1, 30
union
select 2, 30
union
select 3, 30
union
select 4, 40
union
select 5, 40
union
select 6, 40
declare #customercollection table (CollectionId int, CustomerId int)
insert into #customercollection (CollectionId, CustomerId)
select 100, 1
union
select 200, 2
union
select 300, 5

SQL Query return latest date with additional information from tables

I have 5 tables and columns pertinent to this query:
Dogs (ID, CallName, Color, Sex, Chipnumber, BreedID)
Breed (ID, Name)
Status (ID, Status, OwnedOnPremises)
DogsStatus (ID, DogsID, StatusID, StatusDate, Note, ContactsID)
Contacts (ID, Name)
I am wanting a result of all dogs and their LATEST status. For a test I am using the following records:
Dogs (251, Tank, Fawn, M, 14410784, 23) (266, Bonnie, Brindle, 14964070, 23)
Breed (23, Mastiff)
Status (3, Sold) (4, Given Away) (7, Purchased) (9, Returned)
DogsStatus (29, 251, 3, 2013-10-12, 5) (39, 251, 9, 2013-11-10, 17) (146, 251, 4, 2014-01-10, 7) (40, 266, 7, 2013-10-30, 1)
Contacts (1, Person1) (5, Person5) (7, Person7) (17, Person17)
So far I have:
SELECT
d.CallName, b.Name AS 'Breed', d.Color, d.Sex, d.ChipNumber
FROM
Dogs d
JOIN
(SELECT
DogsID, MAX(StatusDate) as MaxStatusDate
FROM DogsStatus
GROUP BY DogsID) mds ON mds.DogsID = d.ID
JOIN
Breeds b ON b.ID = d.BreedID
This will return 2 unique records (1 for Tank and 1 for Bonnie), but whenever I try to get any other of the DogsStatus and/or Status info, I either return only one dog record, or all 3 of Tanks DogsStatus records.
Thanks in advance.
You'll need to join your MaxStatusDate to the DogsStatus table. That way you will only get the most recent status, in the case where you have multiple statuses.
Something like
SELECT
d.CallName, b.Name AS 'Breed', d.Color, d.Sex, d.ChipNumber
FROM
Dogs d
innner join DogsStatus ds
ON d.dogsid = ds.dogs_id
JOIN
(SELECT
DogsID, MAX(StatusDate) as MaxStatusDate
FROM DogsStatus
GROUP BY DogsID) mds ON mds.DogsID = d.ID
JOIN
Breeds b ON b.ID = d.BreedID
AND mds.maxstatusdate = ds.statusdate
Something along those lines.
You're close. You just need to go back to the DogStatus table to get that full record. Note that I prefer CTEs for this, but your existing derived table (subquery) approach works just fine, too:
With StatusDates As
(
SELECT
DogsID, MAX(StatusDate) as StatusDate
FROM DogsStatus
GROUP BY DogsID
), CurrentStatus As
(
SELECT ds.*
FROM DogStatus ds
INNER JOIN StatusDates sd ON sd.DogsID = ds.DogsID AND ds.StatusDate = sd.StatusDate
)
SELECT d.Name, b.Name As Breed, d.Color, d.Sex, d.ChipNumber
, s.Status, cs.StatusDate, c.Name As ContactName
FROM Dogs d
INNER JOIN CurrentStatus cs ON cs.DogsID = d.ID
INNER JOIN Breed b on b.ID = d.BreedID
INNER JOIN Status s on s.ID = cs.StatusID
INNER JOIN Contact c on c.ID = cs.ContactID
You may want to use a LEFT join for some of those, and then change the select list to use coalesce() expressions to clean up the NULLs.
It would be something like this, I'm certain you'll be able to adapt it to your needs:
;with x as (
select *, row_number() over(partition by d.DogsId order by StatusDate desc) as rn
from Dogs d
inner join DogsStatus on d.DogsId = ds.DogsId
)
select *
from x
where rn = 1

SQL get rows in '= ALL' style

I have simple many-to-many relation in table Product_Category (MSSQL 2008 r2):
CREATE TABLE #Product_Category (ProductId int, CategoryId int);
go
INSERT INTO #Product_Category (ProductId, CategoryId)
VALUES (1, 200);
go
INSERT INTO #Product_Category (ProductId, CategoryId)
VALUES (2, 200);
go
INSERT INTO #Product_Category (ProductId, CategoryId)
VALUES (2, 400);
go
INSERT INTO #Product_Category (ProductId, CategoryId)
VALUES (3, 300);
go
INSERT INTO #Product_Category (ProductId, CategoryId)
VALUES (2, 300);
go
DROP TABLE #Product_Category
How can I select ProductId with condition: CategoryId = 200 and CategoryId = 300 and CategoryId = 400?
Query example (sql below doesn't work):
SELECT ProductId FROM #Product_Category
WHERE CategoryId = ALL (select 200 union select 300 union select 400)
I expect result: ProductId = 2
select PC.ProductId
from #Product_Category as PC
where PC.CategoryId in (200, 300, 400)
group by PC.ProductId
having count(distinct PC.CategoryId) = 3
Update:
It is still ugly but it does work:
SELECT DISTINCT master.ProductId
FROM #Product_Category master
JOIN (
SELECT ProductId,
cat200 = max(case when CategoryId=200 then 1 else 0 end),
cat300 = max(case when CategoryId=300 then 1 else 0 end),
cat400 = max(case when CategoryId=400 then 1 else 0 end)
FROM #Product_Category
GROUP BY ProductId
) sub ON sub.ProductId = master.ProductId
WHERE cat200=1
and cat300=1
AND cat400=1
try this
SELECT a.ProductId
FROM Product_Category as a,
Product_Category as b,
Product_Category as c
WHERE a.CategoryId = 200
And b.`CategoryId` = 300
And c.`CategoryId` = 400
And a.`ProductId` = b.`ProductId`
And b.`ProductId` = c.`ProductId`
for more like 500 and 600
SELECT a.ProductId
FROM Product_Category as a,
Product_Category as b,
Product_Category as c,
Product_Category as d,
Product_Category as e,
WHERE a.CategoryId = 200
And b.`CategoryId` = 300
And c.`CategoryId` = 400
And d.`CategoryId` = 500
And e.`CategoryId` = 600
And a.`ProductId` = b.`ProductId`
And b.`ProductId` = c.`ProductId`
And c.`ProductId` = d.`ProductId`
And d.`ProductId` = e.`ProductId`
check live demo http://sqlfiddle.com/#!2/8965e/1/0
Here I am using a CTE, but you could use a table variable or something different for the category_filter.
with category_filter as (
select * from (values (200), (300), (400)) as v(id)
)
select distinct ProductId
from #Product_Category
join category_filter
on (#Product_Category.CategoryId = category_filter.id)
group by ProductId
having COUNT(distinct CategoryId) = (select COUNT(*) from category_filter)
Also ugly solution :)
WITH category_filter1(CategoryId) AS (
SELECT * FROM (VALUES (200), (300), (400)) tmp1(tmp2)
)
SELECT p.ProductId
FROM (
SELECT ProductId,
CASE WHEN CategoryId IN (SELECT CategoryId FROM category_filter1) THEN 1 ELSE 0 END f
FROM #Product_Category
) p
GROUP BY p.ProductId, p.f
HAVING COUNT(*) = (SELECT COUNT(*) FROM category_filter1);
You can use Values as Table Source and check them in WHERE clause with NOT EXISTS and EXCEPT operators
SELECT *
FROM #Product_Category p
WHERE NOT EXISTS (
SELECT Match
FROM (VALUES(200),
(300),
(400))
x(Match)
EXCEPT
SELECT CategoryId
FROM #Product_Category p2
WHERE p.ProductID = p2.ProductID
)
Demo on SQLFiddle
WITH L AS (
SELECT *
FROM (VALUES (200),(300),(400)) AS T(CategoryId)
)
SELECT ProductId
FROM Product_Category P
INNER JOIN L
ON L.CategoryId = P.CategoryId
GROUP BY ProductId
HAVING COUNT(1) = (SELECT Count(1) FROM L)
;
The WITH disappears if you are planning to use TVP.