Choose column based on max() of another column - sql

Given the data below from the two tables cases and acct_transaction, how can I include just the acct_transaction.create_date of the largest acct_transaction amount whilst also calculating the sum of all amounts and the value of the largest amount? Platform is t-sql.
id amount create_date
---|----------|------------|
1 | 1.99 | 01/09/2009 |
1 | 2.99 | 01/13/2009 |
1 | 578.23 | 11/03/2007 |
1 | 64.57 | 03/03/2008 |
1 | 3.99 | 12/12/2012 |
1 | 31337.00 | 04/18/2009 |
1 | 123.45 | 05/12/2008 |
1 | 987.65 | 10/10/2010 |
Result set should look like this:
id amount create_date sum max_amount max_amount_date
---|----------|------------|----------|-----------|-----------
1 | 1.99 | 01/09/2009 | 33099.87 | 31337.00 | 04/18/2009
1 | 2.99 | 01/13/2009 | 33099.87 | 31337.00 | 04/18/2009
1 | 578.23 | 11/03/2007 | 33099.87 | 31337.00 | 04/18/2009
1 | 64.57 | 03/03/2008 | 33099.87 | 31337.00 | 04/18/2009
1 | 3.99 | 12/12/2012 | 33099.87 | 31337.00 | 04/18/2009
1 | 31337.00 | 04/18/2009 | 33099.87 | 31337.00 | 04/18/2009
1 | 123.45 | 05/12/2008 | 33099.87 | 31337.00 | 04/18/2009
1 | 987.65 | 10/10/2010 | 33099.87 | 31337.00 | 04/18/2009
This is what I have so far, I just don't know how to pull the date of the largest acct_transaction amount for max_amount_date column.
SELECT cases.id, acct_transaction.amount, acct_transaction.create_date AS 'create_date', SUM(acct_transaction.amount) OVER () AS 'sum', MIN(acct_transaction.amount) OVER () AS 'max_amount'
FROM cases INNER JOIN
acct_transaction ON cases.id = acct_transaction.id
WHERE (cases.id = '1')

;WITH x AS
(
SELECT c.id, t.amount, t.create_date,
s = SUM(t.amount) OVER(),
m = MAX(t.amount) OVER(),
rn = ROW_NUMBER() OVER(ORDER BY t.amount DESC)
FROM dbo.cases AS c
INNER JOIN dbo.acct_transaction AS t
ON c.id = t.id
)
SELECT x.id, x.amount, x.create_date,
[sum] = y.s,
max_amount = y.m,
max_amount_date = y.create_date
FROM x CROSS JOIN x AS y WHERE y.rn = 1;

You can just do a full outer join to the table which defines the aggregates:
select id, amount, create_date, x.sum, x.max_amount, x.max_amount_date
from table1
full outer join
(select sum(amount) as sum, max(amount) as max_amount,
(select top 1 create_date from table1 where amount = (select max(amount) from table1)) as max_amount_date
from table1) x
on 1 = 1
SQL Fiddle demo

Try this abomination of a query... I make no claims for its speed or elegance. It's likely I should pray that Cod have mercy on my soul.
Here is the out put of a join on the two tables that you mention but for which you do not provide schemas.
[SQL Fiddle][1]
SELECT A.case_id
,A.trans_id
,A.trans_amount
,A.trans_create_date
,A.trans_type
,B.max_amount
,B.max_amount_date
,E.sum_amount
FROM acct_transaction AS A
INNER JOIN (select C.case_id
,MAX(C.trans_amount) AS max_amount
,C.trans_create_date AS max_amount_date
FROM acct_transaction AS C group by C.case_id, C.trans_create_date ) AS B ON B.case_id = A.case_id
inner JOIN (select D.case_id, SUM(D.trans_amount) AS sum_amount FROM acct_transaction AS D GROUP BY D.case_id) AS E on E.case_id = A.case_id
WHERE (A.case_id = '1') AND (A.trans_type = 'F')
GROUP BY A.case_id

Thanks, that got me on the right track to this which is working:
,CAST((SELECT TOP 1 t2.create_date from acct_transaction t2
WHERE t2.case_sk = act.case_sk AND (t2.trans_type = 'F')
order by t2.amount, t2.create_date DESC) AS date) AS 'max_date'
It won't let me upvote because I have less than 15 rep :(

Related

SQL joining tables based off latest previous date

Let's say I have two tables for example:
Table 1 - customer order information
x---------x--------x-------------x
cust_id | item | order date |
x---------x--------x-------------x
1 | 100 | 01/01/2020 |
1 | 112 | 03/07/2022 |
2 | 100 | 01/02/2020 |
2 | 168 | 05/03/2022 |
3 | 200 | 15/06/2021 |
----------x--------x-------------x
and Table 2 - customer membership status
x---------x--------x-------------x
cust_id | Status | startdate |
x---------x--------x-------------x
1 | silver | 01/01/2019 |
1 | bronze | 05/12/2019 |
1 | gold | 05/06/2022 |
2 | silver | 24/12/2021 |
----------x--------x-------------x
I want to join the two tables so that I can see what their membership status was at the time of purchase, to produce something like this:
x---------x--------x-------------x----------x
cust_id | item | order date | status |
x---------x--------x-------------x----------x
1 | 100 | 01/01/2020 | bronze |
1 | 112 | 03/07/2022 | gold |
2 | 100 | 01/02/2020 | NULL |
2 | 168 | 05/03/2022 | silver |
3 | 200 | 15/06/2021 | NULL |
----------x--------x-------------x----------x
Tried multiple ways include min/max, >=, group by having etc with no luck. I feel like multiple joins are going to be needed here but I can't figure out - any help would be greatly appreciated.
(also note: dates are in European/au not American format.)
Try the following using LEAD function to define periods limits for each status:
SELECT T.cust_id, T.item, T.orderdate, D.status
FROM order_information T
LEFT JOIN
(
SELECT cust_id, Status, startdate,
LEAD(startdate, 1, GETDATE()) OVER (PARTITION BY cust_id ORDER BY startdate) AS enddate
FROM customer_membership
) D
ON T.cust_id = D.cust_id AND
T.orderdate BETWEEN D.startdate AND D.enddate
See a demo on SQL Server.
SELECT
[cust_id],
[item],
[order date],
[status]
FROM
(
SELECT
t1.[cust_id],
t1.[item],
t1.[order date],
t2.[status],
ROW_NUMBER() OVER (PARTITION BY t1.[cust_id], t1.[item] ORDER BY t2.[startdate] DESC) rn
FROM #t1 t1
LEFT JOIN #t2 t2
ON t1.[cust_id] = t2.[cust_id] AND t1.[order date] >= t2.[startdate]
) a
WHERE rn = 1
SELECT
o.cust_id,
o.item,
o.order_date,
m.status
FROM
customer_order o
LEFT JOIN
customer_membership m
ON o.cust_id = m.cust_id
AND o.order_date > m.start_date
GROUP BY
o.cust_id,
o.item,
o.order_date
HAVING
Count(m.status) = 0
OR m.start_date = Max(m.start_date);

SQL Query - Check for Two Distinct Values

Given the below data set I want to run a query to highlight any 'pairs' that do not consist of a 'left' and 'right'.
+---------+-----------+---------------+----------------------+
| Pair_Id | Pair_Name | Individual_Id | Individual_Direction |
+---------+-----------+---------------+----------------------+
| 1 | A | A1 | Left |
| 1 | A | A2 | Right |
| 2 | B | B1 | Right |
| 2 | B | B2 | Left |
| 3 | C | C1 | Left |
| 3 | C | C2 | Left |
| 4 | D | D1 | Right |
| 4 | D | D2 | Left |
| 5 | E | E1 | Left |
| 5 | E | E2 | Right |
+---------+-----------+---------------+----------------------+
In this instance Pair 3 'C' has two lefts. Therefore, I would look to display the following:
+---------+-----------+---------------+----------------------+
| Pair_Id | Pair_Name | Individual_Id | Individual_Direction |
+---------+-----------+---------------+----------------------+
| 3 | C | C1 | Left |
| 3 | C | C2 | Left |
+---------+-----------+---------------+----------------------+
You can simply use not exists:
select t.*
from t
where not exists (select 1
from t t2
where t2.pair_id = t.pair_id and
t2.Individual_Direction <> t.Individual_Direction
) ;
With an index on (pair_id, Individual_Direction), this should not only be the most concise solution but also the fastest.
If you want to be sure that there are pairs (the above returns singletons):
select t.*
from t
where not exists (select 1
from t t2
where t2.pair_id = t.pair_id and
t2.Individual_Direction <> t.Individual_Direction
) and
exists (select 1
from t t2
where t2.pair_id = t.pair_id and
t2.Individual_ID <> t.Individual_ID
);
You can also do this using window functions:
select t.*
from (select t.*,
count(*) over (partition by pair_id) as cnt,
min(status) over (partition by pair_id) as min_status,
max(status) over (partition by pair_id) as max_status
from t
) t
where cnt > 1 and min_status <> max_status;
One option uses aggregation:
WITH cte AS (
SELECT Pair_Name
FROM yourTable
WHERE Individual_Direction IN ('Left', 'Right')
GROUP BY Pair_Name
HAVING MIN(Individual_Direction) = MAX(Individual_Direction)
)
SELECT *
FROM yourTable
WHERE Pair_Name IN (SELECT Pair_Name FROM cte);
The HAVING clause used above asserts that a matching pair has both a minimum and maximum direction which are the same. This implies that such a pair only has one direction.
As is the case with Gordon's answer, an index on (Pair_Name, Individual_Direction) might help performance:
CREATE INDEX idx ON yourTable (Pair_Name, Individual_Direction);
There should be an elegant way of using window function than what I wrote:
WITH ranked AS
(
SELECT *, RANK() OVER(ORDER BY Pair_Id, Pair_Name, Individual_Direction) AS r
FROM pairs
),
counted AS
(
SELECT Pair_Id, Pair_Name, Individual_Direction,r, COUNT(r) as times FROM ranked
GROUP BY Pair_Id, Pair_Name, Individual_Direction, r
HAVING COUNT(r) > 1
)
SELECT ranked.Pair_Id, ranked.Pair_Name, ranked.Individual_Id, ranked.Individual_Direction FROM ranked
RIGHT JOIN counted
ON ranked.Pair_Id=counted.Pair_Id
AND ranked.Pair_Name=counted.Pair_Name
AND ranked.Individual_Direction=counted.Individual_Direction

Retrieve the minimal create date with multiple rows

I have an issue with an SQL query that I am trying to write. I am trying to retrieve the row that has the minimal create_dt for each inst (see table) and amount (which isn't unique).
Unfortunately I can't use group by as the amount column isn't unique.
+--------------+--------+------+-------------+
| Company_Name | Amount | inst | Create Date |
+--------------+--------+------+-------------+
| Company A | 1000 | 4545 | 01/10/2018 |
| Company A | 400 | 4545 | 01/11/2018 |
| Company A | 200 | 4545 | 31/10/2018 |
| Company B | 2000 | 4893 | 01/10/2016 |
| Company B | 212 | 4893 | 04/10/2016 |
| Company B | 100 | 4893 | 10/10/2017 |
| Company B | 20 | 4893 | 04/10/2018 |
+--------------+--------+------+-------------+
In the above example I expect to see:
+--------------+--------+------+-------------+
| Company_Name | Amount | inst | Create Date |
+--------------+--------+------+-------------+
| Company A | 1000 | 4545 | 01/10/2018 |
| Company B | 2000 | 4893 | 01/10/2016 |
+--------------+--------+------+-------------+
Code:
SELECT
bill_company, bill_name, account_no
FROM
dbo.customer_information;
SELECT
balance_id, balance_id2, minus_balance,new_balance,
create_date, account_no
FROM
dbo.btr
SELECT
balance_id, balance_id2, expired_Date, amount, balance_type, account_no
FROM
dbo.btr_balance
SELECT
balance_ist, expired_date, account_no, balance_type
FROM
dbo.BALANCE_inst
Retrieve the minimal create data for a balance instance with the lowest balance for a balance inst.
(SELECT
bill_company,
bill_name,
account_no,
balance_ist,
amount,
MIN(create_date)
FROM
dbo.mtr btr
LEFT JOIN
btr_balance btrb ON btr.balance_id = btrb.balance_id
AND btr.balance_id2 = btrb.balance_id2
LEFT JOIN
balance_inst bali ON btr.account_no = bali.account_no
AND btrb.expired_date = bali.expired_date
GROUP BY
bill_company, bill_name, account_no,amount, balance_ist)
I have seen some solutions about using correlated query but can't see to get my head around it.
Common Table Expression (CTE) will help you.
;with cte as (
select *, row_number() over(partition by company_name order by create_date) rn
from dbo.myTable
)
select * from cte
where rn = 1;
use row_number() i assumed bill_company is your company name
select * from
( SELECT bill_company,
bill_name,
account_no,
balance_ist,
amount,
create_date,
row_number() over(partition by bill_company order by create_date) rn
FROM dbo.mtr btr left join btr_balance btrb
on btr.balance_id = btrb.balance_id and btr.balance_id2 = btrb.balance_id2
left join balance_inst bali
on btr.account_no = bali.account_no and btrb.expired_date = bali.expired_date
) t where t.rn=1

Select Customers where not have value in other table with join

I need to select all Customers from the table Customer where Value in table Customer_Value is not 4.
Customers:
+------------+-------+
| Customer | ... |
+------------+-------+
| 312 | ... |
| 345 | ... |
| 678 | ... |
+------------+-------+
Customer_Value:
+------------+-------+
| Customer | Value |
+------------+-------+
| 312 | 1 |
| 312 | 2 |
| 345 | 1 |
| 345 | 2 |
| 345 | 3 |
| 678 | 1 |
| 678 | 2 |
| 678 | 4 |
+------------+-------+
To get my result I've used the following query:
SELECT C.Customer FROM [Customer] C
Left join Customer_Value V ON (C.Customer = V.Customer)
WHERE C.Customer NOT IN (SELECT Customer FROM [Customer_Value] WHERE Value = '4')
GROUP BY C.Customer
So my question is:
Is that a fast and good query? Or are there some other better solutions to get all the Customer Ids?
You can avoid Negative condition using Left Join and IS NULL Filter in where Condition.
SELECT C.Customer FROM [Customer] C
Left join Customer_Value V ON (C.Customer = V.Customer) and V.Value = '4'
WHERE V.Value is null
GROUP BY C.Customer
Your method is overkill; the JOIN is not necessary. I would use not exists:
select c.Customer
from Customer c
where not exists (select 1
from customer_value cv
where c.Customer = v.Customer and
cv.value = 4
);
You can also use aggregation, if you assume that all customers have at least one row in customer_value:
select cv.customer
from customer_value cv
group by cv.customer
having sum(case when cv.value = 4 then 1 else 0 end) = 0;
I would do
select * from customer c
join (
select distinct customer from customer_value where value!=4) v on c.customer = v.customer

How to join results from two tables in Oracle 10

Let say that I have 2 tables with the same structure : STOCK and NEW_STOCK.
These tables have a primary key composed of (ID_DATE, ID_SELLER, ID_INVOICE, ID_DOC).
Now, I need to get for every (ID_DATE, ID_SELLER, ID_INVOICE, ID_DOC), the value of the amount (field AMOUNT) regarding this requirement:
If a record is present in NEW_STOCK, I get the AMOUNT from NEW_STOCK, otherwise, I get the AMOUNT from STOCK table.
Note that ID_DATE and ID_SELLER are the inputs given to the query, i.e. a query that considers only STOCK table will look like :
select AMOUNT, ID_DATE, ID_SELLER, ID_INVOICE
from STOCK
where ID_DATE = 1
and ID_SELLER = 'SELL1';
STOCK :
+---------+-----------+------------+--------+--------+
| ID_DATE | ID_SELLER | ID_INVOICE | ID_DOC | AMOUNT |
+---------+-----------+------------+--------+--------+
| 1 | SELL1 | IN1 | DOC1 | 100 |
| 1 | SELL1 | IN2 | DOC2 | 50 |
| 1 | SELL1 | IN3 | DOC3 | 42 |
+---------+-----------+------------+--------+--------+
NEW_STOCK:
+---------+-----------+------------+--------+--------+
| ID_DATE | ID_SELLER | ID_INVOICE | ID_DOC | AMOUNT |
+---------+-----------+------------+--------+--------+
| 1 | SELL1 | IN2 | DOC2 | 12 |
+---------+-----------+------------+--------+--------+
Then, I must get the following results:
+---------+-----------+------------+--------+--------+
| ID_DATE | ID_SELLER | ID_INVOICE | ID_DOC | AMOUNT |
+---------+-----------+------------+--------+--------+
| 1 | SELL1 | IN1 | DOC1 | 100 |
| 1 | SELL2 | IN2 | DOC2 | 12 |
| 1 | SELL3 | IN3 | DOC3 | 42 |
+---------+-----------+------------+--------+--------+
ps: I'm working on Oracle 10.
Use outer join and NVL(arg1, arg2) function.
It returns first argument if it is not NULL, otherwise it returns second argument. Example:
select s.AMOUNT, s.ID_DATE, s.ID_SELLER, s.ID_INVOICE,
NVL(n.AMOUNT, s.AMOUNT) amount
from STOCK s, NEW_STOCK n
where s.ID_DATE = n.ID_DATE(+)
and s.ID_SELLER = n.ID_SELLER(+)
and s.ID_INVOICE = n.ID_INVOICE(+)
and s.ID_DOC = n.ID_DOC(+)
and s.ID_DATE = 1
and s.ID_SELLER = 'SELL1';
You can use LEFT OUTER JOIN syntax instead of (+) if you find it more readable. I'm using Oracle since v7 and I like (+) more.
Here is LEFT OUTER JOIN syntax:
select s.AMOUNT, s.ID_DATE, s.ID_SELLER, s.ID_INVOICE,
NVL(n.AMOUNT, s.AMOUNT) amount
from STOCK s left outer join NEW_STOCK n
on s.ID_DATE = n.ID_DATE
and s.ID_SELLER = n.ID_SELLER
and s.ID_INVOICE = n.ID_INVOICE
and s.ID_DOC = n.ID_DOC
where s.ID_DATE = 1
and s.ID_SELLER = 'SELL1';
SELECT * FROM (
SELECT * FROM new_stock
UNION ALL
SELECT * FROM stock
WHERE (ID_DATE,ID_SELLER,ID_INVOICE,ID_DOC) NOT IN
(SELECT ID_DATE,ID_SELLER,ID_INVOICE,ID_DOC FROM new_stock)
)
WHERE ID_DATE = 1
AND ID_SELLER = 'SELL1';
The following should work for it:
SELECT s.AMOUNT, s.ID_DATE, s.ID_SELLER, s.ID_INVOICE
FROM STOCK s
LEFT JOIN NEW_STOCK ns
ON s.ID_DATE = ns.ID_DATE
AND s.ID_SELLER = ns.ID_SELLER
AND s.ID_INVOICE = ns.ID_INVOICE
WHERE s.ID_DATE = 1
AND s.ID_SELLER = 'SELL1'
AND ns.ID_DATE IS NULL
UNION
SELECT AMOUNT, ID_DATE, ID_SELLER, ID_INVOICE
FROM NEW_STOCK
WHERE ID_DATE = 1
AND ID_SELLER = 'SELL1';
Exclude the matched rows from a LEFT JOIN and UNION that set with the results from the NEW_STOCK table.
SELECT COALESCE(NS.AMOUNT, S.AMOUNT) AMOUNT,
S.ID_DATE,
S.ID_SELLER,
S.ID_INVOICE
FROM STOCK S
LEFT JOIN NEW_STOCK NS ON S.ID_DATE = NS.ID_DATE
AND S.ID_SELLER = NS.ID_SELLER
AND S.ID_INVOICE = NS.ID_INVOICE
AND S.ID_DOC = NS.ID_DOC
WHERE S.ID_DATE = 1
AND S.ID_SELLER = 'SELL1'