JOIN Multiple Tables with SUM - sql

I need help with the scenario below.
order table
orderId orderAmount
10001 1000.00
10002 2000.00
10003 3000.00
10004 1000.00
10005 1000.00
transfer table
transferId orderId transferAmount
9001 10001 100.00
9002 10001 200.00
9003 10001 25.00
9004 10002 250.00
9005 10002 450.00
9006 10004 100.00
returns table
returnId orderId returnAmount
8001 10001 450.00
8002 10001 50.00
8003 10002 245.00
8004 10003 100.00
Result Needed
/*include all orders from orders table, even if no transfers or returns*/
orderId transfers returns
10001 325.00 500.00 /*sum of transfers and returns per orderId from respective tables*/
10002 700.00 245.00
10003 0.00 100.00
10004 100.00 0.00
10005 0.00 0.00 /*use zero whenever no rows for orderId in respective table*/
How can I implement the above in single query?

This is tricky. I think the easiest way is union all. If I have the arithmetic correct, you want to add the order amount to transfers but not returns:
select orderId,
sum(transfers) as transfers,
sum(returns) as returns
from ((select orderId, Amount, 0 as transfers, 0 as returns
from orders o
) union all
(select orderId, 0, transferAmount, 0
from transfers
) union all
(select orderId, 0, 0, returnAmount
from transfers
)
) otr
group by orderId;

You can just select all the order lines.
For each order, you subquery the tables linked by the OrderId.
SELECT o.orderId
, (SELECT sum(TransferAmount)
FROM Transaction t
WHERE t.OrderId = o.OrderId) 'Transaction'
, (SELECT sum(r.ReturnAmount)
FROM Result r
WHERE r.OrderId = o.OrderId) 'Return'
FROM Order o

I would use derived tables, something like (Apologies if the syntax is a little off, not tested):
SELECT O.OrderID,COALESCE(T.Total,0) AS [Transfer Total],COALESCE(R.Total,0) AS [Return Total]
FROM Orders AS O
LEFT JOIN
(
SELECT T.OrderID,SUM(T.TransferAmount) [Total]
FROM Transfer_Table AS T
GROUP BY T.OrderID
) AS T ON O.OrderID = T.OrderID
LEFT JOIN
(
SELECT R.OrderID,SUM(R.ReturnAmount) [Total]
FROM Return_Table AS R
GROUP BY R.OrderId
) AS R ON R.OrderId = O.OrderID

Related

How to show two calculated columns to an existing table

I would like to pull a report that will add two columns to the first two columns of an existing table (Shipment_Info). Shipment_Info has three columns ShipmentID and ItemID and Item_Status. ItemID values are always unique, but ShipmentIDs will repeat because different Items can be in the same shipment, and item status can be in different states (Allocated, Filled and Packed)
I want to run a query that will add two column to the existing table (first two columns). The first new column (Shipment_Size) will show how many unique items there are in that specific shipmentID, and the 2nd new column (Shipment_ready) will show if the entire shipmentID is ready to be shipped. For an shipmentID to be ready to be ItemIDs must be in a "Packed" status. Any help would be greatly appreciated.
DB Table: Shipment_INFO
ShipmentID
ItemID
Item_status
10001
20001
Packed
10002
20002
Allocated
10002
20003
Packed
10003
20004
Filled
10004
20005
Packed
10004
20006
Packed
10004
20007
Packed
10005
20008
Filled
10005
20009
Packed
10006
20010
Filled
Ideal output.
ShipmentID
ItemID
Shipment_Size
Shipment_Ready
10001
20001
1
Yes
10002
20002
2
No
10002
20003
2
No
10003
20004
1
No
10004
20005
3
Yes
10004
20006
3
Yes
10004
20007
3
Yes
10005
20008
2
No
10005
20009
2
No
10006
20010
1
No
Someone help me with getting the Shipment_Size using this query, but I'm struggling to figure out how to integrate the the Shipment_Packed column to the query below.
select s.shipmentID, s.ItemID, i.Shipment_Size
from Shipment_INFO s
inner join ( select shipmentID, count(*) as Shipment_Size
from Shipment_INFO
group by shipmentID ) i on i.shipmentID=s.shipmentID`
Something like this should work
select
shipmentid,
itemid,
count(*) over (partition by shipmentid) as shipment_size,
case
when (
select count(*) from shipment_info si2
where si2.shipmentid = si.shipmentid
and si2.item_status = 'Packed'
) = count(*) over (partition by shipmentid)
then 'Yes'
else 'No'
end as shipment_ready
from
shipment_info si
or:
with
shipment_status as (
select
shipmentid,
count(1) as shipment_size,
sum(case item_status when 'Packed' then 1 else 0 end) as packed_count
from
shipment_info
group by
shipmentid
)
select
si.shipmentid,
si.itemid,
ss.shipment_size,
case ss.packed_count
when ss.shipment_size then 'Yes'
else 'No'
as shipment_ready
from
shipment_info si
join
shipment_status ss on ss.shipmentid = si.shipmentid
example for you :
select ShipmentID,ItemID,
count(ItemID) over (partition by ShipmentID) Shipment_Size,
case when
sum(case when Item_status='Packed' then 1 else 0 end) OVER (partition by ShipmentID ) =count(ItemID) over (partition by ShipmentID)
then 'Yes' else 'no' end as Shipment_Ready
from Shipment_INFO
group by ShipmentID,Item_status,ItemID
demo :https://dbfiddle.uk/80HA0Njd
You want to select all rows, but for each row look at its whole shipment. To do this use analytic funtions with OVER (PARTITION BY shipmentid):
select
shipmentid,
itemid,
count(*) over (partition by shipmentid) as shipment_size,
min(item_status) over (partition by shipmentid) = 'Packed' as shipment_ready
from shipment_info
order by itemid;
I am using a trick here. 'Packed' is the last status in alphabetical order. So if the minimum status for a shipment is 'Packed', then all its items are packed and the shipment is ready.

SQL Query to get value of recent order alongwith data from other tables

I am writing an SQL query to get data from more than 3 tables, but for simplifying the question here I am using a similar scenario with 3 tables.
Table1 Customer (PK-CustomerID, Name)
CustomerID
Name
1
John
2
Tina
3
Sam
Table2 Sales (FK-Id, SalePrice)
ID
SalePrice
1
200.00
2
300.00
3
400.00
Table3 Order (PK-Id, FK-CustomerID, Date, Amount)
Id
CustomerID
Date
Amount
101
1
25-09-2021
30.0
102
1
27-09-2021
40.0
103
2
19-09-2021
60.0
In the output, Date and Amount should be the from most recent Order (latest Date), for a customer
My approach was
Select c.CustomerID, c.Name, s.SalePrice, RecentOrder.Date, RecentOrder.Amount from
Customer as c
LEFT JOIN Sales s ON c.CustomerID = s.ID
LEFT JOIN (SELECT top 1 o.Date, o.Amount, o.CustomerID
FROM Order o, Customer c1 WHERE c1.CustomerID = o.CustomerID ORDER BY o.Date DESC)
RecentOrder ON c.CustomerID = RecentOrder.CustomerID
Output I get
CustomerID, Name, SalePrice, Date, Amount
CustomerID
Name
SalePrice
Date
Amount
1
John
200.00
27-09-2021
40.0
2
Tina
300.00
null
null
3
Sam
400.00
null
null
The output I get includes the most recent order out of all the orders. But I want to get the recent order out of the orders made by that customer
Output Required
CustomerID, Name, SalePrice, Date, Amount
CustomerID
Name
SalePrice
Date
Amount
1
John
200.00
27-09-2021
40.0
2
Tina
300.00
19-09-2021
60.0
3
Sam
400.00
null
null
Instead of subquery in left join, you can check with outer apply.
Check following way
Select c.CustomerID, c.Name, s.SalePrice, RecentOrder.Date, RecentOrder.Amount
from Customer c
LEFT JOIN Sales s ON c.CustomerID = s.ID
OUTER APPLY (
SELECT top 1 o.Date, o.Amount, o.CustomerID
FROM [Order] o
WHERE o.CustomerID = c.CustomerID ORDER BY o.Date DESC) RecentOrder`
You need to pre-aggregate or identify the most recent order for each customer order, your query is selecting 1 row for all orders.
Try the following (untested!)
select c.CustomerID, c.Name, s.SalePrice, o.Date, o.Amount
from Customer c
left join Sales s on c.CustomerID = s.ID
outer apply (
select top (1) date, amount
from [order] o
where o.CustomerId=c.CustomerId
order by Id desc
)o

Calculate column amount and get total with order number?

How to calculate each order number amount and total amount with SQL status
Order_no Status Amount
9008258656 P 50.00
9008258656 P 0.00
9008510713 P 50.00
9008510713 P 0.00
Well, it looks like you want a simple aggregated query :
SELECT order_no, count(*) number_of_orders, sum(amount) total_amount
FROM orders
GROUP BY order_no
If you need to filter on a specific status :
SELECT order_no, count(*) number_of_orders, sum(amount) total_amount
FROM orders
WHERE status = 'P'
GROUP BY order_no
If you are looking to keep your individual line numbers (i.e. 4 total records) and not have aggregates (i.e. 2 total records), you can use the sum window function.
SELECT ord.Order_no
, ord.Status
, ord.Amount
, TotalSum = SUM(ord.Amount)OVER(PARTITION BY ord.Order_no, ord.Status)
FROM Orders ord
This would produce the following result:
Order_no Status Amount TotalAmount
9008258656 P 50.00 50.00
9008258656 P 0.00 50.00
9008510713 P 50.00 50.00
9008510713 P 0.00 50.00
Based off the example you provided, there probably is not much value in doing the sum over in this scenario. #GMB's response should suffice. However, there are a lot of cool things you can do with the sum window function such as running totals. For example, if you had an order date column, you can include after the PARTITION BY ord.Order_no, ord.Status ORDER BY ord.Order_date and this would give you a running sum of the amount that increments by each order. I.E:
Order_no Order_date Status Amount RunningTotal
9008258656 1/2/2019 P 50.00 50.00
9008258656 1/3/2019 P 0.00 50.00
9008258656 1/4/2019 P 50.00 100.00
9008258656 1/5/2019 P 0.00 100.00

Update one table based upon SUM(values) in another table on multiple criteria

I can't seem to find out how to do this and not sure exactly how to search for it!
I have a table [MASTER]:
ID varchar(6)
CCY varchar(3)
Val1 decimal(20,5)
Val2 decimal(20,5)
FOO decimal(20,5)
and another table [FOOS]
ID varchar(6)
CCY varchar(3)
Val decimal(20,5)
MASTER contains one row per ID/CCY composite key (not sure if thats correct term) e.g.
ABCDEF GBP 200.00 100.00 null
ABCDEF EUR 400.00 150.00 null
ZYXWVU GBP 300.00 200.00 null
ZYXWVU EUR 400.00 200.00 null
FOOS contains multiple rows and DOES NOT contain a row for every MASTER e.g.
ABCDEF GBP 50.00
ABCDEF GBP 51.00
ABCDEF GBP 150.00
ZYXWVU GBP 100.00
ZYXWVU EUR 200.00
ZYXWVU EUR 400.00
I'd like to run a query to update only matching MASTER rows with SUM(FOOS.Val). e.g.
ABCDEF GBP 200.00 100.00 251.00
ABCDEF EUR 400.00 150.00 null
ZYXWVU GBP 300.00 200.00 100.00
ZYXWVU EUR 400.00 200.00 600.00
...but although I've tried a numer of options (where exists, inner join) I can't seem to be able to either link to a single MASTER or do the SUM(...)
Try this solution:
UPDATE m
SET m.Foo = f.valsum
FROM [MASTER] m
INNER JOIN
(
SELECT ID, CCY, SUM(val) valsum
FROM Foos
GROUP BY ID, CCY
) f ON m.ID = f.ID AND m.CCY = f.CCY;
With postgres, I had to adjust the solution with this to work for me:
UPDATE [MASTER] m
SET Foo = f.valsum
FROM
(
SELECT ID, CCY, SUM(val) valsum
FROM Foos
GROUP BY ID, CCY
) f
WHERE m.ID = f.ID AND m.CCY = f.CCY;
UPDATE accounts SET (contact_last_name, contact_first_name) =
(SELECT last_name, first_name FROM salesmen
WHERE salesmen.id = accounts.sales_id);
update orders
set amount =
(select sum(item_quantity) from order_items
where orders.id = order_items.order_id);

Multiple Join Multipliers

I have 3 tables: Insurance Policies, Claims and Receivables
I need a query that will return one row per policy period per policy. The query needs to include the policy start and end date, total claims for each period, total paid and O/S for each period, and total amount received for each period.
I've managed to do everything but the recievables. When this is introduced to the query, everything is multiplied by the number of rows in that table.
Here is my data
Policies
PolNo Version TransType InceptionDate RenewalDate
0021 0 New 01/01/2008 01/01/2009
0021 1 MTA 01/01/2008 01/01/2009
0021 2 MTA 01/01/2008 01/01/2009
0021 3 Renewal 01/01/2009 01/01/2010
Claims
PolNo ClaimNo ClaimDate Paid Outstanding
0021 0001 01/05/2008 300.00 -100.00
0021 0002 01/06/2008 500.00 200.00
0021 0003 01/07/2008 200.00 300.00
0021 0004 01/08/2008 800.00 0.00
0021 0005 01/02/2009 0.00 0.00
0021 0006 01/10/2009 0.00 1000.00
Receivables
PolNo Version RecvdDate Amount
0021 0 02/01/2008 150.00
0021 0 01/02/2008 150.00
0021 0 01/03/2008 150.00
0021 0 01/04/2008 150.00
0021 0 01/05/2008 150.00
0021 0 01/06/2008 150.00
0021 0 01/07/2008 150.00
0021 0 01/08/2008 150.00
0021 2 01/09/2008 150.00
0021 2 01/10/2008 150.00
0021 3 02/01/2009 500.00
0021 3 01/02/2009 500.00
Here is my working query
select distinct(a.InceptionDate) as InceptionDate, a.RenewalDate as RenewalDate,
count(b.ClaimNo) as ClaimCount, sum(b.Paid) as TotPaid, sum(b.Outstanding) as TotalO/S
from Policies a, Claims b
where a.PolNo='0021'
and a.PolNo=b.PolNo
and b.ClaimDate between a.InceptionDate and a.RenewalDate
and a.TransType in ('New','Renewal')
group by a.InceptionDate, a.RenewalDate
Result:
InceptionDate RenewalDate ClaimCount TotPaid TotalO/S
01/01/2008 01/01/2009 4 1800.00 400.00
01/01/2009 01/01/2010 2 0 1000.00
I had to add TransType to the query as I was getting a multiplier for claims data, but this query works.
Now when I add the third table, I get a multiplier. Everything from Claims is multiplied by the number or rows in Receivables for the period, and AmountRecvd is multiplied by the number of rows in Claims for the period.
Here is the query:
select distinct(a.InceptionDate) as InceptionDate, a.RenewalDate as RenewalDate,
count(b.ClaimNo) as ClaimCount, sum(b.Paid) as TotPaid, sum(b.Outstanding) asTotalO/S,
sum(c.Amount) as RecvdAmount
from Policies a, Claims b, Receivables c
where a.PolNo='0021'
and a.PolNo=b.PolNo
and a.PolNo=c.PolNo
and b.ClaimDate between a.InceptionDate and a.RenewalDate
and c.RecvdDate between a.InceptionDate and a.RenewalDate
and a.TransType in ('New','Renewal')
group by a.InceptionDate, a.RenewalDate
Result
InceptionDate RenewalDate ClaimCount TotPaid TotalO/S AmountRecvd
01/01/2008 01/01/2009 40 18000 4000 6000.00
01/01/2009 01/01/2010 20 0 10000 2000.00
I cant see that it is possible to join Receivables with Claims. I tried joining them on PolNo but had the same result.
Can anyone see a solution?
Cheers
Updated This query doesn't muliply ClaimsCount, but AmountRevd still multiplying by Claims (TotPaid removed for simplicity):
SELECT
p.InceptionDate,
p.RenewalDate,
sum(c1.ClaimCount) AS ClaimsCount,
sum(r1.TotRecvd) AS AmountRecvd
FROM Policies p
LEFT JOIN (SELECT
p1.InceptionDate,
p1.RenewalDate,
c.Polno,
c.ClaimDate,
COUNT(c.SubCount) AS ClaimCount
FROM Policies p1
INNER JOIN (SELECT
PolNo,
ClaimDate,
Count(*) as SubCount
FROM Claims
GROUP BY PolNo, ClaimDate) c
ON p1.PolNo=c.PolNo
WHERE c.ClaimDate BETWEEN p1.InceptionDate AND p1.RenewalDate
AND p1.TransType IN ('New','Renewal')
GROUP BY p1.InceptionDate, p1.RenewalDate, c.Polno, c.ClaimDate) c1
ON p.PolNo=c1.PolNo
LEFT JOIN (SELECT
p2.InceptionDate,
p2.RenewalDate,
r.Polno,
SUM(r.SubTot) AS TotalRecvd
FROM Policies p2
INNER JOIN (SELECT
PolNo,
RecvdDate,
SUM(Amount) as SubTot
FROM Receivables
GROUP BY Polno,RecvdDate) r
ON p2.PolNo=r.PolNo
WHERE r.RecvdDate BETWEEN p2.InceptionDate AND p2.RenewalDate
AND p2.TransType IN ('New','Renewal')
GROUPBY p2.InceptionDate, p2.RenewalDate, r.Polno) r1
ON p.PolNo=r1.PolNo
WHERE p.PolNo = '0021'
AND p.TransType IN ('New','Renewal')
AND p.PolNo = c1.PolNo
AND p.PolNo = r1.PolNo
AND p.InceptionDate = c1.InceptionDate
AND p.RenewalDate = c1.RenewalDate
AND p.InceptionDate = r1.InceptionDate
AND p.RenewalDate = r1.RenewalDate
GROUP BY p.InceptionDate, p.RenewalDate
-- Query for the periods:
WITH Periods AS (
SELECT DISTINCT
PolNo, InceptionDate, RenewalDate
FROM Policies
WHERE TransType in ('New','Renewal')
),
-- Query for the claims
PeriodClaims AS (
SELECT
p.PolNo, p.InceptionDate, p.RenewalDate,
COUNT(*) AS CountClaims,
SUM(c.Paid) AS PaidClaims,
SUM(c.Outstanding) AS OutstandingClaims
FROM Periods p
INNER JOIN Claims c ON c.PolNo = p.PolNo
WHERE c.ClaimDate BETWEEN p.InceptionDate AND p.RenewalDate
GROUP BY p.PolNo, p.InceptionDate, p.RenewalDate
),
-- Query for the receivables
PeriodRecieved AS (
SELECT
p.PolNo, p.InceptionDate, p.RenewalDate,
COUNT(*) AS CountReceived,
SUM(r.Amount) AS AmountReceived
FROM Periods p
INNER JOIN Receivables r ON r.PolNo = p.PolNo
WHERE r.RecvdDate BETWEEN p.InceptionDate AND p.RenewalDate
GROUP BY p.PolNo, p.InceptionDate, p.RenewalDate
)
-- All together now
SELECT
p.PolNo, p.InceptionDate, p.RenewalDate,
c.CountClaims, c.PaidClaims, c.OutstandingClaims,
r.CountReceived, r.AmountReceived
FROM Periods p
LEFT JOIN PeriodClaims c
ON c.PolNo = p.PolNo
AND c.InceptionDate = p.InceptionDate
AND c.RenewalDate = p.RenewalDate
LEFT JOIN PeriodRecieved r
ON r.PolNo = p.PolNo
AND r.InceptionDate = p.InceptionDate
AND r.RenewalDate = p.RenewalDate
Or, without CTE:
SELECT
p.PolNo, p.InceptionDate, p.RenewalDate,
c.CountClaims, c.PaidClaims, c.OutstandingClaims,
r.CountReceived, r.AmountReceived
FROM (
-- Query for the periods:
SELECT DISTINCT
PolNo, InceptionDate, RenewalDate
FROM Policies
WHERE TransType in ('New','Renewal')
) p
LEFT JOIN (
-- Query for the claims
SELECT
p.PolNo, p.InceptionDate, p.RenewalDate,
COUNT(*) AS CountClaims,
SUM(c.Paid) AS PaidClaims,
SUM(c.Outstanding) AS OutstandingClaims
FROM (
-- Query for the periods:
SELECT DISTINCT
PolNo, InceptionDate, RenewalDate
FROM Policies
WHERE TransType in ('New','Renewal')
) p
INNER JOIN Claims c ON c.PolNo = p.PolNo
WHERE c.ClaimDate BETWEEN p.InceptionDate AND p.RenewalDate
GROUP BY p.PolNo, p.InceptionDate, p.RenewalDate
) c
ON c.PolNo = p.PolNo
AND c.InceptionDate = p.InceptionDate
AND c.RenewalDate = p.RenewalDate
LEFT JOIN (
-- Query for the receivables
SELECT
p.PolNo, p.InceptionDate, p.RenewalDate,
COUNT(*) AS CountReceived,
SUM(r.Amount) AS AmountReceived
FROM (
-- Query for the periods:
SELECT DISTINCT
PolNo, InceptionDate, RenewalDate
FROM Policies
WHERE TransType in ('New','Renewal')
) p
INNER JOIN Receivables r ON r.PolNo = p.PolNo
WHERE r.RecvdDate BETWEEN p.InceptionDate AND p.RenewalDate
GROUP BY p.PolNo, p.InceptionDate, p.RenewalDate
) r
ON r.PolNo = p.PolNo
AND r.InceptionDate = p.InceptionDate
AND r.RenewalDate = p.RenewalDate
Output (transposed):
PolNo 21 21
InceptionDate 2008-01-01 2009-01-01
RenewalDate 2009-01-01 2010-01-01
CountClaims 4 2
PaidClaims 1800.00 0.00
OutstandingClaims 400.00 1000.00
CountReceived 10 2
AmountReceived 1500.00 1000.00