Apply MAX value. Then add conditions based on the MAX Value Row - sql

I have the below table. I need the MAX value of Date Per ID when CategoryID = 201 Per ID
TableA
ID Date CategoryID
1 1/1/17 101
1 1/2/17 201
1 1/4/17 201
1 1/5/17 301
2 1/1/17 101
2 5/1/17 201
(Work) Query:
,MAX(TABLEA.DATE)
KEEP (DENSE_RANK LAST ORDER BY TABLEA.DATE)
OVER (PARTITION BY ID)
AS most_recent_dt
I need to add a condition in the query: When CategoryId = 201 Then take the MAX Date
Expected Output:
ID Date CatergoryId Most_Recent_Dt
1 1/1/17 101 1/4/17
1 1/2/17 201 1/4/17
1 1/4/17 201 1/4/17
1 1/5/17 301 1/4/17
2 1/1/17 101 5/1/17
2 5/1/17 201 5/1/17
*Edit
Now that I have my MAX Line I need to add Conditions based on the MAX line only.
Expected Output:
In short.
**Partition by ID.
Apply Max Value when CategoryID = 201
Now apply conditions based off the MAX value ROW
When Role = Gold and HistID is not null Then "Approved"
else "Pending"
ID Date CategoryID Most_Recent_Dt Role HistId Category
1 1/1/17 101 1/4/17 Gold (Null) Approved
1 1/2/17 201 1/4/17 Bronze 201 Approved
*1 1/4/17 201 1/4/17 Gold 101 Approved
1 1/5/17 301 1/4/17 Gold 101 Approved
2 1/1/17 101 5/1/17 Gold (Null) Pending
*2 5/1/17 201 5/1/17 Bronze 101 Pending

You should not need the KEEP clause (since it is the same as the MAX) and can just do:
MAX( CASE When CategoryId = 201 THEN TABLEA.DATE END )
OVER (PARTITION BY ID)
AS most_recent_201_dt
Now that I have my MAX Line I need to add Conditions based on the MAX line only.
Case When (Role = Gold And HistId IS NOT NULL) OR () THEN 'Approved' WHEN... THEN 'NotApproved' ELSE 'Pending' END AS Category
This is when you would use the KEEP clause as you want the values for the Role and HistID columns for the latest date value.
Something like:
CASE
WHEN (
MAX( CASE Role WHEN 'Gold' THEN Role END )
KEEP ( DENSE_RANK LAST
ORDER BY CASE WHEN CategoryId = 201 THEN TABLEA.DATE END NULLS FIRST )
OVER ( PARTITION BY ID )
= Role
AND
MAX( HistID )
KEEP ( DENSE_RANK LAST
ORDER BY CASE WHEN CategoryId = 201 THEN TABLEA.DATE END NULLS FIRST,
CASE Role WHEN 'Gold' THEN Role END NULLS FIRST )
OVER ( PARTITION BY ID )
IS NOT NULL
)
OR ( ... )
THEN 'Approved'
WHEN ...
THEN 'NotApproved'
ELSE 'Pending'
END

I would do this as:
MAX(CASE WHEN CategoryId = 201 THEN TABLEA.DATE END) OVER (PARTITION BY id) as most_recent_dt
That is, don't think of this as a "first value" calculation. Think of it as a (condition) maximum over all records with the same id.

Related

sql finding cid with most expired cards [closed]

Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 4 months ago.
Improve this question
I have a table Cards(card_id,status,cid)
With the columns:
cid - customer id
status - exp/vld
card_id - card id's
How to find the cid with the most expired cards?
From Oracle 12, you can use:
SELECT cid,
COUNT(*) AS num_exp
FROM cards
WHERE status = 'exp'
GROUP BY cid
ORDER BY num_exp DESC
FETCH FIRST ROW WITH TIES;
You can get count of expired cards for individual customers and then choose customer with MAX count. The below query should give results.
WITH t AS(
SELECT cid, count(1) customer_exp_cards_count
FROM Cards where status = 'exp'
group by cid)
SELECT cid FROM t t1
WHERE t1.customer_exp_cards_count IN (SELECT MAX(t2.customer_exp_cards_count)
FROM t t2)
Sample data and its result:
cardid status cid
3 exp 5
1 exp 1
2 exp 1
3 vld 1
5 vld 1
1 exp 2
2 exp 2
3 exp 2
4 vld 2
5 vld 2
6 exp 3
7 vld 4
4 vld 5
Result:
2
Suppose you have these two tables (just a sample data)
CUSTOMERS
CUST_ID
CUST_NAME
CUST_STATUS
101
John
ACTIVE
102
Annie
ACTIVE
103
Jane
ACTIVE
104
Bob
INACTIVE
CARDS
CARD_ID
CARD_STATUS
CUST_ID
1001001
VALID
101
1001002
VALID
101
1001003
EXPIRED
101
1001004
EXPIRED
101
1001005
VALID
101
1002010
VALID
102
1002020
EXPIRED
102
1002030
EXPIRED
102
1002040
EXPIRED
102
1003100
VALID
103
1003200
VALID
103
If you want just a CUST_ID with the number of most expired cards you can do it without table CUSTOMERS:
Select CUST_ID, EXPIRED_CARDS
From (Select CUST_ID, Count(CARD_ID) "EXPIRED_CARDS" From cards Where CARD_STATUS = 'EXPIRED' Group By CUST_ID)
Where EXPIRED_CARDS = (Select Max(EXPIRED_CARDS) From (Select Count(CARD_ID) "EXPIRED_CARDS" From cards Where CARD_STATUS = 'EXPIRED' Group By CUST_ID) )
--
-- R e s u l t
-- CUST_ID EXPIRED_CARDS
-- ---------- -------------
-- 102 3
Maybe you could consider creating a CTE with the data from both tables which will give you dataset that you could use later for different questions not just for this one. Something like this:
WITH
customers_cards AS
(
Select
cst.CUST_ID,
cst.CUST_NAME,
cst.CUST_STATUS,
crd.CARD_ID,
crd.CARD_STATUS,
Sum(CASE WHEN crd.CUST_ID Is Null Then 0 Else 1 End) OVER(Partition By crd.CUST_ID) "TOTAL_NUM_OF_CARDS",
Sum(CASE WHEN crd.CARD_ID Is Null Then Null WHEN crd.CARD_STATUS = 'VALID' And crd.CARD_ID Is Not Null Then 1 Else 0 End) OVER(Partition By crd.CUST_ID) "VALID_CARDS",
Sum(CASE WHEN crd.CARD_ID Is Null Then Null WHEN crd.CARD_STATUS = 'EXPIRED' And crd.CARD_ID Is Not Null Then 1 Else 0 End) OVER(Partition By crd.CUST_ID) "EXPIRED_CARDS"
From
customers cst
Left Join
cards crd on(crd.CUST_ID = cst.CUST_ID)
)
/* R e s u l t :
CUST_ID CUST_NAME CUST_STATUS CARD_ID CARD_STATUS TOTAL_NUM_OF_CARDS VALID_CARDS EXPIRED_CARDS
---------- --------- ----------- ------- ----------- ------------------ ----------- -------------
101 John ACTIVE 1001001 VALID 5 3 2
101 John ACTIVE 1001002 VALID 5 3 2
101 John ACTIVE 1001003 EXPIRED 5 3 2
101 John ACTIVE 1001004 EXPIRED 5 3 2
101 John ACTIVE 1001005 VALID 5 3 2
102 Annie ACTIVE 1002010 VALID 4 1 3
102 Annie ACTIVE 1002040 EXPIRED 4 1 3
102 Annie ACTIVE 1002030 EXPIRED 4 1 3
102 Annie ACTIVE 1002020 EXPIRED 4 1 3
103 Jane ACTIVE 1003100 VALID 2 2 0
103 Jane ACTIVE 1003200 VALID 2 2 0
104 Bob INACTIVE 0
*/
This can be used to answer many more potential questions. Here is the list of customers sorted by number of expired cards (descending):
Select Distinct
CUST_ID, CUST_NAME, TOTAL_NUM_OF_CARDS, VALID_CARDS, EXPIRED_CARDS
From
customers_cards
Order By
EXPIRED_CARDS Desc Nulls Last, CUST_ID
--
-- R e s u l t :
-- CUST_ID CUST_NAME TOTAL_NUM_OF_CARDS VALID_CARDS EXPIRED_CARDS
-- ---------- --------- ------------------ ----------- -------------
-- 102 Annie 4 1 3
-- 101 John 5 3 2
-- 103 Jane 2 2 0
-- 104 Bob 0
OR to answer your question:
Select Distinct
CUST_ID, CUST_NAME, TOTAL_NUM_OF_CARDS, VALID_CARDS, EXPIRED_CARDS
From
customers_cards
Where
EXPIRED_CARDS = (Select Max(EXPIRED_CARDS) From customers_cards)
Order By
CUST_ID
--
-- R e s u l t :
-- CUST_ID CUST_NAME TOTAL_NUM_OF_CARDS VALID_CARDS EXPIRED_CARDS
-- ---------- --------- ------------------ ----------- -------------
-- 102 Annie 4 1 3
Regards...

Select only one row when a certain condition is met?

ID
NAME
DATE
STATUS
1
Joe
01-22
Approved
1
Joe
01-22
Pending
2
Bill
02-22
Approved
2
Bill
02-22
Sent back
3
John
01-22
Approved
4
Bob
02-22
Pending
How do I only return one row per ID, placing priority on approved?
Example: for Id 1 I only want the row that is approved and not the one that is pending.
Some Id's may only have 1 record for example ID 4 has just one record and is pending.
What I want is:
IF status = approved and pending for the same Id then keep the approved record and not select the pending record
If status = pending then keep that record
This will preferentially select Approved, then Pending, then everything else. If you don't want "everything else" just filter in the WHERE clause.
select id,
name,
date,
status
from (
select *,
row_number() over
( partition by id
order by case when status = 'Approved' then 1
when status = 'Pending' then 2
else 3
end asc,
date
) as first_by_date_with_approved_precedence
from your_table
) tmp
where first_by_date_with_approved_precedence = 1
It could also be as easy as the following (provided status is not blank or null)
Select Top 1 with ties *
from YourTable
order by row_number() over (partition by id order by Status)
Results
ID NAME DATE STATUS
1 Joe 01-22 Approved
2 Bill 02-22 Approved
3 John 01-22 Approved
4 Bob 02-22 Pending

Postgres select max id only if value of another column is negative

So i need to write a query that will return some values but only if the latest record in that table for any user is less than 0. Here is what if been playing around with:
select
et.id,
et.user_id,
et.amount,
et.trans_type,
COALESCE(et."endingBalance", 0) AS current_balance,
et."processId",
upu.email,
et.created_at
from "users-permissions_user" upu
join employer_transactions et
on upu.id = et.user_id
and et.id = (
select max(et2.id) from employer_transactions et2 where et2."endingBalance" < 0
)
and this is what is is returning:
id
user_id
amount
trans_type
current_balance
email
created_at
1946
333
150
CREDIT
-900.31
...
...
but if i run this query to test that query for that user_id:
select
id,
user_id,
amount ,
trans_type,
"endingBalance"
from employer_transactions et
where user_id = 333
order by id desc;
here is what i see:
id
user_id
amount
trans_type
ending_balance
1952
333
3
DEBIT
1297.31
1951
333
1
DEBIT
1299.31
1950
333
2
DEBIT
1298.31
1947
333
400
CREDIT
1300.31
1946
333
150
CREDIT
-900.31
so in this case what im looking for was for this query to have returned nothing because the record with the highest id is not negative
but lets say the sample data set is this:
id
user_id
amount
trans_type
ending_balance
900
333
3
DEBIT
-1297.31
899
333
1
DEBIT
1299.31
700
222
2
DEBIT
-1298.31
699
222
400
CREDIT
1300.31
600
111
150
CREDIT
900.31
599
111
150
CREDIT
-800.31
then what im looking for my query to return is
id
user_id
amount
trans_type
current_balance
email
created_at
900
333
3
DEBIT
-1297.31
...
...
700
222
2
DEBIT
-1298.31
...
...
because those were the latest records for that particular user_id and the current_balance was negative but noting for user_id: 111 becasue while yes there was a negative record but it wasnt the latest record for that user_id
SELECT T.*
FROM
(
SELECT *, RANK() OVER(PARTITION BY user_id ORDER BY id DESC) RNK
FROM trans_tbl
) T
WHERE T.RNK = 1 AND T.ending_balance < 0;
Fiddle
A common technique, for me, is to do a subquery that finds a desired subset. After that, do more filtering.
with ids as (
select max(id) as last_id
from t
group by user_id
)
select *
from t
where end_balance < 0
and id in (select last_id from ids)
;
The above gives desired output, given that the `id`'s are unique and form an ascending sequence.

How to join three tables in SQL Server 2012 and calculate ranking based on 2 attributes

I have 3 tables:
tblEmployee
E_ID E_Name E_City
--------------------------------
101 sasa Mumbai
102 sdf California
103 trt Illinois
104 dssd Texas
105 trt Pennsylvania
106 wee Arizona
107 rer Texas
108 wqe California
109 sadd Michigan
tblGen
Tgenerate is boolean value
Emp_ID Tgenerate
--------------------
105 1
108 1
102 1
102 1
102 0
104 1
107 0
108 1
109 0
And the tblStat:
Emp_ID Status
------------------
103 Pending
107 Pending
103 Pending
101 Delivered
104 Pending
104 Pending
108 Pending
101 Delivered
105 Delivered
I have to join these 3 tables and want output like this
E_Name EmployeeID City TgenerateCount Delivered_Count Ranking
TgenerateCount is calculated for every employee. It is count of TgenerateCount having value 1, for ex 102 has 2 TgenerateCount and 109 has 0 TgenerateCount.
Delivered_Count is count of Status of those who has 'Delivered' status. For ex. 101 has 2 Delivered. I want to display every user in the output table.
Any help would be greatly appreciated.
As your two fact tables have a many:1 relationship with your dimension table, you should aggregate them before joining them.
SELECT
e.*,
COALESCE(g.rows, 0) AS TgenerateCount,
COALESCE(s.rows, 0) AS DeliveredCount,
RANK() OVER (ORDER BY COALESCE(g.rows, 0) + COALESCE(s.rows,0) DESC) AS ranking
FROM
tblEmployee e
LEFT JOIN
(
SELECT E_ID, COUNT(*) AS rows FROM tblGen WHERE Tgenerate = 1 GROUP BY E_ID
)
g
ON g.E_ID = e.E_ID
LEFT JOIN
(
SELECT E_ID, COUNT(*) AS rows FROM tblStat WHERE STATUS = 'Delivered' GROUP BY E_ID
)
s
ON s.E_ID = e.E_ID
You've been unclear on how the ranking should be completed, so this simply gives an example ranking.

Conditional Analytic Function

Work:
,MAX(CASE WHEN MAX(HIST)
AND workid IS NOT NULL
AND ROLE = 'red'
THEN 'ASSIGNED'
ELSE 'UNASSIGNED'
END)
OVER (PARTITION BY id) AS ASSIGNED
Criteria:
Partition By ID
Look at last entry from each ID, utilizing the PKHistid column
If Role = Red and Workid IS NOT NULL from the last entry for each ID
Then Assigned
Else Unassigned
Table:
PKHistid ID Role Entry_Date Workid
1 101 Red 1/1/17 201
2 101 Yellow 1/2/17 201
3 102 Yellow 5/1/17 (Null)
4 102 Red 6/1/17 202
5 103 Red 7/1/17 202
6 103 Red 7/5/17 202
Expected Results: (New Column Assigned_Status)
PKHistid ID Role Entry_Date Workid *Assigned_Status
1 101 Red 1/1/17 201 Unassigned
2 101 Yellow 1/2/17 201 Unassigned
3 102 Yellow 5/1/17 (Null) Assigned
4 102 Red 6/1/17 202 Assigned
5 103 Red 7/1/17 202 Assigned
6 103 Red 7/5/17 202 Assigned
Is this "instead of" your earlier question (also posted today), or is it "in addition to" it? If it is "in addition to", note that you can do both things in the same query.
Here you need a case expression to create the additional column. In the case expression, the condition uses an analytic function. I prefer the analytic version of the LAST function (which, unfortunately, many developers don't seem to know and use). Please read the Oracle documentation for it if it is not familiar to you.
Note that analytic functions can't be nested; but there is absolutely no prohibition against using analytic functions in case expressions. I often see solutions where the analytic function is called in a subquery, and then further processing (such as case expressions using the result from the analytic functions) is done in an outer query. Unnecessary layering!
with
inputs ( pkhistid, id, role, entry_date, workid) as (
select 1, 101, 'Red' , to_date('1/1/17', 'mm/dd/rr'), 201 from dual union all
select 2, 101, 'Yellow', to_date('1/2/17', 'mm/dd/rr'), 201 from dual union all
select 3, 102, 'Yellow', to_date('5/1/17', 'mm/dd/rr'), null from dual union all
select 4, 102, 'Red' , to_date('6/1/17', 'mm/dd/rr'), 202 from dual union all
select 5, 103, 'Red' , to_date('7/1/17', 'mm/dd/rr'), 202 from dual union all
select 6, 103, 'Red' , to_date('7/5/17', 'mm/dd/rr'), 202 from dual
)
-- End of simulated inputs (for testing only, not part of the solution).
-- SQL query begins BELOW THIS LINE. Use your actual table and column names.
select pkhistid, id, role, entry_date, workid,
case when max(role) keep (dense_rank last order by pkhistid)
over (partition by id) = 'Red'
and
max(workid) keep (dense_rank last order by pkhistid)
over (partition by id) is not null
then 'Assigned'
else 'Unassigned' end as assigned_status
from inputs
order by id, pkhistid -- If needed
;
PKHISTID ID ROLE ENTRY_DATE WORKID ASSIGNED_STATUS
---------- ---------- ------ ---------- ---------- ---------------
1 101 Red 01/01/17 201 Unassigned
2 101 Yellow 01/02/17 201 Unassigned
3 102 Yellow 05/01/17 Assigned
4 102 Red 06/01/17 202 Assigned
5 103 Red 07/01/17 202 Assigned
6 103 Red 07/05/17 202 Assigned