Get most recent Price for each Item - sql

I have a table:
ItemID PurchaseDate Price
001 03/17/2013 19.00
002 03/17/2013 14.00
001 03/18/2013 13.00
002 03/18/2013 15.00
001 03/19/2013 17.00
003 03/19/2013 19.00
I need to write a SQL query to get the Price corresponding to the latest PurchaseDate for each ItemID.
Entries in table might not necessarily be entered ordered by date
Like this:
ItemID PurchaseDate Price
001 03/19/2013 17.00
002 03/18/2013 15.00
003 03/19/2013 19.00

The idea behind the subquery is it separately gets the latest PurchaseDate for each ItemID. The result of the subquery is then joined back on the table provided that it matches on two conditions: ItemID and PurchaseDate.
SELECT a.*
FROM TableName a
INNER JOIN
(
SELECT ItemID, MAX(PurchaseDate) max_date
FROM TableName
GROUP BY ItemID
) b ON a.ItemID = b.ItemID AND
a.PurchaseDate = b.max_date

-- WITH clause, works with Oracle.
-- I added this clause to dynamically run the SELECT statement without any DDL.
-- Ignore this section for use on MS Access
WITH v AS (
SELECT 001 ItemID, TO_DATE('03/17/2013', 'MM/DD/YYYY') PurchaseDate, 19.00 Price FROM dual
UNION ALL
SELECT 002, TO_DATE('03/17/2013', 'MM/DD/YYYY'), 14.00 FROM dual
UNION ALL
SELECT 001, TO_DATE('03/18/2013', 'MM/DD/YYYY'), 13.00 FROM dual
UNION ALL
SELECT 002, TO_DATE('03/18/2013', 'MM/DD/YYYY'), 15.00 FROM dual
UNION ALL
SELECT 001, TO_DATE('03/19/2013', 'MM/DD/YYYY'), 17.00 FROM dual
UNION ALL
SELECT 003, TO_DATE('03/19/2013', 'MM/DD/YYYY'), 19.00 FROM dual
)
-- The WITH clause was upto here.
-- Below starts the main query which works on most platforms including MS Access.
-- I have referenced to the same table "v" two times - v_in and v_out.
-- You will need to change the "v" with your table name.
SELECT v_out.itemid, v_out.purchasedate, v_out.price
FROM v v_out
WHERE EXISTS (SELECT 1
FROM v v_in
WHERE v_in.itemid = v_out.itemid
GROUP BY v_in.itemid
HAVING MAX(v_in.purchasedate) = v_out.purchasedate)
ORDER BY v_out.itemid
;

Related

Fetch record with max number in one column except if date in that column is > than today

I have a problem with fetching few exceptions from DB.
Example, table b:
sn
v_num
start_date
end_date
1
001
01-01-2019
31-12-2099
1
002
01-01-2021
31-01-2022
1
003
01-02-2022
31-12-2099
2
001
01-01-2022
31-12-2099
2
002
01-07-2022
31-07-2022
2
003
01-08-2022
31-12-2099
Expected output:
sn
v_num
start_date
end_date
1
003
01-02-2022
31-12-2099
2
001
01-01-2022
31-12-2099
Currently I'm here:
SELECT * FROM table a, table b
WHERE a.sn = b.sn
AND b.v_num = (SELECT max (v_num) FROM b WHERE a.sn = b.sn)
but obviously that is not good because of a few cases like this with sn = 2.
Conclusion, I need to get unique sn record where v_num is max (95% of them in DB) except in case if start_date of max v_num record is > today.
Filter using start_date <= TRUNC(SYSDATE) then use the ROW_NUMBER analytic function:
SELECT *
FROM (
SELECT a.*,
ROW_NUMBER() OVER (PARTITION BY sn ORDER BY v_num DESC) AS rn
FROM "TABLE" a
WHERE start_date <= TRUNC(SYSDATE)
)
WHERE rn = 1;
If the start_date has a time component then you can use start_date < TRUNC(SYSDATE) + INTERVAL '1' DAY to get all the values for today from 00:00:00 to 23:59:59.
If you can have ties for the maximum and want to return all the ties then you can use the RANK analytic function instead of ROW_NUMBER.
Which, for the sample data:
CREATE TABLE "TABLE" (sn, v_num, start_date, end_date) AS
SELECT 1, '001', DATE '2022-01-01', DATE '2099-12-31' FROM DUAL UNION ALL
SELECT 1, '002', DATE '2022-01-01', DATE '2022-01-31' FROM DUAL UNION ALL
SELECT 1, '003', DATE '2022-02-01', DATE '2099-12-31' FROM DUAL UNION ALL
SELECT 2, '001', DATE '2022-01-01', DATE '2099-12-31' FROM DUAL UNION ALL
SELECT 2, '002', DATE '2022-07-01', DATE '2022-07-31' FROM DUAL UNION ALL
SELECT 2, '003', DATE '2022-08-01', DATE '2099-12-31' FROM DUAL;
Outputs:
SN
V_NUM
START_DATE
END_DATE
RN
1
003
2022-02-01 00:00:00
2099-12-31 00:00:00
1
2
001
2022-01-01 00:00:00
2099-12-31 00:00:00
1
db<>fiddle here

How to get latest date of specific record without specifying record?

I have 2 tables: order and transportation.
ORDER
id
TRANSPORTATION
id
order_id
date
status (could be 'ok', 'ca', 'ko')
1 order can have more than 1 transportation. I want all orders which its latest transportation status is 'OK'.
If I do:
select ord.*
from orders ord
join transportation tr
on ord.id = tr.order_id
where tr.date = (select max(date) from transportation where status like 'OK');
I will get the latest date of ALL transportations but I only want the latest date of all transportations of that order in specific.
For example, if I have these orders with these transportations and I want the last transportations of each order which status are 'ok':
order_id, transportation_id, date, status
001, 001, 01/01/19, ok
001, 002, 01/01/20, ca
002, 003, 01/01/19, ca
002, 004, 01/01/18, ok
003, 005, 01/01/17, ok
003, 006, 01/01/16, ca
I would expect these results:
003, 005, 01/01/17, ok
You can do it without an additional sub-query using an analytic query:
SELECT order_id,
transportation_id,
"DATE",
status
FROM (
select ord.id AS order_id,
tr.id AS transportation_id,
tr."DATE",
tr.status,
RANK() OVER ( PARTITION BY ord.id ORDER BY tr."DATE" DESC ) AS rnk
from orders ord
join transportation tr
on ord.id = tr.order_id
)
WHERE rnk = 1
AND status = 'ok';
Use RANK (or DENSE_RANK) if you want to return rows tied for the greatest date per order id; or use ROW_NUMBER if you only want a single row per order id.
So for your test data:
CREATE TABLE ORDERS ( id ) AS
SELECT 1 FROM DUAL UNION ALL
SELECT 2 FROM DUAL UNION ALL
SELECT 3 FROM DUAL;
CREATE TABLE TRANSPORTATION ( order_id, id, "DATE", status ) AS
SELECT 001, 001, DATE '2001-01-19', 'ok' FROM DUAL UNION ALL
SELECT 001, 002, DATE '2001-01-20', 'ca' FROM DUAL UNION ALL
SELECT 002, 003, DATE '2001-01-19', 'ca' FROM DUAL UNION ALL
SELECT 002, 004, DATE '2001-01-18', 'ok' FROM DUAL UNION ALL
SELECT 003, 005, DATE '2001-01-17', 'ok' FROM DUAL UNION ALL
SELECT 003, 006, DATE '2001-01-16', 'ca' FROM DUAL;
This outputs:
ORDER_ID | TRANSPORTATION_ID | DATE | STATUS
-------: | ----------------: | :------------------ | :-----
3 | 5 | 2001-01-17 00:00:00 | ok
db<>fiddle here
DO it with not exists
select ord.* from orders ord join transportation tr on ord.id = tr.order_id
where tr.status = 'OK'
and not exists(select 1 from transportation b where b.status = 'OK' and tr.date > b.date)

Find difference between group of fields if the value of group 1 is higher than value of group 2

I have a table "Components" with the following fields
Person ID Date_from CompType Value
000001 01/01/2003 A1 100
000001 01/01/2003 B1 200
000001 01/01/2003 C1 150
000001 01/01/2003 D1 180
000001 01/01/2003 E1 185
000001 01/01/2002 A1 125
000001 01/01/2002 B1 020
000001 01/01/2002 C1 130
000001 01/01/2002 D1 160
000001 01/01/2002 E1 105
000001 01/01/2001 A1 090
000001 01/01/2001 B1 200
000001 01/01/2001 C1 250
000001 01/01/2001 D1 160
000001 01/01/2001 E1 185
I need to find the difference between sum of (A1+B1+C1+D1) as S1 from the max DATE_FROM (01/01/2003) row and sum of (A1+B1+C1+D1) as S2 for the next date where S1-S2<>0
Similarly, I need to find the difference between sum of (A1+B1+C1+D1) as S2 from previous rows and sum of (A1+B1+C1+D1) as S3 for the next date where , S3-S2<>0
So , my output would be
ID Date Current Difference Previous Difference
000001 01/01/2003 195 245
Also, if I do not find any difference in 01/01/2003 and 01/01/2002 data. The SQL should look into difference of 01/01/2003 and 01/01/2001 data sets.
Your question is a little hard to follow. I think the following basically gets the data that you want -- but this returns three rows rather than 1.
select personId, date_from,
sum(case when comptype in ('A1', 'B1', 'C1', 'D1') then value end) as current_sum,
lag(sum(case when comptype in ('A1', 'B1', 'C1', 'D1') then value end)) over (partition by personId order by min(date_from) as prev_sum,
lag(sum(case when comptype in ('A1', 'B1', 'C1', 'D1') then value end), 2) over (partition by personId order by min(date_from) as prev_sum,
from t
group by personId, date_from
order by personId, date_from desc;
In Oracle 12C, you can add fetch first 1 row only to get the top row.
You can use lag() function with aggregation and abs() at the main query as
with Components(Person_ID, Date_from, CompType, Value) as
(
select '000001',date'2003-01-01','A1',100 from dual union all
select '000001',date'2003-01-01','B1',200 from dual union all
select '000001',date'2003-01-01','C1',150 from dual union all
select '000001',date'2003-01-01','D1',180 from dual union all
select '000001',date'2003-01-01','E1',185 from dual union all
select '000001',date'2002-01-01','A1',125 from dual union all
select '000001',date'2002-01-01','B1',20 from dual union all
select '000001',date'2002-01-01','C1',130 from dual union all
select '000001',date'2002-01-01','D1',160 from dual union all
select '000001',date'2002-01-01','E1',105 from dual union all
select '000001',date'2001-01-01','A1',90 from dual union all
select '000001',date'2001-01-01','B1',200 from dual union all
select '000001',date'2001-01-01','C1',250 from dual union all
select '000001',date'2001-01-01','D1',160 from dual union all
select '000001',date'2001-01-01','E1',185 from dual
), t2 as
(
select Person_ID, Date_from, sum(Value) as sum1,
lag(sum(Value),1,0) over (order by Date_from) as sum2,
lag(sum(Value),2,0) over (order by Date_from) as sum3
from Components
where CompType != 'E1'
group by Person_ID, date_from
)
select Person_ID, date_from,
abs(sum1-sum2) as "Current Difference",
abs(sum2-sum3) as "Previous Difference"
from t2
where sum1 * sum2 * sum3 > 0;
PERSON_ID DATE_FROM Current Difference Previous Difference
000001 01.01.2003 195 265
Demo

Finding missing dates in a sequence

I have following table with ID and DATE
ID DATE
123 7/1/2015
123 6/1/2015
123 5/1/2015
123 4/1/2015
123 9/1/2014
123 8/1/2014
123 7/1/2014
123 6/1/2014
456 11/1/2014
456 10/1/2014
456 9/1/2014
456 8/1/2014
456 5/1/2014
456 4/1/2014
456 3/1/2014
789 9/1/2014
789 8/1/2014
789 7/1/2014
789 6/1/2014
789 5/1/2014
789 4/1/2014
789 3/1/2014
In this table, I have three customer ids, 123, 456, 789 and date column which shows which month they worked.
I want to find out which of the customers have gap in their work.
Our customers work record is kept per month...so, dates are monthly..
and each customer have different start and end dates.
Expected results:
ID First_Absent_date
123 10/01/2014
456 06/01/2014
To get a simple list of the IDs with gaps, with no further details, you need to look at each ID separately, and as #mikey suggested you can count the number of months and look at the first and last date to see if how many months that spans.
If your table has a column called month (since date isn't allowed unless it's a quoted identifier) you could start with:
select id, count(month), min(month), max(month),
months_between(max(month), min(month)) + 1 as diff
from your_table
group by id
order by id;
ID COUNT(MONTH) MIN(MONTH) MAX(MONTH) DIFF
---------- ------------ ---------- ---------- ----------
123 8 01-JUN-14 01-JUL-15 14
456 7 01-MAR-14 01-NOV-14 9
789 7 01-MAR-14 01-SEP-14 7
Then compare the count with the month span, in a having clause:
select id
from your_table
group by id
having count(month) != months_between(max(month), min(month)) + 1
order by id;
ID
----------
123
456
If you can actually have multiple records in a month for an ID, and/or the date recorded might not be the start of the month, you can do a bit more work to normalise the dates:
select id,
count(distinct trunc(month, 'MM')),
min(trunc(month, 'MM')),
max(trunc(month, 'MM')),
months_between(max(trunc(month, 'MM')), min(trunc(month, 'MM'))) + 1 as diff
from your_table
group by id
order by id;
select id
from your_table
group by id
having count(distinct trunc(month, 'MM')) !=
months_between(max(trunc(month, 'MM')), min(trunc(month, 'MM'))) + 1
order by id;
Oracle Setup:
CREATE TABLE your_table ( ID, "DATE" ) AS
SELECT 123, DATE '2015-07-01' FROM DUAL UNION ALL
SELECT 123, DATE '2015-06-01' FROM DUAL UNION ALL
SELECT 123, DATE '2015-05-01' FROM DUAL UNION ALL
SELECT 123, DATE '2015-04-01' FROM DUAL UNION ALL
SELECT 123, DATE '2014-09-01' FROM DUAL UNION ALL
SELECT 123, DATE '2014-08-01' FROM DUAL UNION ALL
SELECT 123, DATE '2014-07-01' FROM DUAL UNION ALL
SELECT 123, DATE '2014-06-01' FROM DUAL UNION ALL
SELECT 456, DATE '2014-11-01' FROM DUAL UNION ALL
SELECT 456, DATE '2014-10-01' FROM DUAL UNION ALL
SELECT 456, DATE '2014-09-01' FROM DUAL UNION ALL
SELECT 456, DATE '2014-08-01' FROM DUAL UNION ALL
SELECT 456, DATE '2014-05-01' FROM DUAL UNION ALL
SELECT 456, DATE '2014-04-01' FROM DUAL UNION ALL
SELECT 456, DATE '2014-03-01' FROM DUAL UNION ALL
SELECT 789, DATE '2014-09-01' FROM DUAL UNION ALL
SELECT 789, DATE '2014-08-01' FROM DUAL UNION ALL
SELECT 789, DATE '2014-07-01' FROM DUAL UNION ALL
SELECT 789, DATE '2014-06-01' FROM DUAL UNION ALL
SELECT 789, DATE '2014-05-01' FROM DUAL UNION ALL
SELECT 789, DATE '2014-04-01' FROM DUAL UNION ALL
SELECT 789, DATE '2014-03-01' FROM DUAL;
Query:
SELECT ID,
MIN( missing_date )
FROM (
SELECT ID,
CASE WHEN LEAD( "DATE" ) OVER ( PARTITION BY ID ORDER BY "DATE" )
= ADD_MONTHS( "DATE", 1 ) THEN NULL
WHEN LEAD( "DATE" ) OVER ( PARTITION BY ID ORDER BY "DATE" )
IS NULL THEN NULL
ELSE ADD_MONTHS( "DATE", 1 )
END AS missing_date
FROM your_table
)
GROUP BY ID
HAVING COUNT( missing_date ) > 0;
Output:
ID MIN(MISSING_DATE)
---------- -------------------
123 2014-10-01 00:00:00
456 2014-06-01 00:00:00
You could use a Lag() function to see if records have been skipped for a particular date or not.Lag() basically helps in comparing the data in current row with previous row. So if we order by DATE, we could easily compare and find any gaps.
select * from
(
select ID,DATE_, case when DATE_DIFF>1 then 1 else 0 end comparison from
(
select ID, DATE_ ,DATE_-LAG(DATE_, 1) OVER (PARTITION BY ID ORDER BY DATE_) date_diff from trial
)
)
where comparison=1 order by ID,DATE_;
This groups all the entries by id, and then arranges the records by date. If a customer is always present, there would not be a gap in his date. So anyone who has a date difference greater than 1 had a gap. You could tweak this as per your requirement.
EDIT : Just observed that you are storing data in mm/dd/yyyy format, when I closely observed above answers.You are storing only first date of every month. So, the above query can be tweaked as :
select * from
(
select ID,DATE_,PREV_DATE,last_day(PREV_DATE)+1 ABSENT_DATE, case when DATE_DIFF>31 then 1 else 0 end comparison from
(
select ID, DATE_ ,LAG(DATE_,1) OVER (PARTITION BY ID ORDER BY DATE_) PREV_DATE,DATE_-LAG(DATE_, 1) OVER (PARTITION BY ID ORDER BY DATE_) date_diff from trial
)
)
where comparison=1 order by ID,DATE_;

Selecting a single ID by the most recent date Order Date -SQL

I have a table that looks like this
Indvdl_Store_ID Indvdl_ID Order_ID Order_Date
101 123 A000 12/24/2011
101 241 B002 01/01/2013
101 201 Y180 01/01/2016
Since we have the same Indvdl_Store_ID associated with 3 different Indvdl_IDs, I want to select/keep the most recent Individual ID for that Indvdl_StoreID based on the order date, but still keep all of the orders associated to the Indvdl_Store_ID. So I would like my final results to look like this
Indvdl_Store_ID Indvdl_ID Order_ID Order_Date
101 201 A000 12/24/2011
101 201 B002 01/01/2013
101 201 Y180 01/01/2016
I have tried using row_number to dedupe and then joining the final results back to the table on Indvdl_store_ID, but I still seem to be having Issues getting the correct results. I would appreciate any help or suggestions.
Thanks in Advance!
Oracle Setup:
CREATE TABLE table_name (Indvdl_Store_ID, Indvdl_ID, Order_ID, Order_Date ) AS
SELECT 101, 123, 'A000', DATE '2011-12-24' FROM DUAL UNION ALL
SELECT 101, 241, 'B002', DATE '2013-01-01' FROM DUAL UNION ALL
SELECT 101, 201, 'Y180', DATE '2016-01-01' FROM DUAL;
Query:
SELECT Indvdl_Store_ID,
MAX( Indvdl_ID ) KEEP ( DENSE_RANK LAST ORDER BY ORDER_DATE )
OVER ( PARTITION BY INDVDL_STORE_ID )
AS Indvdl_ID,
Order_ID,
Order_Date
FROM table_name;
Output:
INDVDL_STORE_ID INDVDL_ID ORDER_ID ORDER_DATE
--------------- ---------- -------- -------------------
101 201 A000 2011-12-24 00:00:00
101 201 Y180 2016-01-01 00:00:00
101 201 B002 2013-01-01 00:00:00
Could be with an inner join and group by
select b.Order_Date, max(a.Indvdl_Store_ID), max( a.Indvdl_ID), b.Order_ID
from my_table as b
inner join my_table as a on a.Indvdl_ID = b.Indvdl_ID
group by b.Order_Date