SQL how to join two tables without duplicate - sql

I have these two tables. I am trying to get the amount in the item_tbl, but the payment_dt is in another table (payment_tbl).
How can I join the two tables and get the amount correctly? Right now, my SQL (using PostgreSQL) generates 3 rows so the amount is multiplied by three when I get the sum.
item_tbl:
receipt_no | gross_amount | other_discount_amount | net_of_discount_amount
0000000617 | 2000.00 | 400.00 | 1600.00
payment_tbl:
receipt_no | amount(net) | payment_method | payment_dt
0000000617 | 639.49 | cash | 2016-05-31 11:48:23.5+08
0000000617 | 500.00 | check | 2016-05-31 11:48:23.5+08
0000000617 | 500.00 | debit card | 2016-05-31 11:48:23.5+08
expected result:
gross_amount | other_discount_amount | net_of_discount_amount
2000.00 | 400.00 | 1600.00
Query:
SELECT
cashier.cashier_name,
COALESCE(gross_amount, 0) AS gross_amount,
(CASE WHEN item.other_discount_type = 'OTHER' THEN COALESCE(item.other_discount_amount, 0) ELSE 0 END) AS other_discount_amount,
COALESCE(item.net_of_discount_amount, 0) AS net_of_discount_amount
FROM
item_tbl item
INNER JOIN
payment_tbl payment ON item.receipt_no = payment.receipt_no
LEFT JOIN
cashier_tbl cashier ON cashier.id = item.cashier_id
WHERE
date(payment.payment_dt) = to_date('31 May 2016', 'dd Mon YYYY')

All payment records for a particular receipt must have the same date time for this to work correctly.
SELECT
cashier.cashier_name,
COALESCE(gross_amount, 0) AS gross_amount,
(CASE WHEN item.other_discount_type = 'PATRONAGE_CASH' THEN COALESCE(item.other_discount_amount, 0) ELSE 0 END) AS other_discount_amount,
COALESCE(item.net_of_discount_amount, 0) AS net_of_discount_amount
FROM item_tbl item
INNER JOIN (Select receipt_no, Max(payment_dt) payment_dt from payment_tbl Group By receipt_no) payment ON item.receipt_no = payment.receipt_no
LEFT JOIN cashier_tbl cashier ON cashier.id = item.cashier_id
WHERE
AND date(payment.payment_dt) = to_date('31 May 2016', 'dd Mon YYYY')

Related

Sum all rows returned in query and use it in each row

I have a query (Oracle) that shows the sales of each customer by years:
SELECT cmp.company_key
, sum(CASE WHEN sd.date between TO_DATE('01-Jan-2010', 'dd-mm-yyyy') and TO_DATE('11-Jun-2010', 'dd-mm-yyyy') THEN sd.qty_ship * sd.unit_price END) AS year1sales
, sum(CASE WHEN sd.date between TO_DATE('01-Jan-2011', 'dd-mm-yyyy') and TO_DATE('11-Jun-2011', 'dd-mm-yyyy') THEN sd.qty_ship * sd.unit_price END) AS year2sales
FROM sales_detail sd
INNER JOIN sales_header sh on sd.sales_header_key = sh.sales_header_key
INNER JOIN companies cmp on sh.company_key = cmp.company_key
GROUP BY cmp.company_key
The query produces this:
company_key | year1sales | year2sales
------------|------------|------------
8687 | 21355.76 | 54326.45
25 | 9375.41 | 12401
34 | 6440.03 | 50349.27
247 | 47355.93 | 77432.67
83 | 15757.35 | 39999.12
But I also need it to return a value ("TBI") showing what percentage that company's sales are compared to the sum of all the other sales numbers.
So, for company #8687 it would be 21355.76 / sigma(year1 sales) which is 21355.76/100,284.48 = 21.3%.
So the result would be:
company_key | year1sales | year1 TBI | year2sales | year1 TBI
------------|------------|-----------|------------|----------
8687 | 21355.76 | 21.30 | 54326.45 | 23.17
25 | 9375.41 | 9.35 | 12401 | 5.29
34 | 6440.03 | 6.42 | 50349.27 | 21.47
247 | 47355.93 | 47.22 | 77432.67 | 33.02
83 | 15757.35 | 15.71 | 39999.12 | 17.06
And obviously the TBI columns would sum up to 100%.
How would you write this query? Also, what is the time complexity for a problem like this? I think it's O(n^2) best case.
You'd use SUM OVER to get the totals:
select
company_key,
year1sales,
year1sales / sum(year1sales) over() as year1tbi,
year2sales,
year2sales / sum(year2sales) over() as year2tbi
from
(
SELECT cmp.company_key
, sum(CASE WHEN sd.date between date '2010-01-01' and date '2010-06-11' THEN sd.qty_ship * sd.unit_price END) AS year1sales
, sum(CASE WHEN sd.date between date '2011-01-01' and date '2011-06-11' THEN sd.qty_ship * sd.unit_price END) AS year2sales
FROM sales_detail sd
INNER JOIN sales_header sh on sd.sales_header_key = sh.sales_header_key
INNER JOIN companies cmp on sh.company_key = cmp.company_key
GROUP BY cmp.company_key
)
order by company_key;
As to the complexity: I cannot answer this. The DBMS has to run through the result, build the totals and calculate the percentages per row then.

SQL Single Row query advice

Can i please get some help with my SQL report query, i am 90% there just need the last step (still learning SQL so be kind :) ).
We currently have 2 different databases:
- [DATABASE1] stores all our assets
- [DATABASE2] stores all assets that we are still paying off
- - This database stores every payment made against the asset to the bank, the date the payment was made, the amount etc.
- - The last row against the asset will be the last payment, and the date on this would be the expected lease end date.
I would like a report that will have a single line per asset shows all columns.
My current report shows all the required information, however it shows ALL the rows per asset instead of a single encapsulated row, e.g:
ASSET NO | FINANCIER | AGEEMENT NUMBER | PAYMENT NUMBER | LEASE COMMENCE DATE | LEASE FINAL DATE | MONTHLY PAYMENTS
asset1 | bank 1 | 1111 | 1 | 01/01/2017 | NULL | NULL
asset1 | bank 1 | 1111 | 2 | NULL | NULL | 2000
asset1 | bank 1 | 1111 | 3 | NULL | NULL | NULL
..
asset1 | bank 1 | 1111 | 20 | NULL | 01/01/2020 | NULL
asset2 | bank 5 | 1536 | 1 | 05/08/2016 | NULL | NULL
..
Instead of:
ASSET NO | FINANCIER | AGEEMENT NUMBER | PAYMENT NUMBER | LEASE COMMENCE DATE | LEASE FINAL DATE | MONTHLY PAYMENTS
asset1 | bank 1 | 1111 | 20 | 01/01/2017 | 01/01/2020 | 2000
asset2 | bank 5 | 1536 | 15 | 05/08/2016 | 12/05/2019 | 5500
..
Below is my query:
Declare #MaxPays TABLE (
ITEMNO VARCHAR(MAX),
PAYNO VARCHAR(MAX)
)
INSERT INTO #MaxPays
SELECT
a.ITEMNO,
a.PAYNO
FROM
[DATABASE1] a
INNER JOIN
(SELECT ITEMNO, MAX(PAYNO) as PAYNO FROM [DATABASE1] GROUP BY ITEMNO) AS b ON
a.ITEMNO = b.ITEMNO AND a.PAYNO = b.PAYNO
SELECT
a.ITEMNO as 'Asset #',
a.FINANCE as 'Financier',
a.AGREENO as 'Agreement number',
a.PAYNO as 'Payment Number',
CASE WHEN a.PAYNO = 1 THEN a.PAYDATE ELSE NULL END as 'Lease Commencing Date',
CASE WHEN a.PAYNO = (SELECT PAYNO FROM #MaxPays WHERE ITEMNO = a.ITEMNO) THEN a.PAYDATE ELSE NULL END as 'Lease Finalising Date',
CASE WHEN a.PAYNO = 2 THEN a.PAYAMOUNT ELSE NULL END as 'Monthly Payments'
FROM
[DATABASE1] a
INNER JOIN
(SELECT DISTINCT ITEMNO from [DATABASE2]) AS b ON
a.ITEMNO = b.ITEMNO
ORDER BY a.ITEMNO
EDIT: The monthly payment links to the 2nd instance because sometimes the 1st payment includes down payments, and isnt a clear indicator of the recurring monthly payments
Any help would be appreciated.
Thanks
You just need a minor modification in your query -
SELECT
a.ITEMNO as 'Asset #',
a.FINANCE as 'Financier',
a.AGREENO as 'Agreement number',
a.PAYNO as 'Payment Number',
MAX(CASE WHEN a.PAYNO = 1 THEN a.PAYDATE ELSE NULL END) as 'Lease Commencing Date',
MAX(CASE WHEN a.PAYNO = (SELECT PAYNO FROM #MaxPays WHERE ITEMNO = a.ITEMNO) THEN a.PAYDATE ELSE NULL END) as 'Lease Finalising Date',
MAX(CASE WHEN a.PAYNO = 2 THEN a.PAYAMOUNT ELSE NULL END) as 'Monthly Payments'
FROM
[DATABASE1] a
INNER JOIN
(SELECT DISTINCT ITEMNO from [DATABASE2]) AS b ON
a.ITEMNO = b.ITEMNO
GROUP BY a.ITEMNO,
a.FINANCE,
a.AGREENO,
a.PAYNO,
ORDER BY a.ITEMNO

How to get MAX values in JOINs in SQL Server 2014?

I have 4 tables that I need to join to pull data from:
| Account Table: A | Plate Table: P | TollTransaction : T | FinTrans Table: F |
=================================================================================
| AccountId | AccountId | AccountId | AcctId |
| AccountNumber | LicPlateNo | LicPlateNo | FinTransTypeCode |
| CurrentBalance | EndDate | EntryTransDt | BusinessDay |
| ActualTagDeposit | | | |
=================================================================================
The relationships:
A.AccountId = P.AccountId = F.AcctId != T.AccountId <--(NOT EQUAL)
P.LicPlateNo = T.LicPlateNo
I want to show:
A.AccountNumber
A.CurrentBalance
Max(BusinessDay) -->(Last Payment Date)
Max(EntryTransDt) -->(Last Transaction Date)
ActualTagDeposit
WHERE P.EndDate IS NULL
AND A.CurrentBalance > 0
AND F.FinTransTypeCode = 'pymt'
AND Max(EntryTransDt) <= '2017-07-28'
AND A.ActualTagDeposit >= 10
My attempt at the code so far:
SELECT A.AccountNumber
,A.CurrentBalance
,MAX(F.Last_Pymt_date) AS Last_Pymnt_Date
,MAX(T.Last_Transaction) AS LastTransaction
,A.ActualTagDeposit
FROM
( SELECT AccountId
,LicPlateNo
,MAX(EntryTransDt) AS Last_Transaction
FROM TollTransaction
GROUP BY AccountId, LicPlateNo
) T
INNER JOIN Plate P ON T.LicPlateNo = P.LicPlateNo
INNER JOIN Account A ON P.AccountId = A.AccountId
LEFT JOIN
( SELECT AcctId
,FinTransTypeCode
,MAX(BusinessDay) AS Last_Pymt_Date
FROM FinTransMaster
GROUP BY AcctID, FinTransTypeCode
) F ON A.AccountId = F.AcctId
WHERE P.EndDate is null
AND A.CurrentBalance > 0
AND F.FinTransTypeCode = 'pymt'
AND Last_Transaction <= '2017-07-28'
AND A.ActualTagDeposit >= 10
GROUP BY AccountNumber, CurrentBalance, Last_Pymt_date, Last_Transaction, A.ActualTagDeposit
ORDER BY AccountNumber
But I get duplicates with this code. Obviously, the MAX part in my code is not working somehow?
A sample of the results I get:
AccountNumber CurrentBalance Last_Pymnt_Date LastTransaction ActualTagDeposit
21233815 12.34 2016-12-12 2016-08-15 10.00
21233815 12.34 2016-12-12 2017-03-11 10.00
21234567 123.12 2017-06-20 2016-12-25 10.00
21234568 1.23 2017-06-05 2012-07-12 10.00
21234568 1.23 2017-06-05 2012-07-21 10.00
This is happening because one account can have multiple license plates. If you added P.LicPlateNo to your current select, you would see something like this:
AccountNumber CurrentBalance Last_Pymnt_Date LastTransaction ActualTagDeposit LicPlateNo
21233815 12.34 2016-12-12 2016-08-15 10.00 A123
21233815 12.34 2016-12-12 2017-03-11 10.00 B456
I would like to say just remove Plates all together, but it seems like you have to use it as the link to table T, so the next best move is to do a MAX() in your outer query (as you are doing). The problem is that you also have the maxed columns in your outer GROUP BY, which prevents them from being aggregated in your MAX()
Changing your outer group by will solve the problem:
GROUP BY AccountNumber, CurrentBalance, A.ActualTagDeposit

Joining 3 tables pgsql

I have 3 tables as shown below
table 1
________________________________________________
id | effective_date | table_3_id | acc_name |
___|____________________|____________|__________|
112|2012-02-01 12:00:00 | 23 | Over Pay |
___|____________________|____________|__________|
table 2
__________________________________
id | table_1_id | amount |
______|________________|_________|
1 | 112 | 400.00 |
______|________________|_________|
table 3
________________________________________
id | emp_num | first_name | last_name|
____|__________|_____________|__________|
23 | 100004 | John | Doe |
____|__________|_____________|__________|
I have a start date and an end date and also a predefined value for acc_name. What I want to do is, to retrieve the emp_num, first_name, last_name and amount from relevant tables that have a effective_date that falls between start date and end date and also the acc_name should be the predefined value.
For above tables if my start date = 2012-01-30 12:00:00 , end date = 2012-03-01 12:00:00 and acc_name = Over Pay; then below values should be returned.
emp_num = 100004
first_name = John
last_name = Doe
amount = 400.00
How can I do this? I am not sure whether joining all 3 the tables is the best approach here. Can anyone help?
Yes. Use a join.
select emp_num, first_name, last_name, amount
from
table1
inner join table2 on table1.id = table2.table_1_id
inner join table3 on table1.table_3_id = table3.id
where
effective_date between '2012-01-30 12:00:00' and '2012-03-01 12:00'
and
acc_name = 'Over Pay'
There is no 30th of February.
Here, try this one:
SELECT a.emp_num,
a.first_name,
a.last_name,
c.amount
FROM table3 a
INNER JOIN table1 b
on a.id = b.table_3_id
INNER JOIN table2 c
on b.id = c.id
WHERE b.effective_date BETWEEN '2012-01-30 12:00:00' AND '2012-02-29 12:00:00'
AND
b.acc_name = 'Over pay'

Multiple join statements not returning the expected result

I have to write a query with the following requirements:
The query should return a list of all
entry values for a customer named
“Steve” and for each date shown (if
available) the most recent status detail for
that date.
Customer Table
CustomerID | CustomerName
1 | Steve
2 | John
Entry Table
CustomerID | EntryDate | EntryValue
1 | 5/4/2010 | 200.0
1 | 4/4/2010 | 100.0
1 | 3/4/2010 | 150.0
1 | 2/4/2010 | 170.0
2 | 5/4/2010 | 220.0
Status Table
CustomerID | StatusDate | Detail
1 | 5/28/2010 | D
1 | 4/24/2010 | S
1 | 4/5/2010 | P
1 | 2/28/2010 | A
The expected output is:
CustomerName | Date | OrderCost | Detail
Steve | 5/4/2010 | 200.0 | S
Steve | 4/4/2010 | 100.0 | A
Steve | 3/4/2010 | 75.0 | A
Steve | 3/4/2010 | 75.0 | <null>
I think that the expected output may be wrong and it should actually be:
CustomerName | Date | OrderCost | Detail
Steve | 5/4/2010 | 200.0 | S
Steve | 4/4/2010 | 100.0 | A
Steve | 3/4/2010 | 150.0 | A
Steve | 2/4/2010 | 170.0 | <null>
Given the requirement, I don't understand why the 3/4/2010 date would occur twice and the second time it would have a Detail. I wrote the following query:
I wrote the following query:
SELECT Customers.CustomerName, Entries.EntryDate, Entries.EntryValue, Status.Detail
FROM Customers
INNER JOIN Entries ON Customers.CustomerID = Entries.CustomerID
LEFT OUTER JOIN Status ON Status.CustomerID = Customers.CustomersID AND Status.StatusDate <= Entries.EntryDate
WHERE (Customers.CustomerName = 'Steve')
The result of my query is this:
CustomerName| EntryDate | EntryValue | Detail
Steve | 5/4/2010 | 200.00 | S
Steve | 5/4/2010 | 200.00 | P
Steve | 5/4/2010 | 200.00 | A
Steve | 4/4/2010 | 100.00 | A
Steve | 3/4/2010 | 150.00 | A
Steve | 2/4/2010 | 170.00 | NULL
Any hints on what I'm doing wrong here? I can't figure it out...
Update
I've changed the order to an entry so it doesn't confuse us that much.
You are getting more results than you expect because the second JOIN condition is satisfied by many rows in the statuses table (e.g. There are 3 statusDates earlier than 5/4 so this date appears 3 times in the result set).
You need to JOIN statuses table, but get only one match (the latest). This can be done in several ways, AFAIK usually with a sub query. I think your case is rather complicated - I used a temp table. Hope it helps... (I currently don't have a DB to test this on, hopefully there are no silly syntax errors).
DROP TABLE IF EXISTS temp;
CREATE TABLE temp AS -- This temp table is basically the result set you got
(SELECT c.CustomerName, e.EntryDate, e.EntryValue, s.Detail, s.StatusDate
FROM Customers c
INNER JOIN Entires e ON c.CustomerID = e.CustomerID
LEFT OUTER JOIN Status s ON s.CustomerID = c.CustomersID
AND s.StatusDate <= e.EntryDate
WHERE (c.CustomerName = 'Steve')
);
SELECT t.CustomerName, t.EntryDate, t.EntryValue, t.Detail
FROM temp t
WHERE t.StatusDate = (SELECT MAX(t2.StatusDate)
FROM temp t2
WHERE t2.EntryDate = t.EntryDate);
To refrain from creating a temp table I believe this will work (please try and do let me know!)
SELECT t.CustomerName, t.EntryDate, t.EntryValue, t.Detail
FROM (SELECT c.CustomerName, e.EntryDate, e.EntryValue, s.Detail, s.StatusDate
FROM Customers c
INNER JOIN Entries e ON c.CustomerID = e.CustomerID
LEFT OUTER JOIN Status s ON s.CustomerID = c.CustomersID
AND s.StatusDate <= e.EntryDate
WHERE c.CustomerName = 'Steve') AS t
WHERE t.StatusDate = (SELECT MAX(t2.StatusDate)
FROM temp t2
WHERE t2.EntryDate = t.EntryDate);
You can use a subquery to get the status.
Use TOP 1 for SQL Server or LIMIT 1 for SQLite/MySQL
SQL Server / SyBase
SELECT Customers.CustomerName, Entries.EntryDate, Entries.EntryValue,
(SELECT top 1 Status.Detail From Status
where Status.CustomerID = Customers.CustomersID AND Status.StatusDate <= Entries.EntryDate
order by Status.StatusDate desc)
FROM Customers
INNER JOIN Entries ON Customers.CustomerID = Entries.CustomerID
WHERE (Customers.CustomerName = 'Steve')
MySQL / SQLite
SELECT Customers.CustomerName, Entries.EntryDate, Entries.EntryValue,
(SELECT Status.Detail From Status
where Status.CustomerID = Customers.CustomersID AND Status.StatusDate <= Entries.EntryDate
order by Status.StatusDate desc
limit 1)
FROM Customers
INNER JOIN Entries ON Customers.CustomerID = Entries.CustomerID
WHERE (Customers.CustomerName = 'Steve')
Shouldn't the status date come after the order date? Something like:
SELECT Customers.CustomerName, Orders.OrderDate, Orders.OrderCost, Status.Detail
FROM Customers
INNER JOIN Orders ON Customers.CustomerID = Orders.CustomerID
LEFT OUTER JOIN Status ON Status.CustomerID = Customers.CustomersID
WHERE Customers.CustomerName = 'Steve' AND Status.StatusDate >= Orders.OrderDate
Also, the CustomerID in the Status table seems a bit strange as it's usually orders that have a status, not the customer. Shouldn't the status table have an OrderID field?
The expected output is wrong. The last row should be for the date 2/4/2010. Also, their order costs are not right either. 2/4/2010 should be returning null, because there is no matching status.