Selecting a single ID by the most recent date Order Date -SQL - 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

Related

Oracle SQL - Find origin ID of autoincrement column

There's a table on my ERP database that has data about certain events. It has the start date, end date and a column shows if the event is a continuation of a previous one (sequential_id references unique_id). Here's an example:
unique_id
start_date
end_date
sequential_id
001
2021-01-01
2021-01-15
002
2021-02-01
2021-02-16
001
003
2021-03-01
2021-03-17
002
004
2021-03-10
2021-03-11
005
2021-03-19
In the example above, rows 001, 002 and 003 are all part of the same event, and 004/005 are unique events, with no sequences. How can I group the data in a way that the output is like this:
origin_id
start_date
end_date
001
2021-01-01
2021-03-17
004
2021-03-10
2021-03-11
005
2021-03-19
I've tried using group by, but due to sequential_id being auto incremental, it didn't work.
Thanks in advance.
You can use modern match_recognize which is an optimal solution for such tasks:
Pattern Recognition With MATCH_RECOGNIZE
DBFiddle
select *
from t
match_recognize(
measures
first(unique_id) start_unique_id,
first(start_date) start_date,
last(end_date) end_date
pattern (strt nxt*)
define nxt as sequential_id=prev(unique_id)
);
You can use hierarchical query for this:
with a (unique_id, start_date, end_date, sequential_id) as (
select '001', date '2021-01-01', date '2021-01-15', null from dual union all
select '002', date '2021-02-01', date '2021-02-16', '001' from dual union all
select '003', date '2021-03-01', date '2021-03-17', '002' from dual union all
select '004', date '2021-03-10', date '2021-03-11', null from dual union all
select '005', date '2021-03-19', null, null from dual
)
, b as (
select
connect_by_root(unique_id) as unique_id
, connect_by_root(start_date) as start_date
, end_date
, connect_by_isleaf as l
from a
start with sequential_id is null
connect by prior unique_id = sequential_id
)
select
unique_id
, start_date
, end_date
from b
where l = 1
order by 1 asc
UNIQUE_ID | START_DATE | END_DATE
:-------- | :--------- | :--------
001 | 01-JAN-21 | 17-MAR-21
004 | 10-MAR-21 | 11-MAR-21
005 | 19-MAR-21 | null
db<>fiddle here
This is a graph-walking problem, so you can use a recursive CTE:
with cte (unique_id, start_date, end_date, start_unique_id) as (
select unique_id, start_date, end_date, unique_id
from t
where not exists (select 1 from t t2 where t.sequential_id = t2.unique_id)
union all
select t.unique_id, t.start_date, t.end_date, cte.start_unique_id
from cte join
t
on cte.unique_id = t.sequential_id
)
select start_unique_id, min(start_date), max(end_date)
from cte
group by start_Unique_id;
Here is a db<>fiddle.

Query to aggregate data from table into another table based on number of entries of an id column

I have a table like this:
order_id start_date end_date amount corrected_amount
1 2020-01-01 2020-01-31 100 95
1 2020-02-01 2020-02-28 200 200
1 2020-03-01 2020-03-30 100 100
1 2020-10-01 2020-11-25 200 95
2 2020-01-01 2020-05-30 500 250
3 2020-01-01 2020-12-31 400 5
And I am trying to create a query to aggregate this into a smaller table with just one row per order_id and I need to sum this together using a few rules that I am having some problems implementing.
In the case where there just exists one entry like for id 2 and 3 then I want to return just the order_id start_date, end_date and value from the amount column
In the case where there exists multiple entries like for 1 then I want to return the order_id, the minimum start_date, the maximum end_date, and for every end_date that is "lower" than todays date I want to sum up the corrected_amounts and also add this to the amount where end_date is "bigger" than today.
So for the table above the result would look like
order_id start_date end_date amount
1 2020-01-01 2020-11-25 595
2 2020-01-01 2020-05-30 500
3 2020-01-01 2020-12-31 400
Consider using IF:
WITH TestData AS (
SELECT 1 as order_id, DATE('2020-01-01') as start_date, DATE('2020-01-31') as end_date, 100 as amount, 95 as corrected_amount UNION ALL
SELECT 1, DATE('2020-02-01'), DATE('2020-02-28'), 200, 200 UNION ALL
SELECT 1, DATE('2020-03-01'), DATE('2020-03-30'), 100, 100 UNION ALL
SELECT 1, DATE('2020-10-01'), DATE('2020-11-25'), 200, 95 UNION ALL
SELECT 2, DATE('2020-01-01'), DATE('2020-05-30'), 500, 250 UNION ALL
SELECT 3, DATE('2020-01-01'), DATE('2020-12-31'), 400, 5
)
SELECT order_id,
MIN(start_date) AS start_date,
MAX(end_date) AS end_date,
IF(COUNT(*) > 1,
SUM(IF(end_date < CURRENT_DATE(), corrected_amount, amount)),
SUM(amount)
) as amount
FROM TestData
GROUP BY order_id
The result is:

How can update a column based on the value of another column in SQL?

Basically I have Product table like this:
date price
--------- -----
02-SEP-14 50
03-SEP-14 60
04-SEP-14 60
05-SEP-14 60
07-SEP-14 71
08-SEP-14 45
09-SEP-14 45
10-SEP-14 24
11-SEP-14 60
I need to update the table in this form
date price id
--------- ----- --
02-SEP-14 50 1
03-SEP-14 60 2
04-SEP-14 60 2
05-SEP-14 60 2
07-SEP-14 71 3
08-SEP-14 45 4
09-SEP-14 45 4
10-SEP-14 24 5
11-SEP-14 60 6
What I have tried:
CREATE SEQUENCE user_id_seq
START WITH 1
INCREMENT BY 1
CACHE 20;
ALTER TABLE Product
ADD (ID number);
UPDATE Product SET ID = user_id_seq.nextval;
This is updating the ID in the usual way like 1,2,3,4,5..
I have no idea how to do it using basic SQL commands. Please suggest how can I make it. Thank you in advance.
Here is one way to create a view from your base data. I assume you have more than one product (identified by product id), and that the price dates aren't necessarily consecutive. The sequence is separate for each product id. (Also, product should be the name of a different table - where the product id is primary key, and you have other information such as product name, category, etc. The table in your post would be more properly called something like price_history.)
alter session set nls_date_format='dd-MON-rr';
create table product ( prod_id number, dt date, price number );
insert into product ( prod_id, dt, price )
select 101, '02-SEP-14', 50 from dual union all
select 101, '03-SEP-14', 60 from dual union all
select 101, '04-SEP-14', 60 from dual union all
select 101, '05-SEP-14', 60 from dual union all
select 101, '07-SEP-14', 71 from dual union all
select 101, '08-SEP-14', 45 from dual union all
select 101, '09-SEP-14', 45 from dual union all
select 101, '10-SEP-14', 24 from dual union all
select 101, '11-SEP-14', 60 from dual union all
select 102, '02-SEP-14', 45 from dual union all
select 102, '04-SEP-14', 45 from dual union all
select 102, '05-SEP-14', 60 from dual union all
select 102, '06-SEP-14', 50 from dual union all
select 102, '09-SEP-14', 60 from dual
;
commit;
create view product_vw ( prod_id, dt, price, seq ) as
select prod_id, dt, price,
count(flag) over (partition by prod_id order by dt)
from ( select prod_id, dt, price,
case when price = lag(price) over (partition by prod_id order by dt)
then null else 1 end as flag
from product
)
;
Now check what the view looks like:
select * from product_vw;
PROD_ID DT PRICE SEQ
------- ------------------- ---------- ----------
101 02/09/0014 00:00:00 50 1
101 03/09/0014 00:00:00 60 2
101 04/09/0014 00:00:00 60 2
101 05/09/0014 00:00:00 60 2
101 07/09/0014 00:00:00 71 3
101 08/09/0014 00:00:00 45 4
101 09/09/0014 00:00:00 45 4
101 10/09/0014 00:00:00 24 5
101 11/09/0014 00:00:00 60 6
102 02/09/0014 00:00:00 45 1
102 04/09/0014 00:00:00 45 1
102 05/09/0014 00:00:00 60 2
102 06/09/0014 00:00:00 50 3
102 09/09/0014 00:00:00 60 4
NOTE: This answers the question that was originally asked. The OP changed the data.
If your data is not too large, you can use a correlated subquery:
update product p
set id = (select count(distinct p2.price)
from product p2
where p2.date <= p.date
);
If your data is larger, then merge is more appropriate.
WITH cts AS
(
SELECT row_number() over (partition by price order by price ) as id
,date
,price
FROM Product
)
UPDATE p
set p.id = cts.id
from product p join cts on cts.id = p.id
This is the best way by which you try to do.
There is no another simple way to do this using simple statements

Return Data based Relevant Dates

I have an occupancy table and a pay history table. I want to return the state that the employee is in from the occupancy at the time of the relevant pay.
Occupancy Table
Emp#|Commence Date|State
-----|-------------|----
101 | 1/01/2016 | VIC
101 | 1/04/2016 | NSW
101 | 1/08/2016 | ACT
Pay History Table
Emp#|Pay Date
----|--------
101 |15/01/2016
101 |15/02/2016
101 |15/03/2016
101 |15/04/2016
101 |15/05/2016
101 |15/06/2016
101 |15/07/2016
101 |15/08/2016
101 |15/09/2016
I'm wanting to return the following
Emp#|:Pay Date:|State
----|----------|-----
101 |15/01/2016|VIC
101 |15/02/2016|VIC
101 |15/03/2016|VIC
101 |15/04/2016|NSW
101 |15/05/2016|NSW
101 |15/06/2016|NSW
101 |15/07/2016|NSW
101 |15/08/2016|ACT
101 |15/09/2016|ACT
Can someone assist, please
You need to generate the end_date in the occupancy table in a subquery; the lead() function is perfect for this purpose. I use it with all three arguments - the third argument gives a "default" date which I chose arbitrarily as 15 December 2099 for the "current" status. Then its a simple join on empno and a between condition on dates.
I assume you have more than one empno in your data, so I accommodated that. Then: I don't know if # is legal in Oracle column names, but I didn't want to try; I changed to empno. And names definitely can't have spaces in them unless you quote the names, which has many disadvantages; I worked around that too.
with
occupancy ( empno, commence_date, state ) as (
select 101, to_date('1/01/2016', 'dd/mm/yyyy'), 'VIC' from dual union all
select 101, to_date('1/04/2016', 'dd/mm/yyyy'), 'NSW' from dual union all
select 101, to_date('1/08/2016', 'dd/mm/yyyy'), 'ACT' from dual
),
pay_history ( empno, pay_date ) as (
select 101, to_date('15/01/2016', 'dd/mm/yyyy') from dual union all
select 101, to_date('15/02/2016', 'dd/mm/yyyy') from dual union all
select 101, to_date('15/03/2016', 'dd/mm/yyyy') from dual union all
select 101, to_date('15/04/2016', 'dd/mm/yyyy') from dual union all
select 101, to_date('15/05/2016', 'dd/mm/yyyy') from dual union all
select 101, to_date('15/06/2016', 'dd/mm/yyyy') from dual union all
select 101, to_date('15/07/2016', 'dd/mm/yyyy') from dual union all
select 101, to_date('15/08/2016', 'dd/mm/yyyy') from dual union all
select 101, to_date('15/09/2016', 'dd/mm/yyyy') from dual
)
-- end of test data (not part of the SQL query); query begins below this line
select p.empno, p.pay_date, o.state
from pay_history p inner join (
select empno, commence_date,
lead(commence_date, 1, date '2099-12-15')
over (partition by empno order by commence_date) as end_date,
state
from occupancy ) o
on p.empno = o.empno
and p.pay_date between o.commence_date and o.end_date
order by empno, pay_date -- if needed
;
Output:
EMPNO PAY_DATE STATE
----- ---------- -----
101 15/01/2016 VIC
101 15/02/2016 VIC
101 15/03/2016 VIC
101 15/04/2016 NSW
101 15/05/2016 NSW
101 15/06/2016 NSW
101 15/07/2016 NSW
101 15/08/2016 ACT
101 15/09/2016 ACT
9 rows selected.
Negotiating against myself here :-) I am posting this as a separate Answer, rather than editing my earlier one, because this is indeed a different answer.
Please refer to my other Answer for the input data and sample output - they are the same. Only the query is different. Instead of a join, we can UNION ALL the two tables (with some necessary adjustments: add a null column for state to the pay_history table, and a flag of 0 for the occupancy table and 1 for the pay_history table); then use the last_value() analytic function on the resulting union, and filter out the rows from the occupancy table in the outermost query. This may be quite a bit faster than the join-based solution.
select empno, dt as pay_date, state
from (
select empno, dt, flag,
last_value(state ignore nulls)
over (partition by empno order by dt, flag) as state
from (
select empno, commence_date as dt, state, 0 as flag
from occupancy
union all
select empno, pay_date, null, 1
from pay_history
)
)
where flag = 1
order by empno, pay_date -- if needed
;

Get most recent Price for each Item

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
;