Oracle 11g - SQL to Calculate time difference between several rows - sql

PROBLEM
I'm still finding my feet with SQL and trying to calculate how long a certain user has been scanning items during their shift.
Each scan is timestamped generating a unique 9 digit sequence number (SEQ column) and date/time in the format 05-NOV-16 15:35:24 (THE_DATE column).
The person may be scanning for several hours, and what im trying to do is subtract the first timestamp they generated from the very last timestamp at the end of their shift.
So for example given this data sample:
+-----------+--------------------+--------+---------+---------+------------+-----------+
| SEQ | THE_DATE | SCANID | LOCATN | USER_ID | FIRST_NAME | LAST_NAME |
+-----------+--------------------+--------+---------+---------+------------+-----------+
| 103939758 | 05-NOV-16 14:36:22 | 194972 | DOOR 19 | AX9868 | Mike | Derry |
| 103939780 | 05-NOV-16 14:38:07 | 194972 | DOOR 19 | AX9868 | Mike | Derry |
| 103939792 | 05-NOV-16 14:39:24 | 194972 | DOOR 19 | AX9868 | Mike | Derry |
| 103940184 | 05-NOV-16 15:16:53 | 194972 | DOOR 19 | AX9868 | Mike | Derry |
| 103940185 | 05-NOV-16 15:51:41 | 194972 | DOOR 19 | AX9868 | Mike | Derry |
| 103940214 | 05-NOV-16 09:51:42 | 194993 | DOOR 16 | BC1910 | Tony | McCann |
| 103940215 | 05-NOV-16 15:19:06 | 194993 | DOOR 16 | BC1910 | Tony | McCann |
|+-----------+--------------------+--------+---------+---------+------------------------
DESIRED RESULT
I would like to subtract the timestamp in the first row for Mike Derry, from the last row on which he appears, row 5 in this case, so that i have an answer in hours (1.25).
the final result should be grouped by day and by user_id,first_name and last_name.
So far i have looked online and at the oracle documentation ,which led me to try using the LEAD function which seemed promising. It looks at the next rows to find the next timestamp where a userid appears next and then partitions by this userid to create a new column with that timestamp.
So the SQL looked like this
SELECT SEQ, THE_DATE,SCANID,LOCATN,USER_ID,LEAD(SYSDAT ) OVER (PARTITION BY USER_ID ORDER BY SYSDAT) AS NEXT_SCAN
FROM myTable...
However this is giving me incorrect results as it seems to double count the time difference. Im sure you SQL gurus have a more elegant way around this as i dont think this function suits this particular problem :)
So the final result im trying to achieve is:
+-----------+---------+------------+-----------+-----------+
| THE_DATE | USER_ID | FIRST_NAME | LAST_NAME | TOTAL_HRS |
+-----------+---------+------------+-----------+-----------+
| 05-NOV-16 | AX9868 | Mike | Derry | 1.25 |
| 05-NOV-16 | BC1910 | Tony | McCann | 5.47 |
+-----------+---------+------------+-----------+-----------+
Your help is much appreciated

Notes.... you shouldn't have redundant data (first name, last name) in this table, you should have a separate table just for that. It seems your hours are truncated and not rounded? (the rounding would give 1.26 in the first row).
with
test_data ( seq, the_date, scanid, locatn, user_id, first_name, last_name ) as (
select 103939758, to_date('05-NOV-16 14:36:22', 'dd-MON-yy hh24:mi:ss'), 194972, 'DOOR 19', 'AX9868', 'Mike', 'Derry' from dual union all
select 103939780, to_date('05-NOV-16 14:38:07', 'dd-MON-yy hh24:mi:ss'), 194972, 'DOOR 19', 'AX9868', 'Mike', 'Derry' from dual union all
select 103939792, to_date('05-NOV-16 14:39:24', 'dd-MON-yy hh24:mi:ss'), 194972, 'DOOR 19', 'AX9868', 'Mike', 'Derry' from dual union all
select 103940184, to_date('05-NOV-16 15:16:53', 'dd-MON-yy hh24:mi:ss'), 194972, 'DOOR 19', 'AX9868', 'Mike', 'Derry' from dual union all
select 103940185, to_date('05-NOV-16 15:51:41', 'dd-MON-yy hh24:mi:ss'), 194972, 'DOOR 19', 'AX9868', 'Mike', 'Derry' from dual union all
select 103940214, to_date('05-NOV-16 09:51:42', 'dd-MON-yy hh24:mi:ss'), 194993, 'DOOR 16', 'BC1910', 'Tony', 'McCann' from dual union all
select 103940215, to_date('05-NOV-16 15:19:06', 'dd-MON-yy hh24:mi:ss'), 194993, 'DOOR 16', 'BC1910', 'Tony', 'McCann' from dual
)
-- end of test data; solution (SQL query) begins below this line
select trunc(the_date) as the_date, user_id, first_name, last_name,
trunc(24 * (max(the_date) - min(the_date)), 2) as total_hrs
from test_data
group by trunc(the_date), user_id, first_name, last_name
;
THE_DATE USER_ID FIRST_NAME LAST_NAME TOTAL_HRS
--------- ------- ---------- --------- ----------
05-NOV-16 AX9868 Mike Derry 1.25
05-NOV-16 BC1910 Tony McCann 5.45

SELECT TRUNC(THE_DATE) as THE_DATE, USER_ID, FIRST_NAME, LAST_NAME,
MAX(THE_DATE) - MIN(THE_DATE) as TOTAL_HRS
FROM yourTable
GROUP BY TRUNC(THE_DATE), USER_ID, FIRST_NAME, LAST_NAME

Related

How to get the IN / OUT hour from a single column in Oracle

I have a REGISTER table in Oracle that looks like this
| ID | BOOOK_ID | CLIENT_ID | TYPE | DATE |
| 1 | 2447 | 274761 | 1 | 2020-04-07 09:38:54 |
| 2 | 2447 | 274761 | 2 | 2020-04-07 09:39:25 |
| 3 | 2447 | 274761 | 1 | 2020-04-07 09:39:53 |
| 4 | 2447 | 274761 | 2 | 2020-04-07 09:41:03 |
| 5 | 1000 | 274761 | 1 | 2020-04-07 09:52:05 |
| 6 | 2447 | 274761 | 1 | 2020-04-07 10:04:54 |
| 7 | 1000 | 274761 | 2 | 2020-04-07 10:05:38 |
| 8 | 2447 | 274761 | 2 | 2020-04-07 10:06:04 |
| 9 | 3002 | 274761 | 1 | 2020-04-07 11:22:02 |
And the result I want is something like this
| ID | BOOOK_ID | IN | OUT |
| 1 | 2447 | 2020-04-07 09:38:54 | 2020-04-07 09:39:25 |
| 2 | 2447 | 2020-04-07 09:39:53 | 2020-04-07 09:41:03 |
| 3 | 1000 | 2020-04-07 09:52:05 | 2020-04-07 10:05:38 |
| 4 | 2447 | 2020-04-07 10:04:54 | 2020-04-07 10:06:04 |
| 5 | 3002 | 2020-04-07 11:22:02 | |
Where the type = 1 indicates a start date and type = 2 indicates an end date.
You can use window functions. If we assume the types are correctly interleaved as in your sample data:
select rownum as id, book_id, client_id, date as in_date, out_date
from (select t.*,
lead(date) over (partition by book_id, client_id order by date) as out_date
from t
) t
where type = 1;
Here is a db<>fiddle.
If the data is not correctly interleaved, you should ask a new question with good examples of what can go wrong and what you want the final result to look like. This answers the question that you have asked here.
MATCH_RECOGNIZE
In Oracle 12.1 and higher, you can do this easily with the match_recognize clause.
alter session set nls_date_format = 'yyyy-mm-dd hh24:mi:ss';
with register (id, book_id, client_id, type_, date_) as (
select 1, 2447, 274761, 1, to_date('2020-04-07 09:38:54') from dual union all
select 2, 2447, 274761, 2, to_date('2020-04-07 09:39:25') from dual union all
select 3, 2447, 274761, 1, to_date('2020-04-07 09:39:53') from dual union all
select 4, 2447, 274761, 2, to_date('2020-04-07 09:41:03') from dual union all
select 5, 1000, 274761, 1, to_date('2020-04-07 09:52:05') from dual union all
select 6, 2447, 274761, 1, to_date('2020-04-07 10:04:54') from dual union all
select 7, 1000, 274761, 2, to_date('2020-04-07 10:05:38') from dual union all
select 8, 2447, 274761, 2, to_date('2020-04-07 10:06:04') from dual union all
select 9, 3002, 274761, 1, to_date('2020-04-07 11:22:02') from dual
)
select *
from register
match_recognize(
partition by book_id, client_id
order by date_
measures i.date_ as date_in, o.date_ as date_out
pattern ( i o? )
define i as type_ = 1, o as type_ = 2
)
order by client_id, date_in, book_id
;
BOOK_ID CLIENT_ID DATE_IN DATE_OUT
------- --------- ------------------- -------------------
2447 274761 2020-04-07 09:38:54 2020-04-07 09:39:25
2447 274761 2020-04-07 09:39:53 2020-04-07 09:41:03
1000 274761 2020-04-07 09:52:05 2020-04-07 10:05:38
2447 274761 2020-04-07 10:04:54 2020-04-07 10:06:04
3002 274761 2020-04-07 11:22:02
A few notes: TYPE, DATE, IN are Oracle keywords (the last two are even reserved keywords), so they shouldn't be column names, either in the input or the output. I changed the first two to TYPE_ and DATE_ (with a trailing underscore), and I named the output columns DATE_IN and DATE_OUT. Also, I changed the BOOOK_ID column name to BOOK_ID, and you would do well to do the same; the English word book has only two o's, not three.
Then - if you try this in SQL Developer, you will find that the question mark in the PATTERN clause of MATCH_RECOGNIZE throws an error. This is a defect in SQL Developer; if you run the same query in SQL*Plus or other interfaces, it will work just fine. In SQL Developer, you will need to change o? to the equivalent expression o{0,1}.
= = = =
PIVOT
In Oracle 11.1 and higher, you can do this with the PIVOT operator. You need some prep work (the subquery) to match the "in" and "out" event into pairs; the analytic function ROW_NUMBER(), partitioned by book, client and type, is a simple way to do that.
select client_id, book_id, date_in, date_out
from ( select client_id, book_id, type_, date_,
row_number() over (partition by book_id, client_id, type_
order by date_) as rn
from register r
)
pivot (max(date_) for type_ in (1 as date_in, 2 as date_out))
order by client_id, book_id, date_in
;

oracle sql query time periods union of set

i have a oracle database that has the following tables.How can i count the usage time of each room?
Time periods may overlap for each room,
The table structure is as follows。
t_room_electricity
+------------+--------------------+--------------------+
| roomcode | starttime | endtime |
+------------+--------------------+--------------------+
| 123 | 2019/5/10 10:00:00 | 2019/5/10 11:30:00 |
| 123 | 2019/5/10 10:30:00 | 2019/5/10 11:00:00 |
| 456 | 2019/5/10 11:00:00 | 2019/5/10 12:00:00 |
| 456 | 2019/5/10 13:00:00 | 2019/5/10 14:00:00 |
| 456 | 2019/5/10 13:30:00 | 2019/5/10 15:00:00 |
| 789 | 2019/6/10 14:22:00 | 2019/6/10 14:26:00 |
| 789 | 2019/6/10 14:31:00 | 2019/6/10 14:36:00 |
| 886 | 2019/6/10 14:32:00 | 2019/6/10 14:35:00 |
+------------+--------------------+--------------------+
Updating Answer to cater to the cases mentioned by OP.
USING MATCH_RECOGNIZE
with data
as (
select *
from t_room_electricity
match_recognize(
partition by roomcode
order by starttime
measures
first(starttime) f_starttime
,last(starttime) l_starttime
,first(endtime) f_endtime
,last(endtime) l_endtime
,min(starttime) as min_starttime
,max(endtime) as max_endtime
,match_number() as mn
,classifier() as cls
pattern(strt group1*)
define group1
as starttime<first(endtime)
)
)
select roomcode
,round(sum((max_endtime-min_starttime)*24*60)) as diff_in_minutes
from data
group by roomcode
See Output
https://dbfiddle.uk/?rdbms=oracle_18&fiddle=783140ede5dfbf8088a6ce77232ecef7
USING SQL
select x.roomcode
,count(distinct x.minutes_worked)
from (
select a.roomcode
,a.starttime
,a.endtime
,a.starttime + lvl/24/60 as minutes_worked
from t_room_electricity a
join (select level as lvl
from dual
connect by level<=24*60
)b
on b.lvl<=to_number((a.endtime-a.starttime)*24*60)
)x
group by x.roomcode
See output..
https://dbfiddle.uk/?rdbms=oracle_11.2&fiddle=2add9344f1335376b2fe32df21f769d6
Updated Answer to cater to overlaps, (based on a new recordset)
456 | 2019/5/10 11:00:00 | 2019/5/10 12:00:00 |
456 | 2019/5/10 13:00:00 | 2019/5/10 14:00:00 |
456 | 2019/5/10 13:30:00 | 2019/5/10 15:00:00 |
456 | 2019/5/10 13:45:00 | 2019/5/10 15:05:00 |
Considering overlaps the following query can achieve the intended result
select roomcode
,sum(round(time_in_minutes))
from (
select roomcode
,eventdate
,case when start_flag=1
and (lag(start_flag) over(partition by roomcode order by eventdate asc) = 0
or lag(start_flag) over(partition by roomcode order by eventdate asc) is null)
then null
else (eventdate
-
lag(eventdate) over(partition by roomcode order by eventdate asc)
)*24*60
end as time_in_minutes
from (
select roomcode,starttime as eventdate,1 as start_flag from t_room_electricity
union all
select roomcode,endtime as eventdate,0 as start_flag from t_room_electricity
)x
)y
group by roomcode
+----------+-----------------------------+
| ROOMCODE | SUM(ROUND(TIME_IN_MINUTES)) |
+----------+-----------------------------+
| 123 | 90 |
| 456 | 185 |
| 789 | 9 |
| 886 | 3 |
+----------+-----------------------------+
This first creates an inner block which stores each eventdatetime into a single column called EventDate.
Then the step is to check if the previous event was a closed boundary(ie start_flag=1) if it is then it begins counting, other wise it continues computing the difference between the previous event in minutes
After this the results are grouped by roomcode and the time_in_minutes is summed up
https://dbfiddle.uk/?rdbms=oracle_11.2&fiddle=650b4f0ad7304d7f44e7fabbca160a90
Old Answer
You can achieve this by using a group by expression as follows
select roomcode,sum((endtime-starttime)*24*60) as diff_in_minutes
from t_room_electricity
group by roomcode
+----------+-----------------+
| ROOMCODE | DIFF_IN_MINUTES |
+----------+-----------------+
| 123 | 120 |
| 789 | 9 |
| 456 | 210 |
| 886 | 3 |
+----------+-----------------+
See dbfiddle link.
https://dbfiddle.uk/?rdbms=oracle_11.2&fiddle=16fd05220157fd274cf0fab4e61c8802
Work with two tables like room:
with rooms AS
(SELECT 123 as room, to_date('2019/05/10 10:00:00', 'yyyy/mm/dd hh24:mi:ss') starttime, to_date('2019/05/10 11:30:00', 'yyyy/mm/dd hh24:mi:ss') endtime from dual
UNION ALL
SELECT 123 as room, to_date('2019/05/10 10:30:00', 'yyyy/mm/dd hh24:mi:ss') starttime, to_date('2019/05/10 11:00:00', 'yyyy/mm/dd hh24:mi:ss') endtime from dual
UNION ALL
SELECT 456 as room, to_date('2019/05/10 11:00:00', 'yyyy/mm/dd hh24:mi:ss') starttime, to_date('2019/05/10 12:00:00', 'yyyy/mm/dd hh24:mi:ss') endtime from dual
UNION ALL
SELECT 456 as room, to_date('2019/05/10 13:00:00', 'yyyy/mm/dd hh24:mi:ss') starttime, to_date('2019/05/10 14:00:00', 'yyyy/mm/dd hh24:mi:ss') endtime from dual
UNION ALL
SELECT 456 as room, to_date('2019/05/10 13:30:00', 'yyyy/mm/dd hh24:mi:ss') starttime, to_date('2019/05/10 15:00:00', 'yyyy/mm/dd hh24:mi:ss') endtime from dual
UNION ALL
SELECT 789 as room, to_date('2019/06/10 14:22:00', 'yyyy/mm/dd hh24:mi:ss') starttime, to_date('2019/06/10 14:26:00', 'yyyy/mm/dd hh24:mi:ss') endtime from dual
UNION ALL
SELECT 789 as room, to_date('2019/06/10 14:31:00', 'yyyy/mm/dd hh24:mi:ss') starttime, to_date('2019/06/10 14:36:00', 'yyyy/mm/dd hh24:mi:ss') endtime from dual
UNION ALL
SELECT 886 as room, to_date('2019/06/10 14:32:00', 'yyyy/mm/dd hh24:mi:ss') starttime, to_date('2019/06/10 14:35:00', 'yyyy/mm/dd hh24:mi:ss') endtime from dual)
select room, sum(round((endtime - starttime)*24*60)) from
(SELECT r1.room, min(r1.starttime) starttime, nvl(r2.endtime, r1.endtime) endtime
FROM rooms r1 left join
rooms r2
ON (r1.room = r2.room
AND r2.starttime between r1.starttime and r1.endtime
AND r2.starttime <> r1.starttime)
GROUP BY r1.room, nvl(r2.endtime, r1.endtime))
GROUP BY room
output:
789 9
123 60
456 180
886 3

Counting consecutive days including weekends SQL Server

I have an sql query that counts consecutive days but i need it to count weekends to. For example, if someone has a friday and a monday off i need this to count as 2 consecutive days if that makes sense.
tables:
CREATE TABLE Absence(
Date Date,
Code varchar(10),
Name varchar(10),
Type varchar(10)
);
INSERT INTO Absence (Date, Code, Name, Type)
VALUES ('01-10-18', 'S', 'Sam', 'Sick'),
('01-11-18','S', 'Sam', 'Sick'),
('01-12-18','S', 'Sam', 'Sick'),
('01-21-18','S', 'Sam', 'Sick'),
('01-26-18','S', 'Sam', 'Sick'),
('01-27-18','S', 'Sam', 'Sick'),
('02-12-18','S', 'Sam', 'Holiday'),
('02-13-18','S', 'Sam', 'Holiday'),
('02-18-18','S', 'Sam', 'Holiday'),
('02-25-18','S', 'Sam', 'Holiday'),
('02-10-18','S', 'Sam', 'Holiday'),
('02-13-18','F', 'Fred', 'Sick'),
('02-14-18','F', 'Fred', 'Sick'),
('03-09-18','F', 'Fred', 'Sick'),
('03-12-18','F', 'Fred', 'Sick'),
('02-28-18','F', 'Fred', 'Sick');
I have this code:
select name, min(date), max(date), count(*) as numdays, type
from (select a.*,
row_number() over (partition by name, type order by date) as
seqnum_ct
from absence a
) a
group by name, type, dateadd(day, -seqnum_ct, date);
And it produces this result:
| name | | | numdays | type |
|------|------------|------------|---------|---------|
| Fred | 2018-02-13 | 2018-02-14 | 2 | Sick |
| Fred | 2018-02-28 | 2018-02-28 | 1 | Sick |
| Fred | 2018-03-09 | 2018-03-09 | 1 | Sick |
| Fred | 2018-03-12 | 2018-03-12 | 1 | Sick |
| Sam | 2018-02-10 | 2018-02-10 | 1 | Holiday |
| Sam | 2018-02-12 | 2018-02-13 | 2 | Holiday |
| Sam | 2018-02-18 | 2018-02-18 | 1 | Holiday |
| Sam | 2018-02-25 | 2018-02-25 | 1 | Holiday |
| Sam | 2018-01-10 | 2018-01-12 | 3 | Sick |
| Sam | 2018-01-21 | 2018-01-21 | 1 | Sick |
| Sam | 2018-01-26 | 2018-01-27 | 2 | Sick |
If you look at these lines
('03-09-18','F', 'Fred', 'Sick'),
('03-12-18','F', 'Fred', 'Sick'),
This should equal 1 consecutive period even though it is a Friday and a Monday if this make sense. How can i edit this code so that it includes weekends to?
Thanks
SQL fiddle - http://sqlfiddle.com/#!18/1de27/1
Try this:
select name, min(date), max(date), count(*) as numdays, type
from (
select date, code, name, type, seqnum_ct + sum(weekend) over (partition by name, type order by date) seqnum_ct
from (select a.*,
row_number() over (partition by name, type order by date) as seqnum_ct,
case when datepart(weekday, [date]) = 2 and
datepart(weekday, lag([date]) over (partition by name, type order by date)) = 6 then 2 else 0 end [weekend]
from #absence a
) a
) a
group by name, type, dateadd(day, -seqnum_ct, date);
You can use a running sum to create groups to handle weekends. All you need to check is the current row's weekday is 2 (for Monday) and the previous row's is 6 (for Friday), for a given name,type in date order.
select name, min(date), max(date), count(*) as numdays, type
from (select a.*,sum(col) over(partition by Name,type order by [Date]) as grp
from (select a.*,
case when datediff(day,lag([Date]) over(partition by Name,type order by [Date]),[Date])=1 or
(datepart(weekday,[Date])=2 and datepart(weekday,lag([Date]) over(partition by Name,type order by [Date]))=6)
then 0 else 1 end as col
from absence a
) a
) a
group by name, type, grp

How use Group by and Max(date) multi record

i want Group by by Max(Datetime) each record. but i query have dupplicatate record. i want don't duplicate record.
SQL:
select a.pmn_code,
a.ref_period,
a.SERVICE_TYPE,
min(a.status) keep (dense_rank last order by a.updated_dtm) as status,
max(a.updated_dtm) as updated_dtm
from tempChkStatus a
group by a.pmn_code, a.ref_period, a.SERVICE_TYPE
Data Table tempChkStatus:
PMN_CODE | REF_PERIOD | SERVICE_TYPE | STATUS | UPDATED_DTM
A | 01/2016 | OI | I | 19/08/2016 10:54:44
A | 01/2016 | OP | N | 06/06/2017 15:09:55
A | 02/2016 | OT | I | 31/08/2016 08:37:45
A | 02/2016 | OT | N | 12/10/2016 11:13:56
A | 04/2016 | OI | I | 19/08/2016 10:54:44
A | 04/2016 | OP | N | 06/06/2017 15:09:55
Result SQL:
PMN_CODE | REF_PERIOD | SERVICE_TYPE | STATUS | UPDATED_DTM
A | 01/2016 | OI | I | 19/08/2016 10:54:44
A | 01/2016 | OP | N | 06/06/2017 15:09:55
A | 02/2016 | OT | N | 12/10/2016 11:13:56
A | 04/2016 | OI | I | 19/08/2016 10:54:44
A | 04/2016 | OP | N | 06/06/2017 15:09:55
But I want Result:
PMN_CODE | REF_PERIOD | SERVICE_TYPE | STATUS | UPDATED_DTM
A | 01/2016 | OP | N | 06/06/2017 15:09:55
A | 02/2016 | OT | N | 12/10/2016 11:13:56
A | 04/2016 | OP | N | 06/06/2017 15:09:55
Help me please. Thanks advance ;)
with tempChkStatus (
PMN_CODE, REF_PERIOD , SERVICE_TYPE , STATUS , UPDATED_DTM) as
(
select 'A', '01/2016' ,'OI', 'I', to_date('19/08/2016 10:54:44', 'dd/mm/yyyy hh24:mi:ss') from dual union all
select 'A', '01/2016' ,'OP', 'N', to_date('06/06/2017 15:09:55', 'dd/mm/yyyy hh24:mi:ss') from dual union all
select 'A', '02/2016' ,'OT', 'I', to_date('31/08/2016 08:37:45', 'dd/mm/yyyy hh24:mi:ss') from dual union all
select 'A', '02/2016' ,'OT', 'N', to_date('12/10/2016 11:13:56', 'dd/mm/yyyy hh24:mi:ss') from dual union all
select 'A', '04/2016' ,'OI', 'I', to_date('19/08/2016 10:54:44', 'dd/mm/yyyy hh24:mi:ss') from dual union all
select 'A', '04/2016' ,'OP', 'N', to_date('06/06/2017 15:09:55', 'dd/mm/yyyy hh24:mi:ss') from dual
)
select * from (
select e.*, max(updated_dtm) over (partition by ref_period) md from tempchkstatus e
)
where updated_dtm = md
;
You just need to remove SERVICE_TYPE from the GROUP BY:
select s.pmn_code, s.ref_period,
min(s.SERVICE_TYPE) as service_type,
min(s.status) keep (dense_rank last order by s.updated_dtm) as status,
max(s.updated_dtm) as updated_dtm
from tempChkStatus s
group by s.pmn_code, s.ref_period;
The GROUP BY expressions define the rows returns by an aggregation query.
This version uses MIN() on SERVICE_TYPE. It is not clear what logic you want for the result set.

Oracle normalize multiple rows into new view

I need to find a solution for the following problem.
Out internal postman has to scan a QR-barcode on a mailbox first and a datamatrix-barcode on each (internal) letter he puts into the mailbox.
The data from his scanner-device is stored into a Oracle 11g database-table in the following format
|----|---------------------|--------------|---------------|
| ID | SCAN_DATE | BAROCDE_TYPE | BARCODE_VALUE |
----------------------------------------------------------|
| 1 | 2016/02/01 08:10:30 | QR | Dept_HR |
| 2 | 2016/02/01 08:10:35 | DM | Lett_1 |
| 3 | 2016/02/01 08:10:38 | DM | Lett_3 |
| 4 | 2016/02/01 08:10:41 | DM | Lett_6 |
| 5 | 2016/02/01 08:16:37 | QR | Dept_FI |
| 6 | 2016/02/01 08:16:38 | DM | Lett_2 |
| 7 | 2016/02/01 08:16:40 | DM | Lett_4 |
|----|---------------------|--------------|---------------|
I want to "normalize?" the data into a database-view in the following format
(where it easy to see which letter was delivered to which mailbox)
|---------------------|------------|---------------------|----------|
| ScanDate Postbox | Department | ScanDate Letter | LetterID |
|---------------------|------------|---------------------|----------|
| 2016/02/01 08:10:30 | Dept_HR | 2016/02/01 08:10:35 | Lett_1 |
| 2016/02/01 08:10:30 | Dept_HR | 2016/02/01 08:10:38 | Lett_3 |
| 2016/02/01 08:10:30 | Dept_HR | 2016/02/01 08:10:41 | Lett_6 |
| 2016/02/01 08:16:37 | Dept_FI | 2016/02/01 08:16:38 | Lett_2 |
| 2016/02/01 08:16:37 | Dept_FI | 2016/02/01 08:16:40 | Lett_4 |
|---------------------|------------|---------------------|----------|
Any ideas how I can create an oracle database-view showing the data as described above?
I guess the Postbox record is the previous record to the letter records. This is a bad because unsafe association.
the following select should do the job:
-- Your testdata
with data(id,
scan_date,
barcode_type,
barcode_value) as
(select 1,
to_date('2016/02/01 08:10:30', 'YYYY/MM/DD HH24:MI:SS'),
'QR',
'Dept_HR'
from dual
union all
select 2,
to_date('2016/02/01 08:10:35', 'YYYY/MM/DD HH24:MI:SS'),
'DM',
'Lett_1'
from dual
union all
select 3,
to_date('2016/02/01 08:10:38', 'YYYY/MM/DD HH24:MI:SS'),
'DM',
'Lett_3'
from dual
union all
select 4,
to_date('2016/02/01 08:10:41', 'YYYY/MM/DD HH24:MI:SS'),
'DM',
'Lett_6'
from dual
union all
select 5,
to_date('2016/02/01 08:16:37', 'YYYY/MM/DD HH24:MI:SS'),
'QR',
'Dept_FI'
from dual
union all
select 6,
to_date('2016/02/01 08:16:38', 'YYYY/MM/DD HH24:MI:SS'),
'DM',
'Lett_2'
from dual
union all
select 7,
to_date('2016/02/01 08:16:40', 'YYYY/MM/DD HH24:MI:SS'),
'DM',
'Lett_4'
from dual)
-- Select
select dp.scan_date as "ScanDate Postbox",
dp.barcode_value as "Departement",
d.scan_date as "ScanDate Letter",
d.barcode_value as "LetterId"
from data dp, data d
where d.barcode_type = 'DM'
and dp.barcode_type = 'QR'
and dp.scan_date =
(select max(dpp.scan_date)
from data dpp
where dpp.barcode_type = dp.barcode_type
and dpp.scan_date <= d.scan_date);