selecting set of second lowest values - sql

I have two columns of interest ID and Deadline:
ID Deadline (DD/MM/YYYY)
1 01/01/2017
1 05/01/2017
1 04/01/2017
2 02/01/2017
2 03/01/2017
2 06/02/2017
2 08/03/2017
Each ID can have multiple (n) deadlines. I need to select all rows where the Deadline is second lowest for each individual ID.
Desired output:
ID Deadline (DD/MM/YYYY)
1 04/01/2017
2 03/01/2017
Selecting minimum can be done by:
select min(deadline) from XXX group by ID
but I am lost with "middle" values. I am using Rpostgresql, but any idea helps as well.
Thanks for your help

One way is to use ROW_NUMBER() window function
SELECT id, deadline
FROM (
SELECT *, ROW_NUMBER() OVER (PARTITION BY id ORDER BY deadline) rn
FROM xxx
) q
WHERE rn = 2 -- get only second lowest ones
or with LATERAL
SELECT t.*
FROM (
SELECT DISTINCT id FROM xxx
) i JOIN LATERAL (
SELECT *
FROM xxx
WHERE id = i.id
ORDER BY deadline
OFFSET 1 LIMIT 1
) t ON (TRUE)
Output:
id | deadline
----+------------
1 | 2017-04-01
2 | 2017-03-01
Here is a dbfiddle demo

Using ROW_NUMBER() after taking distinct records will eliminate the chance of getting the lowest date instead of second lowest if there are duplicate records.
select ID,Deadline
from (
select ID,
Deadline,
ROW_NUMBER() over(partition by ID order by Deadline) RowNum
from (select distinct ID, Deadline from SourceTable) T
) Tbl
where RowNum = 2

Related

How can I select the first and second to last record for a given group in SQL?

In Teradata I need to select the first record for a group as well as the second to last record for the same group for multiple groups with other set conditions. How can I acheive this?
ex table:
group id
records
date
place
One
1
2022-01-12
1
One
2
2022-01-12
1
One
3
2022-01-12
1
One
4
2022-01-12
1
One
1
2022-01-12
2
Two
1
2022-01-12
1
Two
2
2022-01-12
1
Two
3
2022-01-12
1
Two
4
2022-01-12
1
Two
5
2022-01-12
1
Two
6
2022-01-12
1
Two
5
2022-05-12
1
Two
6
2022-05-12
1
Desired Output:
group id
records
date
place
One
1
2022-01-12
1
One
3
2022-01-12
1
Two
1
2022-01-12
1
Two
5
2022-01-12
1
I would do something like this:
select
*
from
table
qualify row_number() over (partition by groupid order by date ASC) = 1 --"first"
or row_number() over (partition by groupid order by date DESC) = 2 -- "second to last"
Not tested, just an idea.
select q.*
from
(
select t.*,
max(t.records)-1 over (partition by t.group_id) as mxprev
from yourtable as t
) as q
where q.records=1 or q.records=q.mxprev
This works if you're ok with specifying each group manually:
(SELECT * FROM extable
WHERE groupid = 'One'
ORDER BY date ASC -- or whatever you want to order by to get "first" and "second to last"
LIMIT 1)
UNION
(SELECT * FROM extable
WHERE groupid = 'One'
ORDER BY date DESC -- or whatever you want to order by to get "first" and "second to last"
LIMIT 1
OFFSET 1)
UNION
(SELECT * FROM extable
WHERE groupid = 'Two'
ORDER BY date ASC -- or whatever you want to order by to get "first" and "second to last"
LIMIT 1)
UNION
(SELECT * FROM extable
WHERE groupid = 'Two'
ORDER BY date DESC -- or whatever you want to order by to get "first" and "second to last"
LIMIT 1
OFFSET 1);
(looking into a more generic solution atm)
Should work , not tested
select * from
(select *
,row_number() over(partition by group id order by records) rn1
from table1
) t1 where t1 = 1
union all
select * from
(
select *
,row_number() over(partition by group id order by records desc) rn2
from table1
) t2 where rn2 = 2

Oracle SQL: receive ID of grouped foreign key with smallest Date

I have a table given.
I need the ID of each BID with the smallest MODIFIED date
ID
BID
MODIFIED
1
1
01.01.2020
2
1
01.07.2020
3
2
04.08.2020
4
2
04.06.2020
5
2
01.07.2020
6
2
01.10.2020
7
3
01.09.2020
Desired output:
ID
BID
MODIFIED
1
1
01.01.2020
4
2
04.06.2020
7
3
01.09.2020
so far, I can get a list of BIDs with the smallest MODIFIED date, but not the ID from it:
select BID, min(MODIFIED) from MY_TABLE group by BID
how can I receive the ID, however?
Oracle has a "first" aggregation function, which uses the keep syntax:
select BID, min(MODIFIED),
min(id) keep (dense_rank first over order by modified) as id
from MY_TABLE
group by BID;
A common alternative uses window functions:
select t.*
from (select t.*,
row_number() over (partition by bid order by modified asc) as seqnum
from my_table t
) t
where seqnum = 1;

Return last amount for each element with same ref_id

I have 2 tables, one is credit and other one is creditdetails.
Creditdetails creates new row every day for each of credit.
ID Amount ref_id date
1 2 1 16.03
2 3 1 17.03
3 4 1 18.03
4 1 2 16.03
5 2 2 17.03
6 0 2 18.03
I want to sum up amount of every row with the unique id and last date. So the output should be 4 + 0.
You can use ROW_NUMBER to filter on the latest amount per ref_id.
Then SUM it.
SELECT SUM(q.Amount) AS TotalLatestAmount
FROM
(
SELECT
cd.ref_id,
cd.Amount,
ROW_NUMBER() OVER (PARTITION BY cd.ref_id ORDER BY cd.date DESC) AS rn
FROM Creditdetails cd
) q
WHERE q.rn = 1;
A test on db<>fiddle here
With this query:
select ref_id, max(date) maxdate
from creditdetails
group by ref_id
you get all the last dates for each ref_id, so you can join it to the table creditdetails and sum over amount:
select sum(amount) total
from creditdetails c inner join (
select ref_id, max(date) maxdate
from creditdetails
group by ref_id
) g
on g.ref_id = c.ref_id and g.maxdate = c.date
I think you want something like this,
select sum(amount)
from table
where date = ( select max(date) from table);
with the understanding that your date column doesn't appear to be in a standard format so I can't tell if it needs to be formatted in the query to work properly.

Group BY Having COUNT, but Order on a column not contained in group

I have a table where I need to get the ID, for a group(based on ID and Name) with a COUNT(*) = 3, for the latest set of timestamps.
So for example below, I want to retrieve ID 2. As it has 3 rows, and the latest timestamps (even though ID 3 has latest timestamps overall, it doesn't have a count of 3).
But I don't understand how to order by Date, as I cannot contain it in the Group By clause, as it is not the same:
SELECT TOP 1 ID
FROM TABLE
GROUP BY ID,Name
HAVING COUNT(ID) > 2
AND Name = 'ABC'
--ORDER BY Date DESC
Sample Data
ID Name Date
1 ABC 2015-05-27 08:00
1 ABC 2015-05-27 09:00
1 ABC 2015-05-27 10:00
2 ABC 2015-05-27 11:00
2 ABC 2015-05-27 12:00
2 ABC 2015-05-27 13:00
3 ABC 2015-05-27 14:00
3 ABC 2015-05-27 15:00
In SQL server, you need aggregate the columns not on group by list:
SELECT TOP 1 ID
FROM TABLE
WHERE Name = 'ABC'
GROUP BY ID,Name
HAVING COUNT(ID) > 2
ORDER BY MAX(Date) DESC
The name filter should be put before the group by for better performance, if you really need it.
You could do it in a nested query.
Subquery:
SELECT ID
from TABLE
GROUP BY ID
HAVING Count(ID) > 2
That gives you the IDs you want. Put that in another query:
SELECT ID, Data
FROM Table
Where ID in (Subquery)
Order by Date DESC;
First get all desired IDs. That is all IDs having a count > 2. Get the maximum date for each such ID. Then rank these records with ROW_NUMBER, giving the latest ID #1. At last remove all IDs that are not ranked #1.
select name, id
from
(
select
name, id, row_count() over (partition by name order by max_date desc) as rn
from
(
select name, id, max(date) as max_date
from mytable
--where name = 'ABC'
group by name, id
having count(*) > 2
) wanted_ids
) ranked_ids
where rn = 1;

sql server select query a bit complex

I have a item_prices table with prices. Those prices vary at any time.
I want to display all items where date is highest
ITEM_prices
id | Items_name | item_price | item_date
------------------------------------------
1 A 10 2012-01-01
2 B 15 2012-01-01
3 B 16 2013-01-01
4 C 50 2013-01-01
5 A 20 2013-01-01
I want to display ABC items once each with highest date like as below
id | Items_name | item_price | item_date
-------------------------------------------
3 B 16 2013-01-01
4 C 50 2013-01-01
5 A 20 2013-01-01
when you can use native functions then why to go for any window function or CTE.
SELECT t1.*
FROM ITEM_prices t1
JOIN
(
SELECT Items_name,MAX(item_date) AS MaxItemDate
FROM ITEM_prices
GROUP BY Items_name
)t2
ON t1.Items_name=t2.Items_name AND t1.item_date=t2.MaxItemDate
One approach would be to use a CTE (Common Table Expression) if you're on SQL Server 2005 and newer (you aren't specific enough in that regard).
With this CTE, you can partition your data by some criteria - i.e. your Items_name - and have SQL Server number all your rows starting at 1 for each of those "partitions", ordered by some criteria.
So try something like this:
;WITH NewestItem AS
(
SELECT
id, Items_name, item_price, item_date,
RowNum = ROW_NUMBER() OVER(PARTITION BY Items_name ORDER BY item_date DESC)
FROM
dbo.ITEM_Prices
)
SELECT
id, Items_name, item_price, item_date
FROM
NewestItem
WHERE
RowNum = 1
Here, I am selecting only the "first" entry for each "partition" (i.e. for each Items_Name) - ordered by the item_date in descending order (newest date gets RowNum = 1).
Does that approach what you're looking for??
One way is to use window functions to find the maximum date for each item:
select id, Items_name, item_price, item_date
from (select ip.*,
max(item_date) over (partition by items_name) as max_item_date
from item_prices
) ip
where item_date = max_item_date;
This will select all rows with the max date.
SELECT *
FROM item_prices
WHERE item_date = (SELECT max(item_date) FROM item_prices)
ORDER BY ID
This will select all rows for each item with the max date for that item.
select id, Items_name, item_price, item_date
from (select items_name, max(item_date) max_item_date
from item_prices
group by items_name
) ip
where item_date = max_item_date and items_name = ip.items_name