Oracle query needs to return the highest date from result - sql

I have a really big query in which makes some troubles for me because one join can return several rows. I only want the latest row (identified by a date field) in this result set, but I cant seem to put together the correct query to make it work.
The query I need MAX date from is:
SELECT custid,reason,date FROM OPT opt WHERE opt.custid = 167043;
Teh custid is really found through a join, but for simplicity I've added it to the where clause here. This query produces the following result:
custid grunn date
167043 "Test 1" 19.10.2005 12:33:18
167043 "Test 2" 28.11.2005 16:23:35
167043 "Test 3" 14.06.2010 15:43:16
How can I retrieve only one record from this resultset? And that record is the one with the highest date? Ultimately Im putting this into a big query which does alot of joins, so hopefully I can use this example into my bigger query.

You can do this:
SELECT * FROM
( SELECT custid,reason,date FROM OPT opt WHERE opt.custid = 167043
ORDER BY date DESC
)
WHERE ROWNUM = 1;

You can solve it by using analytic functions. Try something like this:
select custid
,reason
,date
from (select custid
,reason
,date
,row_number() over(partition by cust_id order by date desc) as rn
from opt)
where rn = 1;
This is how it works: The resultset is divided into groups of cust_id (partition by). In each group, the rows will be sorted by the date column in descending order (order by). Each row within the group will be assigned a sequence number (row_number) from 1 to N.
This way the row with the highest value for date will be assigned 1, the second latest 2, third latest 3 etc..
Finally, I just pick the rows with nr = 1, which basically filters out the other rows.

Or another way using the LAST function in its aggregate form.
with my_source_data as (
select 167043 as custid, 'Test 1' as reason, date '2010-10-01' as the_date from dual union all
select 167043 as custid, 'Test 2' as reason, date '2010-10-02' as the_date from dual union all
select 167043 as custid, 'Test 3' as reason, date '2010-10-03' as the_date from dual union all
select 167044 as custid, 'Test 1' as reason, date '2010-10-01' as the_date from dual
)
select
custid,
max(reason) keep (dense_rank last order by the_date) as reason,
max(the_date)
from my_source_data
group by custid
I find this quite useful as it rolls the process of finding the last row and the value all into one. The use of MAX (or another aggregate function such as MIN) in case that the combination of the grouping and the order by is not deterministic.
This function will basically take the contents of the column based on the grouping, order it by the ordering given then take the last value.

rather than using row_number() I think it's better to select what you actually want to select (e.g. the last date)
SELECT custid
, reason
, date
from
(
SELECT custid
, reason
, date
, max(opt.date) over (partition by opt.custid order by opt.date) last_date
FROM OPT opt
WHERE opt.custid = 167043;
)
where date = last_date

both solutions with ROW_NUMBER and KEEP are good. I would tend to prefer ROW_NUMBER when retrieving a large number of columns, and keep KEEP for one or two columns, otherwise you will have to deal with duplicates and the statement will get pretty unreadable.
For a small number of columns however, KEEP should perform better

Related

PostgreSQL backward intersection & join

I have a survey form of certain questions for a certain facility.
the facility can be monitored(data entry) more than once in a month.
now i need the latest data(values) against the questions
but if there is no latest data against any question i will traverse through prior records(previous dates) of the same month.
i can get the latest record but i don't know how to get previous record of the same month id there is no latest data.
i am using PostgreSQL 10.
Table Structure is
Desired output is
You can try to use ROW_NUMBER window function to make it.
SELECT to_char(date, 'MON') month,
facility,
idquestion,
value
FROM (
SELECT *,ROW_NUMBER() OVER(PARTITION BY facility,idquestion ORDER BY DATE DESC) rn
FROM T
) t1
where rn = 1
demo:db<>fiddle
SELECT DISTINCT
to_char(qdate, 'MON'),
facility,
idquestion,
first_value(value) OVER (PARTITION BY facility, idquestion ORDER BY qdate DESC) as value
FROM questions
ORDER BY facility, idquestion
Using window functions:
first_value(value) OVER ... gives you the first value of a window frame. The frame is a group of facility and idquestion. Within this group the rows are ordered by date DESC. So the very last value is first no matter which date it is
DISTINCT filtered the tied values (e.g. there are two values for facility == 1 and idquestion == 7)
Please notice:
"date" is a reserved word in Postgres. I strongly recommend to rename your column to avoid certain trouble. Furthermore in Postgres lower case is used and is recommended.

MAX() for 2 Dates Separately

I am trying to find a way to get the Max Date from one field and then to remove duplication get the Max of those dates from another field.
So far I have managed to get the Max of the Effective Dates, but need to get the Max timestamp from those values to remove duplication.
Here is what I have so far:
SELECT
a2.CUST_ID
, Address
, Effective_Date --DATE variable
, Timestamp_Entry --DATETIME variable
FROM
(SELECT
CUST_ID
, MAX (Effective_Date) as Most_Effective_Date
FROM Address_Table
GROUP BY CUST_ID) a1
JOIN Address_Table a2
ON a1.CUST_ID = a2.CUST_ID and a1.Most_Effective_Date = a2.Effective_Date
(Some timestamp entrys may be newer entries with older effective date, which is why the Effective Date takes priority, and then the TimeStamp should remove duplicates
I think this is what you want:
select a.*
from (select a.*,
row_number() over (partition by cust_id order by effective_date desc, timestamp_entry desc) as seqnum
from address_table a
) a
where seqnum = 1;
This returns the "most recent" address for each customer based on the two columns.

SQL: select next available date for multiple records

I have an oracle DB.
My table has ID and DATE columns (and more).
I would like to select for every ID the next available record after a certain date. For only one ID the query would be:
SELECT * FROM my_table
WHERE id = 1 AND date >= '01.01.2018'
(just ignoring the to_date() function)
How would that look like for multiple IDs? And I do want to SELECT *.
Thanks!
We can use ROW_NUMBER here:
SELECT ID, date -- and maybe other columns
FROM
(
SELECT *,
ROW_NUMBER() OVER (PARTITION BY ID ORDER BY date) rn
FROM my_table
WHERE date >= date '2018-01-01'
) t
WHERE rn = 1
The idea here is to assign a row number to each ID partition, starting with the earliest date which occurs after the cutoff you specify. The first record from each partition would then be the immediate next date, assuming it exists.

Closest Date in Oracle

I am trying to get the closest date to a given date in Oracle. I have been working form How to get the closest dates in Oracle sql, but the example in that question uses two different tables. I'm no PL SQL guru and I'm struggling to get this to work. I have a single table that contains an ID field and a Date field. I need the ID that it closest to the date passed into the query.
select *
from ( select SEQ_ID, ENTERED_DATE, rank() over ( partition by ENTERED_DATE order by difference asc ) as rnk
from ( select SEQ_ID, ENTERED_DATE, abs(ENTERED_DATE - 2/9/1999) from DOWNTIME_DETAILS)) as difference
where rnk = 1
This gives me an error: "SQL command not properly ended"
How can I fix the query? What am I doing wrong?
The as difference is assigning a table alias. You can't use as for table aliases, only for column aliases (so as rnk is OK). Just remove the second as. As you are refering to difference in the outer query, it looks like you meant it to be a column alias and just had it in the wrong place:
select *
from (
select SEQ_ID, ENTERED_DATE,
rank() over ( order by difference ) as rnk
from (
select SEQ_ID, ENTERED_DATE,
abs(ENTERED_DATE - to_date('2/9/1999', 'MM/DD/YYYY')) as difference
from DOWNTIME_DETAILS
)
)
where rnk = 1
You also had a date without any quote marks, so that would have been interpreted as numbers in this case, and wouldn't have had the effect you were looking for. You should always use explicit conversion; I've guessed your date format. And you should not be partitioning by the original entered_date as that will make everything rank as 1. If you have two records that have the same difference they will still both rank as 1 so you'll see both. You could add a way to break ties by modifying the order by, e.g.
rank() over ( order by difference , entered_date, seq_id ) as rnk
... but you'll need to specify the criteria so it makes sense for your data and situation.
You could also do this:
select max(SEQ_ID) keep (dense_rank first
order by abs(ENTERED_DATE - to_date('2/9/1999', 'MM/DD/YYYY')))
as seq_id,
max(ENTERED_DATE) keep (dense_rank first
order by abs(ENTERED_DATE - to_date('2/9/1999', 'MM/DD/YYYY')))
as entered_date
from DOWNTIME_DETAILS;
... but then you have to supply the date twice.

SQL Server : UNION ALL but remove duplicate IDs by choosing first date of occurrence

I am unioning two queries but I'm getting an ID that occurs in each query. I do not know how to keep only the first time the id occurs. Everything else about the row is different. In general, it will be hard to know which of the two queries I will have to keep a duplicate on, therefore, I need a general solution.
I was thinking about creating a temp table and choosing the min date (once the date has been converted to an int).
Any ideas on the proper syntax?
You can do this using the row_number() function. This will assign a sequential number, starting with 1, to each row with the same id (based on the partition by clause). The ordering of the sequence is determined by the order by clause. So, the following assigns 1 to the earliest date for each id:
select t.*
from (select t.*,
row_number() over (partition by id order by date asc) as seqnum
from ((select *
from <subquery1>
) union all
(select *
from <subquery2>
)
) t
) t
where seqnum = 1;
The final where clause simply filters for the first occurrence.
If you use the keyword UNION, then it will remove duplicates from the two data sets you are working with. UNION ALL preserves duplicates.
You can view the specifics here:
http://www.w3schools.com/sql/sql_union.asp
If you want to only have one of the 2 records and they are not identical you will have to filter them yourself. You may need to do something like the following. THis may be possible to do with the one (select union select) block but this should get you started.
select *
from (
select id
, date
, otherstuf
from table_1
union all
select id
, date
, otherstuf
from table_2
) x1
, (
select id
, date
, otherstuf
from table_1
union all
select id
, date
, otherstuf
from table_2
) x2
where x1.id = x2.id
and x1.date < x2.date
Although rethinking this if you go down a path like this why bother to UNION it?