Timestamp to_char subquery - oracle

I have the following query but I need a particular format (HH24,MI,SS) for the results that I get in column "elapsed". Can I do that with a subquery?
SELECT
trunc(dstamp) "DATE",
to_char(dstamp, 'HH24:MI:SS') "Time"
, user_id
, dstamp - lag(dstamp) OVER (
PARTITION BY user_id ORDER BY dstamp
) AS elapsed
, COUNT(CODE) "Lines_Picked"
, ROUND(sum(update_qty / cast(substr(sku_id, instr(sku_id, '-', 1, 1) + 1
, instr(sku_id, '-', 1, 2) - 1 - instr(sku_id, '-', 1, 1)) as integer))) "CASES_PICKED"
FROM v_inventory_transaction
WHERE client_id = 'USKIDS2CA'
AND code = 'Pick'
AND list_id IS NOT NULL
AND STATION_ID LIKE 'R%'
AND reference_id NOT LIKE '%-FK%'
AND trunc(dstamp) = to_date('03/23/2022','mm/dd/yyyy')
GROUP BY
user_id,
dstamp
, cast(substr(sku_id, instr(sku_id, '-', 1, 1) + 1
, instr(sku_id, '-', 1, 2) - 1 - instr(sku_id, '-', 1, 1)) as integer)

What is "timestamp" from the title? Which datatype is it? DATE or TIMESTAMP?
If it is TIMESTAMP, extract helps a lot:
SQL> with temp (user_id, dstamp) as
2 (select 1, to_timestamp('27.03.2022 08:08', 'dd.mm.yyyy hh24:mi') from dual union all
3 select 1, to_timestamp('27.03.2022 05:30', 'dd.mm.yyyy hh24:mi') from dual union all
4 select 1, to_timestamp('27.03.2022 02:28', 'dd.mm.yyyy hh24:mi') from dual
5 )
6 select user_id,
7 dstamp,
8 --
9 to_char(extract (hour from dstamp - lag(dstamp) over (partition by user_id order by dstamp)), 'FM00') ||':'||
10 to_char(extract (minute from dstamp - lag(dstamp) over (partition by user_id order by dstamp)), 'FM00') ||':'||
11 to_char(extract (second from dstamp - lag(dstamp) over (partition by user_id order by dstamp)), 'FM00') result
12 from temp
13 order by user_id, dstamp;
USER_ID DSTAMP RESULT
---------- ----------------------------------- -----------
1 27-MAR-22 02.28.00.000000000 AM ::
1 27-MAR-22 05.30.00.000000000 AM 03:02:00
1 27-MAR-22 08.08.00.000000000 AM 02:38:00
SQL>
If it is DATE, then ELAPSED currently represents number of days between dstamp and its previous row's value per user_id. As it seems that that number is always less than 1 day (as you want result in hh24:mi:ss format), a simple option is to apply combination of
to_char(to_date(elapsed, 'sssss'), 'hh24:mi:ss')
and get the result. Step-by-step (so that you'd see which result you get for each step):
SQL> with temp (user_id, dstamp) as
2 (select 1, to_date('27.03.2022 08:08', 'dd.mm.yyyy hh24:mi') from dual union all
3 select 1, to_date('27.03.2022 05:30', 'dd.mm.yyyy hh24:mi') from dual union all
4 select 1, to_date('27.03.2022 02:28', 'dd.mm.yyyy hh24:mi') from dual
5 )
6 select user_id,
7 dstamp,
8 --
9 dstamp - lag(dstamp) over (partition by user_id order by dstamp) as elapsed_days,
10 --
11 round((dstamp - lag(dstamp) over (partition by user_id order by dstamp)) *
12 24 * 60 * 60) elapsed_seconds,
13 --
14 to_char(to_date(round((dstamp - lag(dstamp) over (partition by user_id
15 order by dstamp)) *
16 24 * 60 * 60), 'sssss'),
17 'hh24:mi:ss') result
18 from temp
19 order by user_id, dstamp;
USER_ID DSTAMP ELAPSED_DAYS ELAPSED_SECONDS RESULT
---------- ------------------- ------------ --------------- --------
1 27.03.2022 02:28:00
1 27.03.2022 05:30:00 .126388889 10920 03:02:00
1 27.03.2022 08:08:00 .109722222 9480 02:38:00
SQL>
However, if elapsed can be larger than 1 day so that it exceeds number of seconds in a day (that's 24 * 60 * 60 = 86400), that won't work so you'll have to actually calculate the result. However, query then becomes really ugly so you'd rather use another CTE (or a subquery) to first extract elapsed and work with it, instead of using dstamp - lag(...) all the time.
Therefore, I hope it is the timestamp after all.

Related

Oracle: Return the specific records based on one column date

I have a database structure as below.
period
month
start_date
1
April
2022-04-01
2
May
2022-05-07
3
June
2022-06-04
4
July
2022-07-02
5
August
2022-08-06
6
September
2022-09-03
7
October
2022-10-01
8
November
2022-11-05
9
December
2022-12-03
10
January
2023-01-01
11
February
2023-02-04
12
March
2023-03-04
End date of the year is 2023-03-31.
Based on current_date, how do I select the query to return where the current date falls under Period 6.
My current query as below.
SELECT period FROM table1 as a
WHERE
a.start_date = (SELECT MAX(start_date) FROM table1 as b WHERE
b.start_date <=current_date) and ROWNUM <= 1
Is there anyway to improve the current query which to avoid using subquery?
Today is September 22nd, so - would this do?
Some sample data:
SQL> with test (period, month, start_date) as
2 (select 1, 'april' , date '2022-04-01' from dual union all
3 select 5, 'august' , date '2022-08-06' from dual union all
4 select 6, 'september', date '2022-09-03' from dual union all
5 select 7, 'october' , date '2022-10-01' from dual union all
6 select 10, 'january' , date '2023-01-01' from dual union all
7 select 12, 'march' , date '2023-03-04' from dual
8 ),
Query begins here:
9 temp as
10 (select period, month, start_date,
11 row_number() over (order by start_date desc) rn
12 from test
13 where start_date <= sysdate
14 )
15 select period
16 from temp
17 where rn = 1
18 /
PERIOD
----------
6
SQL>
It still uses a subquery (or a CTE, as in my example), but - as opposed to your approach, it selects from the source table only once, so performance should be improved.
A few more tests: instead of sysdate (line #13), presume that today is September 2nd (which means that it is in period #5):
9 temp as
10 (select period, month, start_date,
11 row_number() over (order by start_date desc) rn
12 from test
13 where start_date <= date '2022-09-02'
14 )
15 select period
16 from temp
17 where rn = 1;
PERIOD
----------
5
SQL>
Or, if today were August 7th:
9 temp as
10 (select period, month, start_date,
11 row_number() over (order by start_date desc) rn
12 from test
13 where start_date <= date '2022-08-07'
14 )
15 select period
16 from temp
17 where rn = 1;
PERIOD
----------
5
SQL>
Your rule for the start_date appears to be:
If the month is January (first month of the calendar year) or April (typically, first month of the financial year) then use the 1st of that month;
Otherwise use the 1st Saturday of the month.
If that is the case then you can calculate the start date of the next month and use the query:
SELECT *
FROM table1
WHERE start_date <= SYSDATE
AND SYSDATE < CASE
WHEN EXTRACT(MONTH FROM ADD_MONTHS(start_date, 1))
IN (1, 4) -- 1st month of calendar or financial year
THEN TRUNC(ADD_MONTHS(start_date, 1), 'MM')
ELSE NEXT_DAY(TRUNC(ADD_MONTHS(start_date, 1), 'MM') - 1, 'SATURDAY')
END
Then, for your sample data:
CREATE TABLE table1 (
period NUMBER(2,0),
month VARCHAR2(9)
GENERATED ALWAYS AS (
CAST(
TO_CHAR(start_date, 'FXMonth', 'NLS_DATE_LANGUAGE=English')
AS VARCHAR2(9)
)
),
start_date DATE
);
INSERT INTO table1 (period, start_date)
SELECT LEVEL,
CASE
WHEN EXTRACT(MONTH FROM ADD_MONTHS(DATE '2022-04-01', LEVEL - 1))
IN (1, 4) -- 1st month of calendar or financial year
THEN ADD_MONTHS(DATE '2022-04-01', LEVEL - 1)
ELSE NEXT_DAY(ADD_MONTHS(DATE '2022-04-01', LEVEL - 1) - 1, 'SATURDAY')
END
FROM DUAL
CONNECT BY LEVEL <= 12;
Outputs:
PERIOD
MONTH
START_DATE
6
September
2022-09-03 00:00:00
fiddle

How to select the first 5 dates from each group and put them in a single column separated by comma in Oracle?

I have a table like this:
Division
Region
Date of Last Visit
1
2
11/20/2021
1
2
11/18/2021
1
7
10/18/2021
1
7
11/19/2021
2
2
11/17/2021
2
3
09/20/2021
2
3
10/20/2021
I want to write a query that groups by the division and region columns and gives me the last 5 dates for each group separated by commas in a single column. Something like this:
Division
Region
Date of Last Visit
Today
Days since last visit
1
2
11/20/2021, 11/18/2021
sysdate
sysdate - max(date of last visit)
1
7
10/18/2021, 11/19/2021
sysdate
sysdate - max(date of last visit)
2
2
11/17/2021
sysdate
sysdate - max(date of last visit)
2
3
9/20/2021, 10/20/2021
sysdate
sysdate - max(date of last visit)
The last two columns are custom calculated columns that I also need for the final output table. Any help would be greatly appreciated as I have tried a lot of things but I keep getting errors about it not being grouped properly, possibly because of the two extra columns at the end. But even without that, I am not sure how to fetch only the last 5 dates per group in oracle.
Thanks!
You want to filter the greatest-n-per-group using the ROW_NUMBER analytic function and then aggregate:
SELECT division,
region,
LISTAGG(TO_CHAR(date_of_last_visit, 'DD/MM/YYYY'), ',')
WITHIN GROUP (ORDER BY date_of_last_visit DESC)
AS date_of_last_visit,
SYSDATE AS today,
TRUNC(SYSDATE - MAX(date_of_last_visit)) AS days_since_last_visit
FROM (
SELECT t.*,
ROW_NUMBER() OVER (PARTITION BY division, region
ORDER BY date_of_last_visit DESC) AS rn
FROM table_name t
)
WHERE rn <= 5
GROUP BY
division,
region
Which, for the sample data:
CREATE TABLE table_name (division, region, date_of_last_visit) as
select 1, 2, date '2021-11-20' from dual union all
select 1, 2, date '2021-11-18' from dual union all
select 1, 7, date '2021-10-18' from dual union all
select 1, 7, date '2021-11-19' from dual union all
select 2, 2, date '2021-11-17' from dual union all
select 2, 3, date '2021-09-20' from dual union all
select 2, 3, date '2021-10-20' from dual;
Outputs:
DIVISION
REGION
DATE_OF_LAST_VISIT
TODAY
DAYS_SINCE_LAST_VISIT
1
2
20/11/2021,18/11/2021
30-NOV-21
10
1
7
19/11/2021,18/10/2021
30-NOV-21
11
2
2
17/11/2021
30-NOV-21
13
2
3
20/10/2021,20/09/2021
30-NOV-21
41
db<>fiddle here
Here you go; read comments within code.
SQL> with test (division, region, datum) as
2 -- sample data
3 (select 1, 2, date '2021-11-20' from dual union all
4 select 1, 2, date '2021-11-18' from dual union all
5 select 1, 7, date '2021-10-18' from dual union all
6 select 1, 7, date '2021-11-19' from dual union all
7 select 2, 2, date '2021-11-17' from dual union all
8 select 2, 3, date '2021-09-20' from dual union all
9 select 2, 3, date '2021-10-20' from dual
10 ),
11 temp as
12 -- rank rows per division/region, sorted by date column in descending order
13 (select t.*,
14 rank() over (partition by division, region order by datum desc) rnk
15 from test t
16 )
17 -- select up to 5 last rows per division/region
18 select division, region,
19 listagg(datum, ', ') within group (order by datum) dates,
20 trunc(sysdate) today,
21 --
22 (select trunc(sysdate) - a.datum
23 from temp a
24 where a.division = t.division
25 and a.region = t.region
26 and a.rnk = 1) days_since
27 from temp t
28 where rnk <= 5
29 group by division, region
30 order by division, region;
DIVISION REGION DATES TODAY DAYS_SINCE
---------- ---------- ------------------------------ ---------- ----------
1 2 11/18/2021, 11/20/2021 11/30/2021 10
1 7 10/18/2021, 11/19/2021 11/30/2021 11
2 2 11/17/2021 11/30/2021 13
2 3 09/20/2021, 10/20/2021 11/30/2021 41
SQL>

I have one requirement where I have to show the records between specific date and time every day of one week

I have one requirement where I have to show the records between specific date and time every day in one week duration.
in one week duration( 2019-04-01 till 2019-04-06) ,for instance record of 2019-04-01 at 19 PM till 8 Am of 2019-04-02 ,and record of 2019-04-02 at 19 PM till 08 AM of 2019-04-03 and ...
would you please help me!
Use recursive query to create proper periods then join with your data or do it simpler with condition like here:
select callbegin, callerno
from table4
where callerno in ('7032','750')
and callbegin between timestamp '2019-04-01 19:00:00'
and timestamp '2019-04-06 08:00:00'
and ('19' <= to_char(callbegin, 'hh24') or to_char(callbegin, 'hh24') < '08');
demo
Here's how I understood the question.
SQL> alter session set nls_date_format = 'dd.mm.yyyy hh24:mi';
Session altered.
SQL> break on period;
SQL> with
2 data (id, datum) as
3 (select 1, to_date('01.04.2019 15:30', 'dd.mm.yyyy hh24:mi') from dual union all
4 select 2, to_date('01.04.2019 20:00', 'dd.mm.yyyy hh24:mi') from dual union all -- 1st
5 select 3, to_date('02.04.2019 01:15', 'dd.mm.yyyy hh24:mi') from dual union all -- 1st perios
6 select 4, to_date('02.04.2019 11:00', 'dd.mm.yyyy hh24:mi') from dual union all
7 select 5, to_date('02.04.2019 23:15', 'dd.mm.yyyy hh24:mi') from dual union all -- 2nd period
8 select 6, to_date('03.04.2019 00:10', 'dd.mm.yyyy hh24:mi') from dual union all -- 2nd
9 select 7, to_date('04.04.2019 22:20', 'dd.mm.yyyy hh24:mi') from dual -- 3rd period
10 ),
11 test as
12 (select date '2019-04-01' dstart,
13 date '2019-04-06' dend
14 from dual
15 ),
16 inter as
17 (select dstart + level - 1 datum
18 from test
19 connect by level <= dend - dstart + 1
20 ),
21 from_to as
22 (select datum + 19/24 date_from,
23 lead(datum) over (order by datum) + 8/24 date_to
24 from inter
25 )
26 select f.date_From ||' - '|| f.date_to period,
27 d.id,
28 d.datum
29 from data d join from_to f on 1 = 1
30 where d.datum between f.date_from and f.date_to
31 order by f.date_From, d.id;
PERIOD ID DATUM
----------------------------------- ---------- ----------------
01.04.2019 19:00 - 02.04.2019 08:00 2 01.04.2019 20:00
3 02.04.2019 01:15
02.04.2019 19:00 - 03.04.2019 08:00 5 02.04.2019 23:15
6 03.04.2019 00:10
04.04.2019 19:00 - 05.04.2019 08:00 7 04.04.2019 22:20
SQL>
This is how to filter data by days and time by one week:
With date_list as (
Select
to_date(to_char( (sysdate - level), 'yyyymmdd') || '19', 'yyyymmddhh24') begin_time,
to_date(to_char( ((sysdate - level)+1), 'yyyymmdd') || '08', 'yyyymmddhh24') end_time
From dual connect by level <= 7
)
Select begin_time, your_table.*
From
your_table t1,
date_list t2
Where
t1.your_date between t2.begin_time and t2.end_time;

Oracle sql retrive records based on maximum time

i have below data.
table A
id
1
2
3
table B
id name data1 data2 datetime
1 cash 12345.00 12/12/2012 11:10:12
1 quantity 222.12 14/12/2012 11:10:12
1 date 20/12/2012 12/12/2012 11:10:12
1 date 19/12/2012 13/12/2012 11:10:12
1 date 13/12/2012 14/12/2012 11:10:12
1 quantity 330.10 17/12/2012 11:10:12
I want to retrieve data in one row like below:
tableA.id tableB.cash tableB.date tableB.quantity
1 12345.00 13/12/2012 330.10
I want to retrieve based on max(datetime).
The data model appears to be insane-- it makes no sense to join an ORDER_ID to a CUSTOMER_ID. It makes no sense to store dates in a VARCHAR2 column. It makes no sense to have no relationship between a CUSTOMER and an ORDER. It makes no sense to have two rows in the ORDER table with the same ORDER_ID. ORDER is also a reserved word so you cannot use that as a table name. My best guess is that you want something like
select *
from customer c
join (select order_id,
rank() over (partition by order_id
order by to_date( order_time, 'YYYYMMDD HH24:MI:SS' ) desc ) rnk
from order) o on (c.customer_id=o.order_id)
where o.rnk = 1
If that is not what you want, please (as I asked a few times in the comments) post the expected output.
These are the results I get with my query and your sample data (fixing the name of the ORDER table so that it is actually valid)
SQL> ed
Wrote file afiedt.buf
1 with orders as (
2 select 1 order_id, 'iphone' order_name, '20121201 12:20:23' order_time from dual union all
3 select 1, 'iphone', '20121201 12:22:23' from dual union all
4 select 2, 'nokia', '20110101 13:20:20' from dual ),
5 customer as (
6 select 1 customer_id, 'paul' customer_name from dual union all
7 select 2, 'stuart' from dual union all
8 select 3, 'mike' from dual
9 )
10 select *
11 from customer c
12 join (select order_id,
13 rank() over (partition by order_id
14 order by to_date( order_time, 'YYYYMMDD HH24:MI:SS' ) desc ) rnk
15 from orders) o on (c.customer_id=o.order_id)
16* where o.rnk = 1
SQL> /
CUSTOMER_ID CUSTOM ORDER_ID RNK
----------- ------ ---------- ----------
1 paul 1 1
2 stuart 2 1
Try something like
SELECT *
FROM CUSTOMER c
INNER JOIN ORDER o
ON (o.CUSTOMER_ID = c.CUSTOMER_ID)
WHERE TO_DATE(o.ORDER_TIME, 'YYYYMMDD HH24:MI:SS') =
(SELECT MAX(TO_DATE(o.ORDER_TIME, 'YYYYMMDD HH24:MI:SS')) FROM ORDER)
Share and enjoy.

Oracle SELECT query: collapsing NULL values when pairing up dates for different fields

This question is very much like my previous question, but a bit more complicated. Rob van Wijk's answer worked perfectly for my other question, and I've been using that as a starting point. My problem now is that I am pivoting dates for different fields. Whereas before I cared about getting all open_in and open_out values for a given id, now I want new_in, new_out, open_in, open_out, fixed_in, and fixed_out for each id. I have the following:
SELECT id,
state,
state_time,
MAX(new_row_num) OVER (PARTITION BY id ORDER BY state_time) AS new_row_group,
MAX(open_row_num) OVER (PARTITION BY id ORDER BY state_time) AS open_row_group,
MAX(fixed_row_num) OVER (PARTITION BY id ORDER BY state_time) AS fixed_row_group
FROM (
SELECT id,
state,
state_time,
CASE state
WHEN 'New'
THEN ROW_NUMBER() OVER (PARTITION BY id ORDER BY state_time)
END AS new_row_num,
CASE state
WHEN 'Open'
THEN ROW_NUMBER() OVER (PARTITION BY id ORDER BY state_time)
END AS open_row_num,
CASE state
WHEN 'Fixed'
THEN ROW_NUMBER() OVER (PARTITION BY id ORDER BY state_time)
END AS fixed_row_num
FROM ...
)
This gives me data like the following:
id state state_time new_row_group open_row_group fixed_row_group
1 New 2009-03-03 00:03:31 1
1 Closed 2009-03-04 04:15:27 1
2 New 2010-05-22 14:38:49 1
2 Open 2010-05-22 14:39:14 1 2
2 Fixed 2010-05-22 17:15:27 1 2 3
I would like data like the following:
id new_in new_out open_in open_out fixed_in fixed_out
1 2009-03-03 00:03:31 2009-03-04 04:15:27
2 2010-05-22 14:38:49 2010-05-22 14:39:14 2010-05-22 14:39:14 2010-05-22 17:15:27 2010-05-22 17:15:27
How can I pivot the data to get this date-pairing for each id?
Edit: to clarify, an id can enter and leave a state multiple times. For example, an id might go from New to Open to Fixed to Open to Fixed to Closed. In that case, there would need to be as many rows as is necessary to hold all the state times, e.g.:
id new_in new_out open_in open_out fixed_in fixed_out
4 2009-01-01 00:00:00 2009-01-02 00:00:00 2009-01-02 00:00:00 2009-01-03 00:00:00 2009-01-03 00:00:00 2009-01-04 00:00:00
4 2009-01-04 00:00:00 2009-01-05 00:00:00 2009-01-05 00:00:00 2009-01-06 00:00:00
Sarah,
Here is an example with your sample data:
SQL> create table yourtable (id,state,state_time)
2 as
3 select 1, 'New', to_date('2009-03-03 00:03:31','yyyy-mm-dd hh24:mi:ss') from dual union all
4 select 1, 'Closed', to_date('2009-03-04 04:15:27','yyyy-mm-dd hh24:mi:ss') from dual union all
5 select 2, 'New', to_date('2010-05-22 14:38:49','yyyy-mm-dd hh24:mi:ss') from dual union all
6 select 2, 'Open', to_date('2010-05-22 14:39:14','yyyy-mm-dd hh24:mi:ss') from dual union all
7 select 2, 'Fixed', to_date('2010-05-22 17:15:27','yyyy-mm-dd hh24:mi:ss') from dual union all
8 select 3, 'New', date '2009-01-01' from dual union all
9 select 3, 'Open', date '2009-01-02' from dual union all
10 select 3, 'Fixed', date '2009-01-03' from dual union all
11 select 3, 'Open', date '2009-01-04' from dual union all
12 select 3, 'Fixed', date '2009-01-05' from dual union all
13 select 3, 'Closed', date '2009-01-06' from dual
14 /
Table created.
The query:
SQL> select id
2 , max(decode(state,'New',state_time)) new_in
3 , max(decode(state,'New',out_time)) new_out
4 , max(decode(state,'Open',state_time)) open_in
5 , max(decode(state,'Open',out_time)) open_out
6 , max(decode(state,'Fixed',state_time)) fixed_in
7 , max(decode(state,'Fixed',out_time)) fixed_out
8 from ( select id
9 , state
10 , state_time
11 , max(cnt) over (partition by id order by state_time) the_row
12 , lead(state_time) over (partition by id order by state_time) out_time
13 from ( select id
14 , state
15 , state_time
16 , count(*) over (partition by id,state order by state_time) cnt
17 from yourtable
18 )
19 )
20 group by id
21 , the_row
22 order by id
23 , the_row
24 /
ID NEW_IN NEW_OUT OPEN_IN OPEN_OUT FIXED_IN FIXED_OUT
---------- ------------------- ------------------- ------------------- ------------------- ------------------- -------------------
1 03-03-2009 00:03:31 04-03-2009 04:15:27
2 22-05-2010 14:38:49 22-05-2010 14:39:14 22-05-2010 14:39:14 22-05-2010 17:15:27 22-05-2010 17:15:27
3 01-01-2009 00:00:00 02-01-2009 00:00:00 02-01-2009 00:00:00 03-01-2009 00:00:00 03-01-2009 00:00:00 04-01-2009 00:00:00
3 04-01-2009 00:00:00 05-01-2009 00:00:00 05-01-2009 00:00:00 06-01-2009 00:00:00
4 rows selected.
To understand how it works, execute the query from the inside out and check the intermediate result sets. Please let me know if you need some additional explanation.
Regards,
Rob.
I'm not sure how you'd prefer to handle the situation where the same state is repeated more than once for an ID. The following answer takes the easy route, assuming that you would want the first time the state was set and the last time the state was replaced.
select id,
min(case state when 'New' then state_time else null end) as new_in,
max(case state when 'New' then out_state_time else null end) as new_out,
min(case state when 'Open' then state_time else null end) as open_in,
max(case state when 'Open' then out_state_time else null end) as open_out,
min(case state when 'Fixed' then state_time else null end) as fixed_in,
max(case state when 'Fixed' then out_state_time else null end) as fixed_out
from
(select id,
state,
state_time,
lead(state_time) over (partition by id
order by state_time) as out_state_time
from ...
)
group by id
The lead analytic function gets the next row described by the partition/order statement, so that's the easiest way to find out when the state changed. The middle query is a basic pivot query (transforming columns to rows).
select news.id, news.state_time as new_in, min(not_news.state_time) as new_out
, min(opens.state_time) as open_in
, min(not_opens.state_time) as open_out
, min(closes.state_time) as close_in
, min(not_closed.state_time) as close_out
from
(SELECT id,
state,
state_time
from mytable
where state = 'New' ) news
left join
(SELECT id,
state,
state_time
from mytable
where state <> 'New' ) not_news on news.id = not_news.id and news.state_time <= not_news.state_time
left join
(SELECT id,
state,
state_time
from mytable
where state = 'Open' ) opens on news.id = opens.id and news.state_time <= opens.state_time
left join
(SELECT id,
state,
state_time
from mytable
where state not in ('New', 'Open' )) not_opens on news.id = opens.id and news.state_time <= opens.state_time and opens.state_time <= not_opens.state_time
left join
(SELECT id,
state,
state_time
from mytable
where state = 'Closed' ) closes on news.id = closes.id and news.state_time <= closes.state_time
left join
(SELECT id,
state,
state_time
from mytable
where state not in ('Closed' )) not_closed on news.id = not_closed.id and news.state_time <= closes.state_time and closes.state_time <= not_closed.state_time
group by news.id, news.state_time
order by id, news.state_time
My test data (borrowed from Rob):
create table mytable (id,state,state_time)
as
select 1, 'New', to_date('2009-03-03 00:03:31','yyyy-mm-dd hh24:mi:ss') from dual union all
select 1, 'Closed', to_date('2009-03-04 04:15:27','yyyy-mm-dd hh24:mi:ss') from dual union all
select 2, 'New', to_date('2010-05-22 14:38:49','yyyy-mm-dd hh24:mi:ss') from dual union all
select 2, 'Open', to_date('2010-05-22 14:39:14','yyyy-mm-dd hh24:mi:ss') from dual union all
select 2, 'Fixed', to_date('2010-05-22 17:15:27','yyyy-mm-dd hh24:mi:ss') from dual union all
select 3, 'New', date '2009-01-01' from dual union all
select 3, 'Open', date '2009-01-02' from dual union all
select 3, 'Fixed', date '2009-01-03' from dual union all
select 3, 'Open', date '2009-01-04' from dual union all
select 3, 'Fixed', date '2009-01-05' from dual union all
select 3, 'Closed', date '2009-01-06' from dual
query results:
ID NEW_IN NEW_OUT OPEN_IN OPEN_OUT CLOSE_IN CLOSE_OUT
1 3/3/2009 12:03:31 3/4/2009 4:15:27 3/4/2009 4:15:27
2 5/22/2010 2:38:49 5/22/2010 2:39:14 5/22/2010 2:39:14 5/22/2010 5:15:27
3 1/1/2009 1/2/2009 1/2/2009 1/3/2009 1/6/2009
I hope you can read the above, I'm having trouble formatting it.

Resources