Sum with 3 tables to join - sql

I have 3 tables. The link between the first and the second table is REQ_ID and the link between the second and the third table is ENC_ID. There is no direct link between the first and the third table.
INS_RCPT
+----+--------+------+----------+
| ID | REQ_ID | CURR | RCPT_AMT |
+----+--------+------+----------+
| 1 | 1 | USD | 100 |
| 2 | 2 | USD | 200 |
| 3 | 3 | USD | 300 |
+----+--------+------+----------+
ENC_LOG
+----+--------+--------+-------------+
| ID | REQ_ID | ENC_ID | ENC_LOG_AMT |
+----+--------+--------+-------------+
| 1 | 1 | 1 | 20 |
| 2 | 1 | 2 | 50 |
| 3 | 1 | 3 | 30 |
| 4 | 2 | 4 | 20 |
+----+--------+--------+-------------+
ENC_RCPT
+----+--------+--------------+
| ID | ENC_ID | ENC_RCPT_AMT |
+----+--------+--------------+
| 1 | 1 | 10 |
| 2 | 1 | 10 |
| 3 | 2 | 15 |
| 4 | 2 | 25 |
| 5 | 2 | 10 |
| 6 | 3 | 12 |
| 7 | 3 | 18 |
| 8 | 4 | 10 |
+----+--------+--------------+
I would like to have output as follows:
+----+--------+------+----------+-------------+--------------+
| ID | REQ_ID | CURR | RCPT_AMT | ENC_LOG_AMT | ENC_RCPT_AMT |
+----+--------+------+----------+-------------+--------------+
| 1 | 1 | USD | 100 | 100 | 100 |
| 2 | 2 | USD | 200 | 20 | 10 |
| 3 | 3 | USD | 300 | 0 | 0 |
+----+--------+------+----------+-------------+--------------+
I am using SQL Server to write this query. Any help is appreciated.

One approach would be to join the first table to two subqueries which compute the sums separately:
SELECT
ir.ID,
ir.REQ_ID,
ir.CURR,
ir.RCPT_AMT,
el.ENC_LOG_AMT,
er.ENC_RCPT_AMT
FROM INS_RCPT ir
LEFT JOIN
(
SELECT REQ_ID, SUM(ENC_LOG_AMT) AS ENC_LOG_AMT
FROM ENC_LOG
GROUP BY REQ_ID
) el
ON ir.REQ_ID = el.REQ_ID
LEFT JOIN
(
SELECT t1.REQ_ID, SUM(t2.ENC_RCPT_AMT) AS ENC_RCPT_AMT
FROM ENC_LOG t1
INNER JOIN ENC_RCPT t2 ON t1.ENC_ID = t2.ENC_ID
GROUP BY t1.REQ_ID
) er
ON ir.REQ_ID = er.REQ_ID
Demo
Note that your question includes a curve ball. The second subquery needs to return aggregates of the receipt table by REQ_ID, even though this field does not appear in that table. As a result, we actually need to join ENC_LOG to ENC_RCPT in that subquery, and then aggregate by REQ_ID.

You can try the below query. Also change the join from left to inner as per your requirement.
select a.id,a.req_id,a.curr,sum(a.rcpt_amt) rcpt_amt,sum(a.enc_log_amt) enc_log_amt,sum(c.enc_rcpt_amt) enc_rcpt_amt
from
(
select a.id id ,a.req_id req_id ,a.curr curr,sum(rcpt_amt) as rcpt_amt,sum(enc_log_amt) as enc_log_amt
from ins_rcpt a
left join enc_log b
on a.req_id=b.req_id
group by id,req_id,curr
) a
left join enc_rcpt c
on a.enc_id = c.enc_id
group by id,req_id,curr;

Related

Join and Group Three Tables On Multiple Criteria - SQL

I am trying to join three separate tables based on certain criteria. Here are table examples:
TABLE A
+----+------------+----------+---------+
| id | entry num | line num | inv line|
+----+------------+----------+---------+
| 1 | 1 | 1 | 1 |
| 2 | 1 | 1 | 2 |
| 3 | 2 | 1 | 1 |
| 4 | 2 | 2 | 1 |
| 5 | 3 | 1 | 1 |
| 6 | 3 | 1 | 2 |
| 7 | 3 | 1 | 3 |
+----+------------+--------+-----------+
TABLE B
+----+------------+----------+---------+
| id | entry num | line num | code |
+----+------------+----------+---------+
| 1 | 1 | 1 | 100 |
| 2 | 2 | 1 | 370 |
| 3 | 2 | 2 | 120 |
| 4 | 3 | 1 | 300 |
+----+------------+--------+-----------+
TABLE C
+----+------------+--------+-----------+
| id | rate | amt | code |
+----+------------+--------+-----------+
| 1 | 25% | $50 | 100 |
| 2 | 50% | $20 | 370 |
| 3 | 50% | $25 | 120 |
| 4 | 30% | $150 | 300 |
+----+------------+----------+---------+
I need the final table to look like this, but I am at a loss on how to write the syntax:
FINAL TABLE
+----+------------+----------+---------+---------+---------+---------+
| id | entry num | line num | inv line| code | rate | amt |
+----+------------+----------+---------+---------+---------+---------+
| 1 | 1 | 1 | 1 | 100 | 25% | $50 |
| 2 | 1 | 1 | 2 | 100 | 25% | $50 |
| 3 | 2 | 1 | 1 | 370 | 50% | $20 |
| 4 | 2 | 2 | 1 | 120 | 50% | $25 |
| 5 | 3 | 1 | 1 | 300 | 30% | $150 |
| 6 | 3 | 1 | 2 | 300 | 30% | $150 |
| 7 | 3 | 1 | 3 | 300 | 30% | $150 |
+----+------------+----------+---------+---------+---------+---------+
Ultimately, I need table A and B joined where both entry num and line num match, but then I need to show each individual row for the inv line number.
For example, entry num 3 / line num 1 will has 3 invoice numbers. All entry num 3/ line num 1 will have the code 300, 30% rate, and $150 amount, but I need to visibly see that there are 3 invoice lines.
I've tried to join tables, group them, and get total counts, but to no avail. Thanks for your help!
I think that you need to create joins between TableA and Table B on EntryNum and LineNum, and then between TableB and TableC on Code. Your SQL should look like:
SELECT A.ID, A.EntryNum, A.LineNum, A.InvLine, B.Code, C.Rate, C.Amt
FROM TableC AS C INNER JOIN (TableB AS B INNER JOIN TableA AS A ON (B.LineNum = A.LineNum) AND (B.EntryNum = A.EntryNum))
ON C.Code = B.Code;
Which produces the result that you want:
Regards,

Aggregation in Join and where

I have this Query for Invertory Balance and work well:
Select A.BATCH_ID ,
A.QTY_MOV - IsNull(B.QTY_USED,0) As BALANCE
From P_BATCH_PRODUC A
Left OUTER Join (Select MATERIAL_ID,
BATCH_MATERIAL_ID),
SUM(QTY_INS) QTY_USED
From CONSUMPTION
Group By MATERIAL_ID, BATCH_MATERIAL_ID) As B
On B.MATERIAL_ID= A.PRODUCT_ID
And A.BATCH_ID = B.BATCH_MATERIAL_ID"
Where A.QTY_MOV - IsNull(B.QTY_USED,0) > 0
AND A.PRODUCT_ID= 1
and A.BATCH_ID = 1
But now, it's possible to have more than one A.QTY_MOV for each A.BATCH_ID , so i need to Change A.QTY_MOV to Sum(A.QTY_MOV ). What do I need to change for that?
Sample:
Table A
+------------+------------+---------+
| Product_ID | Batch_ID | Qty_Mov |
+------------+------------+---------+
| 1 | 1 | 100 |
| 1 | 1 | 150 |
| 2 | 1 | 80 |
| 1 | 3 | 100 |
| 1 | 4 | 100 |
+------------+------------+---------+
Table B
+------------------+------------+------------+----------+--+
| BATCH_MATERIAL_ID| Product_ID | Batch_ID | Qty_USED | |
+------------------+------------+------------+----------+--+
| 1 | 1 | 1 | 80 | |
| 2 | 1 | 1 | 10 | |
| 3 | 1 | 2 | 150 | |
| 4 | 1 | 3 | 80 | |
+------------------+------------+------------+----------+--+
This is what I want
Batch_ID BALANCE
---------- ---------------
1 160
Based strictly on the question, it sounds like you want a window function:
Select A.BATCH_ID ,
SUM(A.QTY_MOV) OVER (PARTITION BY A.BATCH_ID) - IsNull(B.QTY_USED,0) As BALANCE
I don't know if this does anything useful. If it does not, you should ask a new question with sample data and an explanation of logic.

SQL aggregation over one column giving a result from another

I am trying (and failing) to join some tables in a SQLite database. The data itself is complicated but I think I have boiled it down to an illustrative example.
Here are the three tables I want to join.
Table: Events
+----+---------+-------+-----------+
| id | user_id | class | timestamp |
+----+---------+-------+-----------+
| 1 | 'user1' | 6 | 100 |
| 2 | 'user1' | 12 | 400 |
| 3 | 'user1' | 4 | 900 |
| 4 | 'user2' | 6 | 400 |
| 5 | 'user2' | 3 | 800 |
| 6 | 'user2' | 8 | 900 |
+----+---------+-------+-----------+
Table: Games
+---------+---------+------------+-----------+
| user_id | game_id | game_class | timestamp |
+---------+---------+------------+-----------+
| 'user1' | 1 | 'A' | 200 |
| 'user2' | 2 | 'A' | 300 |
| 'user1' | 3 | 'B' | 500 |
| 'user1' | 4 | 'A' | 600 |
| 'user1' | 5 | 'A' | 700 |
+---------+---------+------------+-----------+
Table: AScores
+---------+-------+
| game_id | score |
+---------+-------+
| 1 | 8 |
| 2 | 2 |
| 4 | 9 |
| 5 | 6 |
+---------+-------+
I would like to join these to provide an additional column on the first table containing the users current score in game class A at the time of the event. I.e. I would like theresult of the join to look like this:
Desired Result
+----+----------+-------+-----------+-----------------+
| id | user_id | class | timestamp | current_a_score |
+----+----------+-------+-----------+-----------------+
| 1 | 'user1' | 6 | 100 | (null) |
| 2 | 'user1' | 12 | 400 | 8 |
| 3 | 'user1' | 4 | 900 | 6 |
| 4 | 'user2' | 6 | 400 | 2 |
| 5 | 'user2' | 3 | 800 | 2 |
| 6 | 'user2' | 8 | 900 | 2 |
+----+----------+-------+-----------+-----------------+
The following simple join pulls together the two tables AScores and Games.
SELECT * FROM AScores
INNER JOIN Games
ON AScores.game_id = Games.game_id
And so I was hoping to join this to the Events table as a sub-query. Something like this:
SELECT Events.*, AScoredGames.time_stamp AS game_time_stamp, AScoredGames.score
FROM Events
LEFT OUTER JOIN (
SELECT AScores.score, Games.* FROM AScores
INNER JOIN Games
ON AScores.game_id = Games.game_id
) AS AScoredGames
ON Events.user_id = AScoredGames.user_id
AND Events.time_stamp >= AScoredGames.time_stamp
ORDER BY Events.time_stamp ASC
That results in the following:
+----+---------+-------+------------+-----------------+-------+
| id | user_id | class | time_stamp | game_time_stamp | score |
+----+---------+-------+------------+-----------------+-------+
| 1 | user1 | 6 | 100 | NULL | NULL |
| 2 | user1 | 12 | 400 | 200 | 8 |
| 4 | user2 | 6 | 400 | 300 | 2 |
| 5 | user2 | 3 | 800 | 300 | 2 |
| 6 | user2 | 8 | 900 | 300 | 2 |
| 3 | user1 | 4 | 900 | 200 | 8 |
| 3 | user1 | 4 | 900 | 600 | 9 |
| 3 | user1 | 4 | 900 | 700 | 6 |
+----+---------+-------+------------+-----------------+-------+
So I need to group by Events.id to get rid of the triplicated row with Events.id 3. But what I want to do is to choose the row with the maximum game_time_stamp but then use the row's score. If I do MAX(game_time_stamp) as my aggregation I still have to independently aggregate the score. Is there a way to tie the row choice in the score column's aggregation function to the result of the game_time_stamp column's aggregation function?
(N.B. Existing answers to questions like Select first record in a One-to-Many relation using left join and SQL Server: How to Join to first row seem to suggest I cannot and say one must use a WHERE clause over a sub-query. But I am struggling with that (I'll post another question about that) and I can think of at least one solution and I am hoping there are better ones.)
The following query should do it. It uses a NOT EXISTS condition with a correlated subquery to locate the relevant game record for each event.
SELECT e.*, s.score current_a_score
FROM
events e
LEFT JOIN games g
ON g.user_id = e .user_id
AND g.timestamp < e.timestamp
AND NOT EXISTS (
SELECT 1
FROM games g1
WHERE
g1.user_id = e .user_id
AND g1.timestamp < e.timestamp
AND g1.timestamp > g.timestamp
)
LEFT JOIN ascores s
ON s.game_id = g.game_id
ORDER BY e.id
This DB Fiddle demo with your test data returns :
| id | user_id | class | timestamp | current_a_score |
| --- | ------- | ----- | --------- | --------------- |
| 1 | user1 | 6 | 100 | |
| 2 | user1 | 12 | 400 | 8 |
| 3 | user1 | 4 | 900 | 6 |
| 4 | user2 | 6 | 400 | 2 |
| 5 | user2 | 3 | 800 | 2 |
| 6 | user2 | 8 | 900 | 2 |
I have one work-around, but it feels hacky and relies on the specifics of my data. First note that the time_stamps are all multiples of 100 while the scores are all below 10. I can acombine these in a way that will not interfere with my comparison but will mean they are both encoded in one numeric column. This query gives the desired result:
SELECT Events.id, MIN(Events.user_id) AS user_id, MIN(Events.class) AS class, MIN(Events.time_stamp) AS time_stamp, MAX(AScoredGames.combination) % 10 AS current_a_score
FROM Events
LEFT OUTER JOIN (
SELECT AScores.score, AScores.score + (Games.time_stamp - 10) AS combination, Games.* FROM AScores
INNER JOIN Games
ON AScores.game_id = Games.game_id) AS AScoredGames
ON Events.user_id = AScoredGames.user_id AND Events.time_stamp >= AScoredGames.time_stamp
GROUP BY Events.id
ORDER BY id ASC
(The combining is done in AScores.score + (Games.time_stamp - 10) and so the aggregate function becomes MAX(AScoredGames.combination) % 10.)
Actual Result
+----+---------+-------+------------+-----------------+
| id | user_id | class | time_stamp | current_a_score |
+----+---------+-------+------------+-----------------+
| 1 | user1 | 6 | 100 | NULL |
| 2 | user1 | 12 | 400 | 8 |
| 3 | user1 | 4 | 900 | 6 |
| 4 | user2 | 6 | 400 | 2 |
| 5 | user2 | 3 | 800 | 2 |
| 6 | user2 | 8 | 900 | 2 |
+----+---------+-------+------------+-----------------+

Need query for JOIN four tables with some conditions?

I have the following four tables:
1) mls_user
2) mls_category
3) bonus_point
4) mls_entry
In mls_user table values are like below:
*-------------------------*
| id | store_id | name |
*-------------------------*
| 1 | 101 | sandeep |
| 2 | 101 | gagan |
| 3 | 102 | santosh |
| 4 | 103 | manu |
| 5 | 101 | jagveer |
*-------------------------*
In mls_category table values are like below:
*---------------------------------*
| cat_no | store_id | cat_value |
*---------------------------------*
| 20 | 101 | 1 |
| 21 | 101 | 4 |
| 30 | 102 | 1 |
| 31 | 102 | 2 |
| 40 | 103 | 1 |
| 41 | 103 | 1 |
*---------------------------------*
In bonus_point table values are like below:
*-----------------------------------*
| user_id | store_id | bonus_point |
| 1 | 101 | 10 |
| 4 | 101 | 5 |
*-----------------------------------*
In mls_entry table values are like below:
*-------------------------------------------------------*
| user_id | store_id | category | distance | status |
*-------------------------------------------------------*
| 1 | 101 | 20 | 10 | Approved |
| 1 | 101 | 21 | 40 | Approved |
| 1 | 101 | 20 | 10 | Approved |
| 2 | 101 | 20 | 5 | Approved |
| 3 | 102 | 30 | 10 | Approved |
| 3 | 102 | 31 | 80 | Approved |
| 4 | 101 | 20 | 15 | Approved |
*-------------------------------------------------------*
And I want below output:
*--------------------------------------------------*
| user name | Points | bonus Point | Total Point |
*--------------------------------------------------*
| Sandeep | 30 | 10 | 40 |
| Santosh | 30 | 0 | 30 |
| Manu | 15 | 5 | 20 |
| Gagan | 5 | 0 | 5 |
| Jagveer | 0 | 0 | 0 |
*--------------------------------------------------*
I tell the calculation of how the points will come for user Sandeep.
Points = ((10+10)/1 + 40/4)=30
Here 1 and 4 is cat value which comes from mls_category.
I am using below code for a particular user but when i
SELECT sum(t1.totald/c.cat_value) as total_distance
FROM mls_category c
join (
select sum(distance) totald, user_id, category
FROM mls_entry
WHERE user_id=1 AND store_id='101' AND status='approved'
group by user_id, category) t1 on c.cat_no = t1.category
I have created tables in online for checking
DEMO
Computing the points (other than the bonus points) requires a separate join between the mls_entry and mls_category tables. I would do this in a separate subquery, and then join this to the larger query.
Here is one approach:
SELECT
u.name,
COALESCE(t1.points, 0) AS points,
COALESCE(b.bonus_point, 0) AS bonus_points,
COALESCE(t1.points, 0) + COALESCE(b.bonus_point, 0) AS total_points
FROM mls_user u
LEFT JOIN
(
SELECT e.user_id, SUM(e.distance / c.cat_value) AS points
FROM mls_entry e
INNER JOIN mls_category c
ON e.store_id = c.store_id AND e.category = c.cat_no
GROUP BY e.user_id
) t1
ON u.id = t1.user_id
LEFT JOIN bonus_point b
ON u.id = b.user_id
ORDER BY
total_points DESC;
This is the output I am getting from the above query in the demo you setup:
The output does not match exactly, because you have (perhaps) a typo in Santosh's data in your question, or otherwise the expected output in your question has a typo.

MS Access SQL getting results from different tables and sorting by date

i hope my description will be enough. i tried to remove all non-significant fields.
i have 5 tables (Customer, Invoice, Items, Invoice_Item, Payment):
Customer fields and sample date are:
+----+------+
| ID | Name |
+----+------+
| 1 | John |
| 2 | Mary |
+----+------+
Invoice fields and sample date are:
+----+-----------+----------+------+
| ID | Date | Customer | Tax |
+----+-----------+----------+------+
| 1 | 1.1.2017 | 1 | 0.10 |
| 2 | 2.1.2017 | 2 | 0.10 |
| 3 | 3.1.2017 | 1 | 0.10 |
| 4 | 3.1.2017 | 2 | 0.10 |
| 5 | 8.1.2017 | 1 | 0.10 |
| 6 | 11.1.2017 | 1 | 0.10 |
| 7 | 12.1.2017 | 2 | 0.10 |
| 8 | 13.1.2017 | 1 | 0.10 |
+----+-----------+----------+------+
Item fields and sample data are:
+----+--------+
| ID | Name |
+----+--------+
| 1 | Door |
| 2 | Window |
| 3 | Table |
| 4 | Chair |
+----+--------+
Invoice_Item fields and sample data are:
+------------+---------+--------+------------+
| Invoice_ID | Item_ID | Amount | Unit_Price |
+------------+---------+--------+------------+
| 1 | 1 | 4 | 10 |
| 1 | 2 | 2 | 20 |
| 1 | 3 | 1 | 30 |
| 1 | 4 | 2 | 40 |
| 2 | 1 | 1 | 10 |
| 2 | 3 | 1 | 15 |
| 2 | 4 | 2 | 12 |
| 3 | 3 | 4 | 15 |
| 4 | 1 | 1 | 10 |
| 4 | 2 | 20 | 30 |
| 4 | 3 | 15 | 30 |
| 5 | 1 | 4 | 10 |
| 5 | 2 | 2 | 20 |
| 5 | 3 | 1 | 30 |
| 5 | 4 | 2 | 40 |
| 6 | 1 | 1 | 10 |
| 6 | 3 | 1 | 15 |
| 6 | 4 | 2 | 12 |
| 7 | 3 | 4 | 15 |
| 8 | 1 | 1 | 10 |
| 8 | 2 | 20 | 30 |
| 8 | 3 | 15 | 30 |
+------------+---------+--------+------------+
The reason the price is in this table not in the item table is because it is customer specific price.
Payment fields are:
+----------+--------+-----------+
| Customer | Amount | Date |
+----------+--------+-----------+
| 1 | 40 | 3.1.2017 |
| 2 | 10 | 7.1.2017 |
| 1 | 60 | 10.1.2017 |
+----------+--------+-----------+
so my report should be combine all tables and sort by DATE (either from Invoice or Payment) for a certain customer.
so for e.g. for customer John (1) it should be like:
+------------+----------------+---------+-----------+
| Invoice_ID | Invoice_Amount | Payment | Date |
+------------+----------------+---------+-----------+
| 1 | 171 | - | 1.1.2017 |
| 3 | 54 | - | 3.1.2017 |
| - | - | 40 | 3.1.2017 |
| 5 | 171 | - | 8.1.2017 |
| - | 10 | 60 | 10.1.2017 |
| 6 | 44.1 | - | 11.1.2017 |
| 8 | 954 | - | 13.1.2017 |
+------------+----------------+---------+-----------+
it is sorted by date, Invoice amount is (sum of (Amount* unit price)) * (1-tax)
i started with union but then got lost.
here is my try:
SELECT Inv_ID as Num, SUM(Invoice_Items.II_Price*Invoice_Items.II_Amount) AS Amount, Inv_Date as Created
FROM Invoice INNER JOIN Invoice_Items ON Invoice.Inv_ID = Invoice_Items.II_Inv_ID
UNION ALL
SELECT Null as Num, P_Value as Amount, P_Date as Created
FROM Payments
ORDER BY created ASC
Your help is appreciated!
Thanks
You can generate the report you requested using the following SQL script:
SELECT CustomerID,Invoice_ID,Invoice_Amount,Payment,Date
FROM (
SELECT c.ID AS CustomerID, i.ID AS Invoice_ID, SUM((t.Amount * t.UnitPrice)*(1-i.tax)) AS Invoice_Amount, NULL AS Payment,i.Date
FROM (Customer c
LEFT JOIN Invoice i
ON c.ID = i.Customer)
LEFT JOIN Invoice_Item t
ON i.ID = t.Invoice_ID
GROUP BY c.ID, i.ID,i.Date
UNION
SELECT c.ID AS CustomerID,NULL AS Invoice_ID, NULL AS Invoice_Amount, p.Amount AS Payment, p.Date
FROM Customer c
INNER JOIN Payment p
ON c.ID = p.Customer ) a
ORDER BY CustomerID, Date, Payment ASC
Note: I've added CustomerID to the output so you know what customer the data corresponds to.
here is the Answer which worked for me, a bit corrected from #Catzeye Answer , which didnt show the second part of the Union.
SELECT c.ID AS CustomerID,NULL AS Invoice_ID, NULL AS Invoice_Amount, p.Amount AS Payment, p.Date
FROM Customer c
INNER JOIN Payment p
ON c.ID = p.Customer
UNION ALL
SELECT c.ID AS CustomerID, i.ID AS Invoice_ID, SUM((t.Amount * t.Unit_Price)*(1-i.tax)) AS Invoice_Amount, NULL AS Payment,i.Date
FROM (Customer c
INNER JOIN Invoice i
ON c.ID = i.Customer)
INNER JOIN Invoice_Item t
ON i.ID = t.Invoice_ID
GROUP BY c.ID, i.ID,i.Date
ORDER BY CustomerID, Date, Payment;