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

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

Related

SQL how to join two tables without duplicate

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')

PostgreSQL Referencing Outer Query in Subquery

I have two Postgres tables (really, more than that, but simplified for the purpose of the question) - one a record of products that have been ordered by customers, and another a historical record of prices per customer and a date they went into effect. Something like this:
'orders' table
customer_id | timestamp | quantity
------------+---------------------+---------
1 | 2015-09-29 16:01:01 | 5
1 | 2015-10-23 14:33:36 | 3
2 | 2015-10-19 09:43:02 | 7
1 | 2015-11-16 15:08:32 | 2
'prices' table
customer_id | effective_time | price
------------+---------------------+-------
1 | 2015-01-01 00:00:00 | 15.00
1 | 2015-10-01 00:00:00 | 12.00
2 | 2015-01-01 00:00:00 | 14.00
I'm trying to create a query that will return every order and its unit price for that customer at the time of the order, like this:
desired result
customer_id | quantity | price
------------+----------+------
1 | 5 | 15.00
1 | 3 | 12.00
2 | 7 | 14.00
1 | 2 | 12.00
This is essentially what I want, but I know that you can't reference an outer query inside an inner query, and I'm having trouble figuring out how to re-factor:
SELECT
o.customer_id,
o.quantity,
p.price
FROM orders o
INNER JOIN (
SELECT price
FROM prices x
WHERE x.customer_id = o.customer_id
AND x.effective_time <= o.timestamp
ORDER BY x.effective_time DESC
LIMIT 1
) p
;
Can anyone suggest the best way to make this work?
Instead of joining an inline view based on the prices table, you can perform a subquery in the SELECT list:
SELECT customer_id, quantity, (
SELECT price
FROM prices p
WHERE
p.customer_id = o.customer_id
AND p.effective_time <= o.timestamp
ORDER BY p.effective_time DESC
LIMIT 1
) AS price
FROM orders o
That does rely on a correlated subquery, which could be bad for performance, but with the way your data are structured I doubt there's a substantially better alternative.
You dont need the subquery, just a plain inner join will do (this assumes there are no duplicate effective_times per customer):
SELECT o.customer_id, o.quantity
,p.price
FROM orders o
JOIN prices p ON p.customer_id = o.customer_id
AND p.effective_time <= o.timestamp
AND NOT EXISTS ( SELECT * FROM prices nx
WHERE nx.customer_id = o.customer_id
AND nx.effective_time <= o.timestamp
AND nx.effective_time > p.effective_time
)
;

SQL Group By Issue with same item ID

I am trying to track the total number of sales a rep has along with the amount of time he was clocked into work.
I have the following two tables:
table1:
employeeID | item | price | timeID
----------------------------------------
1 | 1 | 12.92 | 123
1 | 2 | 10.00 | 123
1 | 2 | 10.00 | 456
table2:
ID | minutes_in_shift
--------------------------
123 | 45
456 | 15
I would join these two queries with the following SQL:
SELECT
t1.employeeID, t1.item, t1.price, t1.shiftID, t2.minutes_in_shift
FROM table1 t1
JOIN table 2 t2 ON (t2.ID = t1.timeID)
Which would return the following table:
employeeID | item | price | timeID | minutes_in_shift
---------------------------------------------------
1 | 1 | 12.92 | 123 | 45
1 | 2 | 10.00 | 123 | 45
1 | 2 | 10.00 | 456 | 15
I would like for the consolidate results, however, to have this outcome:
employeeID | itemsSold | priceTotals | totaltimeworked
-----------------------------------------------------------------
1 | 3 | 32.92 | 60
I could use COUNT and SUM for the items and price but I cannot figure out how to properly show the total time worked in the manner it appears above.
Note: I am only having trouble with calculating the time worked. In shift 123 - employee 1 was working 45 minutes, regardless of how many items he sold.
Any suggestions?
If you wish to use the sample data as they are you will need to extract the shifts and sum the minutes, like this:
with a as (
select employeeID, count(*) itemsSold, sum(price) priceTotals
from Sampletable1
group by employeeID),
b as (
select employeeID, shiftID, max(minutes_in_shift) minutes_in_shift
from Sampletable1
group by employeeID, shiftID),
c as (
select employeeID, sum(minutes_in_shift) totaltimeworked
from b
group by employeeID)
select a.employeeID, a.itemsSold, a.priceTotals, c.totaltimeworked
from a inner join c on a.employeeID = c.employeeID
However, with your existing tables the select statement will be much easier:
with a as (
select employeeID, timeID, count(*) itemsSold, sum(price) priceTotals
from table1
group by employeeID, timeID)
select a.employeeID, sum(a.itemsSold), sum(a.priceTotals), sum(table2.minutes_in_shift) totaltimeworked
from a inner join table2 on a.timeID = table2.ID
group by a.employeeID
I think this query should do what you want:
SELECT t1.employeeID,
count(t1.item) AS itemsSold,
sum(t1.price) AS priceTotals,
sum(DISTINCT t2.minutes_in_shift) AS totaltimeworked
FROM table1 t1
JOIN table2 t2 ON (t2.ID = t1.timeID)
GROUP BY t1.employeeID;
Check on SQL Fiddle

Select distinct records with Min Date from two tables with Left Join

I'm trying to retrieve all distinct AccountId’s as well as the earliest InsertDate for each. Occasionally the AccountId is not known and although the transactions may be distinct I want to bucket all of the ‘-1’s into their own group.
This is what I have attempted so far along with the schemas.
CREATE TABLE #tmpResults (
Trans Varchar(12),
AccountId Varchar(50),
EarlyDate DateTime DEFAULT getdate(), CardType Varchar(16))
insert #tmpResults
select [Trans] = convert(varchar(12),'CashSale')
, [AccountId] = b.AccountId
, [EarlyDate] = min(b.InsertDate)
, case when c.name LIKE '%VISA%' then 'VISA'
when c.name LIKE '%MasterCard%' then 'MasterCard'
when c.name LIKE '%AMEX%' then 'AMEX'
else 'Other'
end as [CardType]
from TransBatch b
left join CardVer_3 c WITH (NOLOCK) ON c.Id = B.BatchId
left join TransBatch b2
on (b.accountid = b2.accountid and (b.InsertDate > b2.InsertDate or b.InsertDate = b2.InsertDate))
and b2.accountid is NULL
group by b.accountid, b.InsertDate,c.name
order by b.accountid DESC
select * from #tmpResults
The table schemas are like so:
**TransBatch**
RecordId |BatchId |InsertDate | AccountId | AccNameHolder
6676 | 11 | 2012-11-01 05:19:04.000 | 12345 | Account1
6677 | 11 | 2012-11-01 05:19:04.000 | 12345 | Account1
6678 | 11 | 2012-11-01 05:19:04.000 | 55555 | Account2
6679 | 11 | 2012-11-01 05:19:04.000 | -1 | NULL
6680 | 12 | 2012-11-02 05:20:04.000 | 12345 | Account1
6681 | 12 | 2012-11-02 05:20:04.000 | 55555 | Account2
6682 | 13 | 2012-11-04 06:20:04.000 | 44444 | Account3
6683 | 14 | 2012-11-05 05:30:04.000 | 44444 | Account3
6684 | 14 | 2012-11-05 05:31:04.000 | -1 | NULL
**CardVer_3**
BatchId |Name
11 |MasterCard
12 |Visa
13 |AMEX
14 |GoCard
This will be an intermediate table, the output is planned to look like the attached.
Gordon, I made some very minor changes to your suggestion and believe I have the correct output: http://www.sqlfiddle.com/#!3/cfbc3/7/0 . Thank you very much. I'm not at all familiar with the windows functions so I'm going to brush up on these.
The code is here:
select 'CashSale' as [Trans],
AccountId,
min(InsertDateTime),
(case when name LIKE '%VISA%' then 'VISA'
when name LIKE '%MasterCard%' then 'MasterCard'
when name LIKE '%AMEX%' then 'AMEX'
else 'Other'
end) as [CardType]
from (select AccountId, InsertDateTime, c.name,
row_number() over (partition by AccountId order by insertDateTime asc) as seqnum
from TransBatch b left join
CardVer_3 c WITH (NOLOCK) ON c.batchId = B.BatchId
) t
where seqnum = 1
group by t.accountid, t.name
The next steps are to dump this into a temp table and try and get the output looking like the attached excel screen.
It sounds like you are trying to get the full record for the minimum insert date time. For this, you want to use windows functions:
select 'CashSale' as Trans,
AccountId,
min(InsertDate),
(case when name LIKE '%VISA%' then 'VISA'
when name LIKE '%MasterCard%' then 'MasterCard'
when name LIKE '%AMEX%' then 'AMEX'
else 'Other'
end) as [CardType]
from (select AccountId, InsertDate, c.name,
row_number() over (partition by AccountId order by insertDate desc) as seqnum
from TransBatch b left join
CardVer_3 c WITH (NOLOCK)
ON c.Id = B.BatchId
) t
where seqnum = 1
I'm taking a guess that "CashSale" means that the credit card did not match. The TransId is then either the recordId or "CashSale".

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.