SQL SELECT multiple max values for a date - sql

I am trying to get the latest record from a table by filtering a column with max, but it looks like I have multiple records for the same max date. I have added an ORDER BY at the end of the query and looks like I can actually retrieve the latest value since it has a correct order, but i don't know how.
Below you will find a descriptive image:
Also please find below the query that I am using:
SELECT *
FROM item_forecast_detail
WHERE item_id = 177010 AND
forecast_dt = (SELECT MAX(forecast_dt)
FROM item_forecast_detail
WHERE item_id = 177010)
ORDER BY forecast_dt DESC

One option is to apply another condition, e.g. fetch the latest starting_hour:
SQL> with item_forecast_Detail (item_id, forecast_dt, starting_hour) as
2 (select 177010, date '2019-07-07', 21 from dual union all
3 select 177010, date '2019-07-07', 18 from dual union all
4 select 177010, date '2019-07-07', 15 from dual union all
5 select 177010, date '2019-07-07', 12 from dual union all
6 --
7 select 123456, date '2019-02-17', 09 from dual
8 )
9 select *
10 from item_forecast_Detail i
11 where i.item_id = 177010
12 and i.forecast_dt = (select max(i1.forecast_dt)
13 from item_forecast_detail i1
14 where i1.item_id = i.item_id
15 )
16 and i.starting_hour = (select max(i2.starting_hour)
17 from item_forecast_detail i2
18 where i2.item_id = i.item_id
19 );
ITEM_ID FORECAST_D STARTING_HOUR
---------- ---------- -------------
177010 07.07.2019 21
SQL>
Another one is to sort them by using analytical function and apply it to the final query:
SQL> with item_forecast_Detail (item_id, forecast_dt, starting_hour) as
2 (select 177010, date '2019-07-07', 21 from dual union all
3 select 177010, date '2019-07-07', 18 from dual union all
4 select 177010, date '2019-07-07', 15 from dual union all
5 select 177010, date '2019-07-07', 12 from dual union all
6 --
7 select 123456, date '2019-02-17', 09 from dual
8 ),
9 sort as
10 (select i.*,
11 row_number() over (partition by item_id order by forecast_dt, starting_hour desc) rn
12 from item_forecast_Detail i
13 )
14 select *
15 from sort s
16 where s.item_id = 177010
17 and s.rn = 1;
ITEM_ID FORECAST_D STARTING_HOUR RN
---------- ---------- ------------- ----------
177010 07.07.2019 21 1
SQL>

Use row_number() window analytic function to order as desired, and max(forecast_dt) over (order by forecast_dt desc) to detect the latest date
SELECT *
FROM
(
SELECT d.*,
row_number() over (order by ending_hour desc) as rn,
max(forecast_dt) over (order by forecast_dt desc) as mx
FROM item_forecast_detail d
WHERE item_id=177010
)
WHERE mx = forecast_dt
ORDER BY rn

You could try this with MySQL, SQLServer:
SELECT * FROM TABLE ORDER BY FORECAST_DT DESC LIMIT 1
SELECT TOP 1 * FROM Table ORDER BY FORECAST_DT DESC

You can use ROWNUM.
-- FETCH MAX FOR SINGLE item_id
WITH item_forecast_detail(item_id, forecast_dt, SOME_OTHER_COLS) AS (
SELECT 177010, DATE '2019-07-01','ANY VALUE - 1' FROM DUAL UNION ALL
SELECT 177010, DATE '2019-07-01','ANY VALUE - 2' FROM DUAL UNION ALL
SELECT 177010, DATE '2019-07-02','ANY VALUE - 4' FROM DUAL UNION ALL
SELECT 177010, DATE '2019-07-02','ANY VALUE - 5' FROM DUAL UNION ALL
SELECT 177010, DATE '2019-07-02','ANY VALUE - 6' FROM DUAL UNION ALL
SELECT 177011, DATE '2019-07-01','ANY VALUE - 1' FROM DUAL UNION ALL
SELECT 177011, DATE '2019-07-01','ANY VALUE - 1' FROM DUAL UNION ALL
SELECT 177011, DATE '2019-06-30','ANY VALUE - 1' FROM DUAL
)
SELECT
ITEM_ID,
FORECAST_DT,
SOME_OTHER_COLS
FROM
(
SELECT
ITEM_ID,
FORECAST_DT,
SOME_OTHER_COLS,
ROW_NUMBER() OVER(
PARTITION BY ITEM_ID
ORDER BY
FORECAST_DT DESC
) AS RN
FROM
ITEM_FORECAST_DETAIL
WHERE
ITEM_ID = 177010
)
WHERE
RN = 1
OUTPUT
-- FETCH MAX FOR MULTIPLE item_id
WITH item_forecast_detail(item_id, forecast_dt, SOME_OTHER_COLS) AS (
SELECT 177010, DATE '2019-07-01','ANY VALUE - 1' FROM DUAL UNION ALL
SELECT 177010, DATE '2019-07-01','ANY VALUE - 2' FROM DUAL UNION ALL
SELECT 177010, DATE '2019-07-02','ANY VALUE - 4' FROM DUAL UNION ALL
SELECT 177010, DATE '2019-07-02','ANY VALUE - 5' FROM DUAL UNION ALL
SELECT 177010, DATE '2019-07-02','ANY VALUE - 6' FROM DUAL UNION ALL
SELECT 177011, DATE '2019-07-01','ANY VALUE - 1' FROM DUAL UNION ALL
SELECT 177011, DATE '2019-07-01','ANY VALUE - 1' FROM DUAL UNION ALL
SELECT 177011, DATE '2019-06-30','ANY VALUE - 1' FROM DUAL
)
SELECT
ITEM_ID,
FORECAST_DT,
SOME_OTHER_COLS
FROM
(
SELECT
ITEM_ID,
FORECAST_DT,
SOME_OTHER_COLS,
ROW_NUMBER() OVER(
PARTITION BY ITEM_ID
ORDER BY
FORECAST_DT DESC
) AS RN
FROM
ITEM_FORECAST_DETAIL
--WHERE item_id = 177010
)
WHERE
RN = 1
OUTPUT
DB<>FIDDLE DEMO
Cheers!!

Related

create date range from day based data

i have following source data...
id date value
1 01.08.22 a
1 02.08.22 a
1 03.08.22 a
1 04.08.22 b
1 05.08.22 b
1 06.08.22 a
1 07.08.22 a
2 01.08.22 a
2 02.08.22 a
2 03.08.22 c
2 04.08.22 a
2 05.08.22 a
and i would like to have the following output...
id date_from date_until value
1 01.08.22 03.08.22 a
1 04.08.22 05.08.22 b
1 06.08.22 07.08.22 a
2 01.08.22 02.08.22 a
2 03.08.22 03.08.22 c
2 04.08.22 05.08.22 a
Is this possible with Oracle SQL? Which functions do I need for this?
Based on the link provided by #astentx, try this solution:
SELECT
id, MIN("date") AS date_from, MAX("date") AS date_until, MAX(value) AS value
FROM (
SELECT
t1.*,
ROW_NUMBER() OVER(PARTITION BY id ORDER BY "date") -
ROW_NUMBER() OVER(PARTITION BY id, value ORDER BY "date") AS rn
FROM yourtable t1
)
GROUP BY id, rn
See db<>fiddle
WITH CTE (id, dateD,valueD)
AS
(
SELECT 1, TO_DATE('01.08.22','DD.MM.YY'), 'a' FROM DUAL UNION ALL
SELECT 1, TO_DATE('02.08.22','DD.MM.YY'), 'a'FROM DUAL UNION ALL
SELECT 1, TO_DATE('03.08.22','DD.MM.YY'), 'a'FROM DUAL UNION ALL
SELECT 1, TO_DATE('04.08.22','DD.MM.YY'), 'b'FROM DUAL UNION ALL
SELECT 1, TO_DATE('05.08.22','DD.MM.YY'), 'b'FROM DUAL UNION ALL
SELECT 2, TO_DATE('01.08.22','DD.MM.YY'), 'a'FROM DUAL UNION ALL
SELECT 2, TO_DATE('02.08.22','DD.MM.YY'), 'a'FROM DUAL UNION ALL
SELECT 2, TO_DATE('03.08.22','DD.MM.YY'), 'c'FROM DUAL
)
SELECT C.ID,C.VALUED,MIN(C.DATED)AS MIN_DATE,MAX(C.DATED)AS MAX_DATE
FROM CTE C
GROUP BY C.ID,C.VALUED
ORDER BY C.ID
https://dbfiddle.uk/?rdbms=oracle_18&fiddle=47c87d60445ce262cd371177e31d5d63

Oracle SQL row concatenation by periods: maximum period

I have the below table:
LAUFD
ID
NEXDT
ORDER_ROW
20140305
C1
20140310
14
20140226
C1
20140305
13
20131125
C1
20131126
12
20131021
C1
20131022
11
20130821
C1
20130828
10
20130814
C1
20130821
9
20130807
C1
20130814
8
20130731
C1
20130807
7
20130724
C1
20130731
6
20130710
C1
20130724
5
20130708
C1
20130709
4
20130624
C1
20130707
3
20130603
C1
20130608
2
20130527
C1
20130603
1
I would like to have the below output:
ID
START
END
C1
20140226
20140310
The logic is: if, ordering ID by order_row, the field NEXDT is equal or equal+1 or equal+2 to the field LAUFD of the next order_row, then continue with the next entry. If not, generate an entry in the output table with the start (earliest LAUFD) and end (latest NEXDT).
Basically, it's the same question as in Oracle SQL row concatenation by periods but I'd like just the latest period as an output.
Looks like this is what you need:
with t (LAUFD, ID, NEXDT, ORDER_ROW) as (
select 20140305,'C1', 20140310, 14 from dual union all
select 20140226,'C1', 20140305, 13 from dual union all
select 20131125,'C1', 20131126, 12 from dual union all
select 20131021,'C1', 20131022, 11 from dual union all
select 20130821,'C1', 20130828, 10 from dual union all
select 20130814,'C1', 20130821, 9 from dual union all
select 20130807,'C1', 20130814, 8 from dual union all
select 20130731,'C1', 20130807, 7 from dual union all
select 20130724,'C1', 20130731, 6 from dual union all
select 20130710,'C1', 20130724, 5 from dual union all
select 20130708,'C1', 20130709, 4 from dual union all
select 20130624,'C1', 20130707, 3 from dual union all
select 20130603,'C1', 20130608, 2 from dual union all
select 20130527,'C1', 20130603, 1 from dual
)
,t1 as (select id, order_row, to_date(laufd,'yyyymmdd') as laufd_dt, to_date(nexdt,'yyyymmdd') as nexdt_dt from t)
select *
from t1
match_recognize (
partition by id
order by order_row desc
measures
min(x.laufd_dt) as dt_start,
max(a.nexdt_dt) as dt_end,
x.laufd_dt-next(x.nexdt_dt) as dates_diff
one row per match
pattern(a x+ y* z*)
define
x as x.order_row=prev(order_row)-1 and prev(laufd_dt)-nexdt_dt<=3
,y as x.order_row=prev(order_row)-1
);
For just the latest period, you could use the previous solution. But instead, look for the first "break". Then only use the rows since that break;
select id, min(laufd), max(nextdt),
row_number() over (partition by id order by min(laufd)) as period
from (select t.*,
sum(case when prev_nextdt >= laufd - interval '2' day then 0 else 1 end) over
(partition by id order by order_row range desc) as grp,
sum(case when prev_nextdt >= laufd - interval '2' day then 0 else 1 end) over (partition by id) as num_grps
from (select t.id, t.order_row, -- any other columns you need
to_date(laufd, 'YYYYMMDD') as laufd,
to_date(nextdt, 'YYYYMMDD') as next_dt,
lag(to_date(nextdt, 'YYYYMMDD')) over (partition by id order by order_row) as prev_nextdt
from t
) t
) t
where num_grps = grp
group by id;
This is basically the same logic. It just keeps the first group.

To find the starting and ending points in a sequence

I've a table (T1) with one column (C1) with the below values
1
2
3
5
6
8
9
10
I want the output to print the continuous sequences with start and ending points like below.
1-3
5-6
8-10
Could you please help?
Any Database is fine.
Oracle: sample data first, while code you really need begins at line #11.
SQL> with t1 (c1) as
2 (select 1 from dual union all
3 select 2 from dual union all
4 select 3 from dual union all
5 select 5 from dual union all
6 select 6 from dual union all
7 select 8 from dual union all
8 select 9 from dual union all
9 select 10 from dual
10 )
11 select min(c1), max(c1)
12 from (select c1, c1 - row_number() over (order by c1) rn
13 from t1
14 )
15 group by rn
16 order by rn;
MIN(C1) MAX(C1)
---------- ----------
1 3
5 6
8 10
SQL>
You can use the following query. I have tested it with SQL Server, but I think it will work without modifications in Oracle:
create table t1(c1 int);
insert into t1
select *
from (values(1),(2),(3),(5),(6),(8),(9),(10))t(x);
select case when count(*) >1 then
concat(min(c1),'-',max(c1))
else concat(max(c1),'')
end as concat_cs
from (
select c1
,ROW_NUMBER() over(order by c1 asc) as rnk
,c1 - ROW_NUMBER() over(order by c1 asc) as grp
from t1
)x
group by x.grp
Output
concat_cs
1-3
5-6
8-10
with stab as (
select 1 as val from dual union all
select 2 as val from dual union all
select 3 as val from dual union all
select 5 as val from dual union all
select 6 as val from dual union all
select 8 as val from dual union all
select 9 as val from dual union all
select 10 as val from dual union all
select 13 as val from dual union all
select 15 as val from dual union all
select 16 as val from dual union all
select 17 as val from dual union all
select 18 as val from dual union all
select 19 as val from dual union all
select 23 as val from dual
),sq2 as(
select
row_number() over(order by 1) as rownumber,val
from stab
)
select
a.val,b.val
from sq2 A
join sq2 b on b.rownumber = a.rownumber+2
where mod(A.rownumber,3)=1
Output:
1 3
5 8
9 13
15 17
18 23

SQL Grouping by Ranges

I have a data set that has timestamped entries over various sets of groups.
Timestamp -- Group -- Value
---------------------------
1 -- A -- 10
2 -- A -- 20
3 -- B -- 15
4 -- B -- 25
5 -- C -- 5
6 -- A -- 5
7 -- A -- 10
I want to sum these values by the Group field, but parsed as it appears in the data. For example, the above data would result in the following output:
Group -- Sum
A -- 30
B -- 40
C -- 5
A -- 15
I do not want this, which is all I've been able to come up with on my own so far:
Group -- Sum
A -- 45
B -- 40
C -- 5
Using Oracle 11g, this is what I've hobbled togther so far. I know that this is wrong, by I'm hoping I'm at least on the right track with RANK(). In the real data, entries with the same group could be 2 timestamps apart, or 100; there could be one entry in a group, or 100 consecutive. It does not matter, I need them separated.
WITH SUB_Q AS
(SELECT K_ID
, GRP
, VAL
-- GET THE RANK FROM TIMESTAMP TO SEPARATE GROUPS WITH SAME NAME
, RANK() OVER(PARTITION BY K_ID ORDER BY TMSTAMP) AS RNK
FROM MY_TABLE
WHERE K_ID = 123)
SELECT T1.K_ID
, T1.GRP
, SUM(CASE
WHEN T1.GRP = T2.GRP THEN
T1.VAL
ELSE
0
END) AS TOTAL_VALUE
FROM SUB_Q T1 -- MAIN VALUE
INNER JOIN SUB_Q T2 -- TIMSTAMP AFTER
ON T1.K_ID = T2.K_ID
AND T1.RNK = T2.RNK - 1
GROUP BY T1.K_ID
, T1.GRP
Is it possible to group in this way? How would I go about doing this?
I approach this problem by defining a group which is the different of two row_number():
select group, sum(value)
from (select t.*,
(row_number() over (order by timestamp) -
row_number() over (partition by group order by timestamp)
) as grp
from my_table t
) t
group by group, grp
order by min(timestamp);
The difference of two row numbers is constant for adjacent values.
A solution using LAG and windowed analytic functions:
SQL Fiddle
Oracle 11g R2 Schema Setup:
CREATE TABLE TEST ( "Timestamp", "Group", Value ) AS
SELECT 1, 'A', 10 FROM DUAL
UNION ALL SELECT 2, 'A', 20 FROM DUAL
UNION ALL SELECT 3, 'B', 15 FROM DUAL
UNION ALL SELECT 4, 'B', 25 FROM DUAL
UNION ALL SELECT 5, 'C', 5 FROM DUAL
UNION ALL SELECT 6, 'A', 5 FROM DUAL
UNION ALL SELECT 7, 'A', 10 FROM DUAL;
Query 1:
WITH changes AS (
SELECT t.*,
CASE WHEN LAG( "Group" ) OVER ( ORDER BY "Timestamp" ) = "Group" THEN 0 ELSE 1 END AS hasChangedGroup
FROM TEST t
),
groups AS (
SELECT "Group",
VALUE,
SUM( hasChangedGroup ) OVER ( ORDER BY "Timestamp" ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW ) AS grp
FROM changes
)
SELECT "Group",
SUM( VALUE )
FROM Groups
GROUP BY "Group", grp
ORDER BY grp
Results:
| Group | SUM(VALUE) |
|-------|------------|
| A | 30 |
| B | 40 |
| C | 5 |
| A | 15 |
This is typical "star_of_group" problem (see here: https://timurakhmadeev.wordpress.com/2013/07/21/start_of_group/)
In your case, it would be as follows:
with t as (
select 1 timestamp, 'A' grp, 10 value from dual union all
select 2, 'A', 20 from dual union all
select 3, 'B', 15 from dual union all
select 4, 'B', 25 from dual union all
select 5, 'C', 5 from dual union all
select 6, 'A', 5 from dual union all
select 7, 'A', 10 from dual
)
select min(timestamp), grp, sum(value) sum_value
from (
select t.*
, sum(start_of_group) over (order by timestamp) grp_id
from (
select t.*
, case when grp = lag(grp) over (order by timestamp) then 0 else 1 end
start_of_group
from t
) t
)
group by grp_id, grp
order by min(timestamp)
;

SQL - display a row if the parameter exists , else display another parameter if exists

I have a table like the following :
ID |INFO | DATE_DT
-------------------------
1091|info5 |10/10/2010
1239|old.info |14/09/2010
1340|old.info |07/10/2010
3481|info |16/10/2010
4134|info3 |21/01/2011
i would like to display just one row with the following conditions :
- if i have in my table one row with INFO = 'info' --> display just this row
- if i dont have one row with INFO = 'info' so i --> display the row with INFO = 'old.info' and DATE_DT = MAX(DATE_DT)
so, in my example if my table is :
ID |INFO | DATE_DT
-------------------------
1091|info5 |10/10/2010
1239|old.info |14/09/2010
1340|old.info |07/10/2010
3481|info |16/10/2010 ===> display this row
4134|info3 |21/01/2011
or if my table doesnt containt INFO = 'info'
ID |INFO | DATE_DT
-------------------------
1091|info5 |10/10/2010
1239|old.info |14/09/2010
1340|old.info |07/10/2010 ===> display this row
4134|info3 |21/01/2011
any suggestions?
Thanks.
You could select both row and take by priority the one that satisfies the first condition:
SQL> WITH my_table AS (
2 SELECT 1091 id, 'info5' info, to_date('10/10/2010') date_dt FROM DUAL
3 UNION ALL SELECT 1239, 'old.info' , to_date('14/09/2010') FROM DUAL
4 UNION ALL SELECT 1340, 'old.info' , to_date('07/10/2010') FROM DUAL
5 UNION ALL SELECT 3481, 'info' , to_date('16/10/2010') FROM DUAL
6 UNION ALL SELECT 4134, 'info3' , to_date('21/01/2011') FROM DUAL)
7 SELECT * FROM (
8 SELECT 1 ord, t.*
9 FROM my_table t
10 WHERE info = 'info'
11 UNION ALL
12 SELECT 2 ord, t.*
13 FROM my_table t
14 WHERE date_dt = (SELECT MAX(date_dt) FROM my_table)
15 ORDER BY ord)
16 WHERE ROWNUM = 1;
ORD ID INFO DATE_DT
---------- ---------- -------- -----------
1 3481 info 16/10/2010
If you remove the row 'info', the row where DATE_DT = MAX(DATE_DT) will be chosen:
SQL> WITH my_table AS (
2 SELECT 1091 id, 'info5' info, to_date('10/10/2010') date_dt FROM DUAL
3 UNION ALL SELECT 1239, 'old.info' , to_date('14/09/2010') FROM DUAL
4 UNION ALL SELECT 1340, 'old.info' , to_date('07/10/2010') FROM DUAL
5 /*UNION ALL SELECT 3481, 'info' , to_date('16/10/2010') FROM DUAL*/
6 UNION ALL SELECT 4134, 'info3' , to_date('21/01/2011') FROM DUAL)
7 SELECT * FROM (
8 SELECT 1 ord, t.*
9 FROM my_table t
10 WHERE info = 'info'
11 UNION ALL
12 SELECT 2 ord, t.*
13 FROM my_table t
14 WHERE date_dt = (SELECT MAX(date_dt) FROM my_table)
15 ORDER BY ord)
16 WHERE ROWNUM = 1;
ORD ID INFO DATE_DT
---------- ---------- -------- -----------
2 4134 info3 21/01/2011
You can also do this with analytic functions, to only require one pass over the data:
with my_tab as (
select 1091 as id, 'info5' as info, to_date('10/10/2010',' DD/MM/YYYY') as date_dt from dual
union all select 1239, 'old.info', to_date('14/09/2010', 'DD/MM/YYYY') from dual
union all select 1340, 'old.info', to_date('07/10/2010', 'DD/MM/YYYY') from dual
union all select 3481, 'info', to_date('16/10/2010', 'DD/MM/YYYY') from dual
union all select 4134, 'info3', to_date('21/01/2011', 'DD/MM/YYYY') from dual
)
select id, info, to_char(date_dt, 'DD/MM/YYYY')
from (
select id, info, date_dt, rank() over (order by ord, date_dt desc) as rnk
from (
select id, info, date_dt,
case info
when 'info' then 1
when 'old.info' then 2
when 'info3' then 3
else null
end as ord
from my_tab
)
)
where rnk = 1;
ID INFO DATE_DT
---------- -------- ----------
3481 info 16/10/2010
Knocking out the 'info' row gives:
ID INFO DATE_DT
---------- -------- ----------
1340 old.info 07/10/2010
Probably no better than #Vincent's for this trivial case, but with more data and more values to choose between this might scale better - just need more ord values in the case, though with any real data I'd have thought you'd look up the precedence from another table...