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.
Related
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
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..
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]
Using the code below (thank you #BHouse), I am attempting to adjust "ECOMMERCE" selling price to reflect "ENFIELD" selling price. As you can see from results, I am getting multiple entries.
Results can been seen here
SELECT
T1.STRTRADECODE AS [STORE],
T1.LINTITEMNUMBER AS [SKU],
CASE WHEN T1.STRTRADECODE = 'ECOMMERCE' THEN T2.CURNORMALSELL ELSE T1.CURNORMALSELL END AS [SELLINGPRICE]
FROM DAILYSALES T1
LEFT JOIN DAILYSALES T2 ON T1.LINTITEMNUMBER = T2.LINTITEMNUMBER AND T1.STRTRADECODE <> T2.STRTRADECODE
WHERE
T1.DTMTRADEDATE >= '2018-01-02 00:00:00'
AND T1.STRSALETYPE = 'I'
AND T1.STRTRADECODE IN ('ECOMMERCE', 'ENFIELD')
AND T1.LINTITEMNUMBER = '18760'
GROUP BY
T1.STRTRADECODE,
T1.LINTITEMNUMBER,
T1.CURNORMALSELL,
T2.CURNORMALSELL
ORDER BY
T1.STRTRADECODE DESC
Any help will be much appreciated.
Added Sku to the table , matching Sku and different store numbers will retrieve selling value for store2.
This query will help u, to use values matching another store
SELECT T1.STRTRADECODE AS [STORE], T1.LINTITEMNUMBER AS [SKU], T2.CURNORMALSELL AS [SELLINGPRICE]
FROM DAILYSALES T1
INNER JOIN (
SELECT *
FROM DAILYSALES
WHERE name = 'enfield'
) t2
ON t1.sku = t2.sku
WHERE T1.DTMTRADEDATE >= '2018-01-02 00:00:00' AND T1.STRSALETYPE = 'I' AND T1.STRTRADECODE IN (
'ECOMMERCE'
,'ENFIELD'
) AND T1.LINTITEMNUMBER = '18760'
ORDER BY T1.STRTRADECODE DESC
sample:
DECLARE #testtable TABLE (id INT identity(1, 1), name VARCHAR(100), sellingPrice FLOAT, sku BIGINT)
INSERT INTO #testtable (name, sellingPrice, sku)
SELECT 'Enfield', '60.50', '16043853021'
UNION
SELECT 'Ecommerce', '190.50', '16043853021'
UNION
SELECT 'Enfield', '120.45', '06062829018'
UNION
SELECT 'Ecommerce', '180.45', '06062829018'
;
select distinct t.id,t.name,t.sku,t2.sellingPrice
from #testtable t Inner join (select * from #testtable where name = 'enfield') t2
on t.sku = t2.sku
I have a requirement to do a left join between two tables. TableA is a transactional table while TableB contains reference data. The logical rule for my join is as follow:
SELECT *
FROM
TableA a
LEFT JOIN TableB b
ON a.ItemCode = b.ItemCode
AND a.ItemType = b.ItemType
AND b.FundID = 1 (but if no match found use b.FundID = 99)
The last join condition, the part in brackets, is what I'm having trouble with.
EDIT: Some clarification - If no match is found on ItemCode & ItemType & FundID = 1 then I want to join on ItemCode & ItemType & FundID = 99. Also TableB might have two records that match on both ItemCode and ItemType with one record having a FundID = 1 and the second record having FundID = 2. I that case I only want the record with FundID = 1.
What would be the most efficient way to write this query?
The only thing I can come up with is to execute the query twice, once with FundID = 1 and then with FundID = 99. Then use a set operator to return all the records form the first query and only records from the second query that does not exist in the first one. The code will not be pretty and it does not seem efficient either.
Thanks for your ideas in advance.
Marius
If i do understand your requirement correctly, this should gives you what you want
SELECT *
FROM
TableA a
OUTER APPLY
(
SELECT TOP 1 *
FROM TableB b
WHERE a.ItemCode = b.ItemCode
AND a.ItemType = b.ItemType
AND b.FundID IN (1, 99)
ORDER BY b.FundID
) b
You can change the query to
AND b.FundID IN (1,99)
or
AND (b.FundID = 1 or b.FundID = 99)
This is the best solution I have received so far. Thanks to #HABO (see the comments section of my question).
Add a column to create a Row_Number() partitioned on ItemType and ItemCode and ordered by FundId, then use only the results with row number 1
For posterity:
-- Sample data.
declare #TableA as Table ( AId Int Identity, ItemCode VarChar(20), ItemType VarChar(20) );
declare #TableB as Table ( BId Int Identity, ItemCode VarChar(20), ItemType VarChar(20), FundId Int );
insert into #TableA ( ItemCode, ItemType ) values
( 'Nemo', 'Fish' ), ( 'Blinky', 'Fish' ), ( 'Muddy Mudskipper', 'Fish' ),
( 'Hammer', 'Tool' ), ( 'Screwdriver', 'Tool' ), ( 'Politician', 'Tool' ),
( 'Grape Nehi', 'Beverage' ), ( 'Screwdriver', 'Beverage' );
insert into #TableB ( ItemCode, ItemType, FundId ) values
( 'Blinky', 'Fish', 1 ), ( 'Muddy Mudskipper', 'Fish', 2 ),
( 'Hammer', 'Tool', 1 ), ( 'Screwdriver', 'Tool', 99 ),
( 'Politician', 'Tool', 1 ), ( 'Politician', 'Tool', 99 ),
( 'Grape Nehi', 'Beverage', 42 ), ( 'Screwdriver', 'Beverage', 1 );
select * from #TableA;
select * from #TableB;
-- Do the deed.
with JoinWithRanking as (
select A.AId, A.ItemCode, A.ItemType, B.BId, B.FundId,
Row_Number() over ( partition by A.ItemCode, A.ItemType order by B.FundId ) as RN
from #TableA as A left outer join
#TableB as B on B.ItemCode = A.ItemCode and B.ItemType = A.ItemType and
B.FundId in ( 1, 99 )
)
select AId, ItemCode, ItemType, BId, FundId
from JoinWithRanking
where RN = 1;