Getting a max value for every id given in query (Postgres) - sql

I have query like this
select distinct on (foreign_id) foreign_id, id, date
from table
where foreign_id IN (1, 2, 3)
I am getting result as
foreign_id
id
date
1
101
2019-03-20
2
102
2020-02-06
3
103
2020-06-09
Which is good because I want to get only single row every foreign_id but I would like to get row with max date and max id value in result.
Right now for id number 1 I am getting date 2019-03-20 which is not the greatest date that is in table
I have tried to use max() function but It returns only one row from one given foreign_id
Any ideas?

You are missing an ORDER BY:
select distinct on (foreign_id) foreign_id, id, date
from table
where foreign_id IN (1, 2, 3)
order by foreign_id, date DESC;

You can use analytical function as follows:
select select foreign_id, id, date from
(select foreign_id, id, date,
row_number() over (partition by foreign_id order by date desc) as rn
from table
where foreign_id IN (1, 2, 3) ) t
where rn = 1

Just add the ORDER BY clause, because DISTINCT ON takes the first record of an ordered group.
select distinct on (foreign_id) foreign_id, id, date
from table
where foreign_id IN (1, 2, 3)
order by foreign_id, date desc <<--- add this

You can achieve this through cte and row_number() as below:
with cte as (
select foreign_id,id,date, row_number()over (partition by foreign_id order by date desc) rownum
from t
)
select foreign_id,id,date from cte where rownum=1

Related

make in one query a count and max over partion by in SQL Oracle

In Oracle SQL I have a table with
userid
qualification
date
One
Qual1
01/01/2020
One
Qual2
01/01/2022
Two
Qual1
01/01/2021
Three
Qual2
01/01/2022
I want to have per user id:
the count of qualifications
the most recent qualification
So for this example I want:
userid
qualification
count
One
Qual2
2
Two
Qual1
1
Three
Qual2
1
I thought to use something like this:
select userid,
count(qualification)OVER (PARTITION BY userid) as count_qual,
MAX(qualification) OVER (PARTITION BY userid ORDER BY date desc) as qual_id
from Qualificaitons
but it returns me two lines for userid One
You can use MAX(..) KEEP (DENSE_RANK ..) aggregation function:
SELECT userid,
MAX(qualification) KEEP (DENSE_RANK LAST ORDER BY "DATE") AS qualification,
COUNT(qualification) AS count
FROM qualifications
GROUP BY userid;
Which, for the sample data:
CREATE TABLE qualifications (userid, qualification, "DATE") AS
SELECT 'One', 'Qual1', DATE '2020-01-01' FROM DUAL UNION ALL
SELECT 'One', 'Qual2', DATE '2022-01-01' FROM DUAL UNION ALL
SELECT 'Two', 'Qual1', DATE '2021-01-01' FROM DUAL UNION ALL
SELECT 'Three', 'Qual2', DATE '2022-01-01' FROM DUAL;
Outputs:
USERID
QUALIFICATION
COUNT
One
Qual2
2
Three
Qual2
1
Two
Qual1
1
db<>fiddle here
You can use two functions to compute the result you want. For example:
select userid, qualification, cnt
from (
select t.*,
count(*) over(partition by userid) as cnt,
row_number() over(partition by userid order by date desc) as rn
from Qualificaitons t
) x
where rn = 1
use first_value instead of max as agg function. read documentation here.
select userid, count(qualification)OVER (PARTITION BY userid) as count_qual,
first_value(qualification) OVER (PARTITION BY userid ORDER BY date desc) as qual_id
from Qualificaitons

Query to get the most recent record and with the higher value

I have this data sample :
card service date value
1 1 27-10-2014 5
1 1 28-10-2014 5
1 1 28-10-2014 6
What is the best approach to return the last row (most recent and in case of ties the higher value)?
Thanks in advance.
Edited:
card service date value
1 1 27-10-2014 5
1 1 28-10-2014 5
1 1 28-10-2014 6
2 2 29-10-2014 7
This should have returned the 3rd and 4th record.
Thanks for all the replies. But today I have a small change request. I will have a column with Percentage and another column with a Char to indicate if is a value or a percentage.
I am trying to do something like this:
select card,
service,
max(date),
case when type = 'v'
then
MAX(value) KEEP (
dense_rank first order by date desc
)
else
max(percentage) valor keep (
dense_rank first order by date desc
) end
from table
group by card,
service;
But I am getting ORA-00979: not a GROUP BY expression
So you want the row with the most recent date and highest value?
If you're on 12.1 and up, you can use fetch first. Sort by the date and value descending and get one row:
create table t (
card int, service int, dt date, val int
);
insert into t values (1, 1, date'2014-10-27', 5);
insert into t values (1, 1, date'2014-10-28', 5);
insert into t values (1, 1, date'2014-10-28', 6);
select * from t
order by dt desc, val desc
fetch first 1 row only;
CARD SERVICE DT VAL
1 1 28-OCT-2014 00:00:00 6
On 11.2 and earlier you need a subquery where you assign a row number sorted by date and value:
with ranked as (
select t.*,
row_number() over (order by dt desc, val desc) rn
from t
)
select * from ranked
where rn = 1;
CARD SERVICE DT VAL RN
1 1 28-OCT-2014 00:00:00 6 1
One good way is to use KEEP..DENSE_RANK or FIRST aggregate function.
SELECT card
,service
,MAX(date_t)
,MAX(value) KEEP (
DENSE_RANK FIRST ORDER BY date_t DESC
) AS value
FROM yourtable
GROUP BY card
,service;
Demo
Try this:
select *
from (
select x.*
from <tablename> x
where date = (select max(date) from <tablename> )
order by value desc
) where rownum<2 ;
Try this query : -
SELECT TOP 1 * FROM tableName ORDER BY dateCol1 DESC,valueCol2 DESC;
Simple Solution in MySQL,
select * from demo_table t
where value = (select max(value) from demo_table)
order by date desc limit 1

Group by with MIN value in same query while presnting all other columns

I have a view called a with this data:
ID tDate name task val
23 2015-06-14
23 2015-06-25
126 2015-06-18
126 2015-06-22
126 2015-06-24
ID is integer and tDate is timestamp.
Basically I want to get for each ID the min value of tDate and present this row.
meaning:
ID tDate name task val
23 2015-06-14
126 2015-06-18
I wrote this query:
select ID, min(tDate)
from a
group by ID
order by ID
This is working BUT it doesn't allow me to present all other columns of a
for example if I do:
select ID, min(tDate), name
from a
group by ID
order by ID
it says that name must be under group by. So I wrote this query:
select ID, MIN(tDate), name, task, val , ....
from a
group by ID, name, task, val , ....
order by ID
And this one doesn't work. it gives false results.
How do I solve it?
Postgres has the very convenient distinct on for this type of problem:
select distinct on (id) a.*
from a
order by id, tdate;
This will return one row for each id. The row is the first one determined by the ordering defined in the order by clause.
Do a join from the one table to a sub-query table on just the ID / Min Date
select
YT.ID,
YT.tDate as OriginalDate,
PQ.MinDate,
YT.name,
YT.task,
YT.val
from
YourTable YT
JOIN ( select ID, min( tdate ) as MinDate
from YourTable
group by ID ) as PQ
on YT.ID = PQ.ID
AND YT.tDate = PQ.MinDate
order by
ID
Try something like this:
select a.id, a.tdate , .... from a
join (select id, min(tdate) min_date
from a
group by ID
) b
on a.id=b.id and a.tdate = b.min_date
order by a.id

"Group" some rows together before sorting (Oracle)

I'm using Oracle Database 11g.
I have a query that selects, among other things, an ID and a date from a table. Basically, what I want to do is keep the rows that have the same ID together, and then sort those "groups" of rows by the most recent date in the "group".
So if my original result was this:
ID Date
3 11/26/11
1 1/5/12
2 6/3/13
2 10/15/13
1 7/5/13
The output I'm hoping for is:
ID Date
3 11/26/11 <-- (Using this date for "group" ID = 3)
1 1/5/12
1 7/5/13 <-- (Using this date for "group" ID = 1)
2 6/3/13
2 10/15/13 <-- (Using this date for "group" ID = 2)
Is there any way to do this?
One way to get this is by using analytic functions; I don't have an example of that handy.
This is another way to get the specified result, without using an analytic function (this is ordering first by the most_recent_date for each ID, then by ID, then by Date):
SELECT t.ID
, t.Date
FROM mytable t
JOIN ( SELECT s.ID
, MAX(s.Date) AS most_recent_date
FROM mytable s
WHERE s.Date IS NOT NULL
GROUP BY s.ID
) r
ON r.ID = t.ID
ORDER
BY r.most_recent_date
, t.ID
, t.Date
The "trick" here is to return "most_recent_date" for each ID, and then join that to each row. The result can be ordered by that first, then by whatever else.
(I also think there's a way to get this same ordering using Analytic functions, but I don't have an example of that handy.)
You can use the MAX ... KEEP function with your aggregate to create your sort key:
with
sample_data as
(select 3 id, to_date('11/26/11','MM/DD/RR') date_col from dual union all
select 1, to_date('1/5/12','MM/DD/RR') date_col from dual union all
select 2, to_date('6/3/13','MM/DD/RR') date_col from dual union all
select 2, to_date('10/15/13','MM/DD/RR') date_col from dual union all
select 1, to_date('7/5/13','MM/DD/RR') date_col from dual)
select
id,
date_col,
-- For illustration purposes, does not need to be selected:
max(date_col) keep (dense_rank last order by date_col) over (partition by id) sort_key
from sample_data
order by max(date_col) keep (dense_rank last order by date_col) over (partition by id);
Here is the query using analytic functions:
select
id
, date_
, max(date_) over (partition by id) as max_date
from table_name
order by max_date, id
;

How to select last entry for one distinct pairing of two columns in Oracle?

I need to select the last row in mytable for a given pair of columns in Oracle v11.2:
id type timestamp raw_value normal_value
-- ---- --------- --------- ------------
1 3 3pm 3-Jun "Jon" "Jonathan"
1 3 5pm 3-Jun "Jonathan" "Jonathan"
1 3 2pm 4-Jun "John" "Jonathan"
1 3 8pm 6-Jun "Bob" "Robert"
1 5 6pm 3-Jun "NYC" "New York City"
1 5 7pm 5-Jun "N.Y.C." "New York City"
4 8 1pm 1-Jun "IBM" "International Business Machines"
4 8 5pm 8-Jun "I.B.M." "International Business Machines"
I'm thinking the query would be something like this:
SELECT raw_value, normal_value, MAX(timestamp)
FROM mytable
WHERE id = 1 and type = 3
GROUP BY id, type
For the above, this should give me:
"Bob", "Robert", 8pm 6-Jun
I do not actually need the timestamp in my answer, but only need it to select the matching row for the given id and type whose timestamp is greatest.
Will my approach work in Oracle v11.2, and if so, is there a way to omit timestamp from the selected columns since I don't actually need its value?
You can do this with the row_number() function:
select raw_value, normal_value, timestamp
from (select myt.*, ROW_NUMBER() over
(partition by id, type order by timestamp desc)
as seqnum
from mytable myt
) tmp
where seqnum = 1
and id = 1 and type = 3;
row_number() is an analytic function (aka window function) that assigns sequential numbers to rows. Every group defined by id, type gets its own numbers. The first row is the one with the most recent timestamp (order by timestamp desc). The outer select chooses this row in the where clause.
In the case of ties, this version returns only one row. To get all the rows, use rank() instead of row_number().
Try this:
SELECT m1.raw_value, m1.normal_value
FROM mytable m1
WHERE id = 1 and type = 3 and timestamp = (
SELECT MAX(timestamp)
FROM mytable m2
WHERE m1.id = m2.id and m1.type = m2.type
GROUP BY m2.id, m2.type
)
You can determine the most recent timestamp using the Oracle analytic RANK function like this:
SELECT
raw_value,
normal_value,
RANK() OVER (ORDER BY timestamp DESC) as TimestampRank
FROM myTable
This will set the TimestampRank column with value 1 for the row with the highest timestamp. If there's a tie for the highest timestamp, all rows with the highest timestamp with have TimestampRank set to 1.
To get just the "Bob", "Robert", surround the query above with an outer query that selects just those columns and filters for TimestampRank = 1:
SELECT raw_value, normal_value
FROM (
SELECT
raw_value,
normal_value,
RANK() OVER (ORDER BY timestamp DESC) as TimestampRank
FROM myTable
)
WHERE TimestampRank = 1
Note again that if there's a tie for the highest timestamp, all rows with that value will be returned. If you always want one row regardless of ties, use ROW_NUMBER() instead of RANK() in the query above.
Try
select max(raw_value ) keep (dense_rank last order by timestamp),
max(normal_value ) keep (dense_rank last order by timestamp)
from mytable
WHERE id = 1 and type = 3