How to sum columns when table is joined? - sql

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

Related

Group Order Line Items in Product Bundles

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..

SQL Case Reference

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

SQL for a Join that contains a condition within one of the join conditions

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;

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

Add column that counts times value occurs in results

I have a query with 5 columns. I want to add 1 column that defines the number of times the value in column 2 (ItemCode) occurs in the results.
This is my query:
SELECT
Items.Description_0 AS [Items.Description],
Items.ItemCode,
warehouse_location,
Stock.Quantity AS StockQty,
Stock.warehouse
FROM
((SELECT gbkmut.artcode, gbkmut.warehouse, ISNULL(gbkmut.warehouse_location,'') AS warehouse_location,
SUM(gbkmut.aantal) AS Quantity FROM gbkmut
INNER JOIN Items ON Items.ItemCode = gbkmut.artcode
INNER JOIN voorrd ON voorrd.artcode=gbkmut.artcode
AND voorrd.magcode=gbkmut.warehouse
INNER JOIN ItemUnits ON ItemUnits.Unit = Items.PackageDescription
WHERE gbkmut.reknr = Items.GLAccountDistribution
AND (( gbkmut.transtype IN ('N', 'C', 'P', 'X')
AND gbkmut.datum BETWEEN {d '2000-01-09'} AND {d '2031-02-08'} ) )
AND (gbkmut.warehouse='MAG1' )
AND Items.Type IN ('S','B')
AND NOT (Items.GLAccountAsset IS NOT NULL AND Items.IsSerialNumberItem=1) AND NOT (Items.Type = 'S' AND ItemUnits.UnitType = 'T')
GROUP BY gbkmut.artcode, gbkmut.warehouse, ISNULL(gbkmut.warehouse_location,'')
HAVING SUM(gbkmut.aantal) > 0)) Stock
INNER JOIN Items ON Items.ItemCode=Stock.artcode
WHERE Items.ItemCode like '10.27021%'
ORDER BY Items.ItemCode
Lazy version, use a common table expression (cte):
with cte as
(
[your huge select]
)
select t1.*, t2.codecount
from cte t1
join (select ItemCode, count(*) as codecount from cte group by ItemCode) as t2
ON t2.ItemCode = t1.ItemCode