create date range from day based data - sql

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

Related

SQL SELECT multiple max values for a date

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!!

Find rows with consecutive ones

I've two integer columns and need to display the rows with consecutive one's in the NUM column.
Sample data:
CREATE TABLE table_name ( ID, NUM ) AS
SELECT 1, 1 FROM DUAL UNION ALL
SELECT 2, 1 FROM DUAL UNION ALL
SELECT 3, 1 FROM DUAL UNION ALL
SELECT 4, 2 FROM DUAL UNION ALL
SELECT 5, 1 FROM DUAL UNION ALL
SELECT 6, 2 FROM DUAL UNION ALL
SELECT 7, 2 FROM DUAL;
Expected Output:
ID NUM
-- ---
1 1
2 1
3 1
I have tried using self-joins and achieved the result:
WITH TAB (ID, NUM) AS
(
SELECT 1, 1 FROM DUAL UNION ALL
SELECT 2, 1 FROM DUAL UNION ALL
SELECT 3, 1 FROM DUAL UNION ALL
SELECT 4, 2 FROM DUAL UNION ALL
SELECT 5, 1 FROM DUAL UNION ALL
SELECT 6, 2 FROM DUAL UNION ALL
SELECT 7, 2 FROM DUAL
)
SELECT DISTINCT
T.ID,
T.NUM
FROM
TAB T
JOIN (
SELECT
T1.ID ID1,
T2.ID ID2,
T1.NUM,
COUNT(1) OVER(
PARTITION BY T1.NUM
) RN
FROM
TAB T1
JOIN TAB T2 ON ( T1.NUM = T2.NUM
AND T1.ID = T2.ID + 1 )
) T_IN ON ( ( T.ID = T_IN.ID1
OR T.ID = T_IN.ID2 )
AND T.NUM = T_IN.NUM
AND RN >= 2 ) -- THIS CONDITION IS TO RESTRICT CONSECUTIVES LESS THAN 3
ORDER BY
1
output:
db<>fiddle demo
Use analytic functions LAG or LEAD:
Oracle Setup:
CREATE TABLE table_name ( ID, NUM ) AS
SELECT 1, 1 FROM DUAL UNION ALL
SELECT 2, 1 FROM DUAL UNION ALL
SELECT 3, 1 FROM DUAL UNION ALL
SELECT 4, 2 FROM DUAL UNION ALL
SELECT 5, 1 FROM DUAL UNION ALL
SELECT 6, 2 FROM DUAL UNION ALL
SELECT 7, 2 FROM DUAL;
Query:
SELECT id,num
FROM (
SELECT id,
num,
LAG( num ) OVER ( ORDER BY id ) AS prev_num,
LEAD( num ) OVER ( ORDER BY id ) AS next_num
FROM table_name
)
WHERE num = 1
AND ( num = prev_num
OR num = next_num )
Output:
ID | NUM
-: | --:
1 | 1
2 | 1
3 | 1
db<>fiddle here

How to select the minimum value in a table or the next one in oracle sql

I have a table L1_CI_PER_ADDRESS with these columns
PER_ID,
SEQ_NUM,
ADDRESS_ID,
ADDRESS_TYPE_XFLG,
START_DT,
END_DT,
SEASON_START_MMDD,
SEASON_END_MMDD,
ADDRESS_PRIO_FLG,
DELIVERABLE_FLG,
VERSION,
LOAD_DATE
I want to select ADDRESS_TYPE_XFLG where the value is MAIN-AE if it exists or the MAIN-EN if the first one does not exists. Else I want to select CORRESPOND-AE or CORRESPOND-AE if MAIN-AE and MAIN-EN do not exists.
How can I do this? I am new to Oracle SQL. I want to remove the duplicates returned when I do my select.
One of the issues is that some person ID's have all four (MAIN-AE, MAIN-EN, CORRESPOND-AE, CORRESPOND-EN), so in this case I just want MAIN-AE to be returned.
I hope my question is clear.
enter image description here
It's top-n query. Use row_number():
select *
from (
select PER_ID, address_id, ADDRESS_TYPE_XFLG,
row_number() over (partition by per_id
order by case ADDRESS_TYPE_XFLG
when 'MAIN-AE' then 1
when 'MAIN-EN' then 2
when 'CORRESPOND-AE' then 3
when 'CORRESPOND-EN' then 4
end) as rn
from L1_CI_PER_ADDRESS)
where rn = 1
If person can own two addresses with the same flag then you need to add proper order after case when section, probably something like , seq_num desc.
Test:
with L1_CI_PER_ADDRESS(PER_ID, address_id, ADDRESS_TYPE_XFLG ) as (
select 1, 1, 'CORRESPOND-AE' from dual union all
select 1, 2, 'MAIN-AE' from dual union all
select 1, 3, 'CORRESPOND-EN' from dual union all
select 1, 4, 'MAIN-EN' from dual union all
select 2, 5, 'CORRESPOND-AE' from dual union all
select 3, 6, 'MAIN-AE' from dual union all
select 4, 7, 'CORRESPOND-EN' from dual union all
select 4, 8, 'MAIN-AE' from dual
)
select PER_ID, address_id
from (
select PER_ID, address_id, ADDRESS_TYPE_XFLG,
row_number() over (partition by per_id
order by case ADDRESS_TYPE_XFLG
when 'MAIN-AE' then 1
when 'MAIN-EN' then 2
when 'CORRESPOND-AE' then 3
when 'CORRESPOND-EN' then 4
end) as rn
from L1_CI_PER_ADDRESS)
where rn = 1
Output:
PER_ID ADDRESS_ID ADDRESS_TYPE_XFLG
---------- ---------- -----------------
1 2 MAIN-AE
2 5 CORRESPOND-AE
3 6 MAIN-AE
4 8 MAIN-AE

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)
;

How to do select count(*) group by and select * at same time?

For example, I have table:
ID | Value
1 hi
1 yo
2 foo
2 bar
2 hehe
3 ha
6 gaga
I want my query to get ID, Value; meanwhile the returned set should be in the order of frequency count of each ID.
I tried the query below but don't know how to get the ID and Value column at the same time:
SELECT COUNT(*) FROM TABLE group by ID order by COUNT(*) desc;
The count number doesn't matter to me, I just need the data to be in such order.
Desire Result:
ID | Value
2 foo
2 bar
2 hehe
1 hi
1 yo
3 ha
6 gaga
As you can see because ID:2 appears most times(3 times), it's first on the list,
then ID:1(2 times) etc.
you can try this -
select id, value, count(*) over (partition by id) freq_count
from
(
select 2 as ID, 'foo' as value
from dual
union all
select 2, 'bar'
from dual
union all
select 2, 'hehe'
from dual
union all
select 1 , 'hi'
from dual
union all
select 1 , 'yo'
from dual
union all
select 3 , 'ha'
from dual
union all
select 6 , 'gaga'
from dual
)
order by 3 desc;
select t.id, t.value
from TABLE t
inner join
(
SELECT id, count(*) as cnt
FROM TABLE
group by ID
)
x on x.id = t.id
order by x.cnt desc
How about something like
SELECT t.ID,
t.Value,
c.Cnt
FROM TABLE t INNER JOIN
(
SELECT ID,
COUNT(*) Cnt
FROM TABLE
GROUP BY ID
) c ON t.ID = c.ID
ORDER BY c.Cnt DESC
SQL Fiddle DEMO
I see the question is already answered, but since the most obvious and most simple solution is missing, I'm posting it anyway. It doesn't use self joins nor subqueries:
SQL> create table t (id,value)
2 as
3 select 1, 'hi' from dual union all
4 select 1, 'yo' from dual union all
5 select 2, 'foo' from dual union all
6 select 2, 'bar' from dual union all
7 select 2, 'hehe' from dual union all
8 select 3, 'ha' from dual union all
9 select 6, 'gaga' from dual
10 /
Table created.
SQL> select id
2 , value
3 from t
4 order by count(*) over (partition by id) desc
5 /
ID VALU
---------- ----
2 bar
2 hehe
2 foo
1 yo
1 hi
6 gaga
3 ha
7 rows selected.