Substitute temporal gaps in result set with default value in Postgres - sql

I'm using this query to average a value (performance) over 3-minute intervals of an hour:
SELECT
date_trunc('hour', created) AS created,
(extract(minute FROM created)::int / 3) minute_part,
worker,
AVG(performance)
FROM perf_stats
WHERE
group_id = 'foo' AND
worker = 'bar' AND
created > '1.1.2021 19:00:00' AND
created < '1.1.2021 20:00:00'
GROUP BY 1, 2, worker
ORDER BY 1, 2, worker;
This returns the following result set for the sample data set:
created
minute_part
worker
avg
2021-01-01 19:00:00
10
bar
3
Now I'd like to fill in zeros for the performance when there are no rows satisfying the query. Resulting in this result set:
created
minute_part
worker
avg
2021-01-01 19:00:00
0
bar
0
2021-01-01 19:00:00
1
bar
0
2021-01-01 19:00:00
2
bar
0
2021-01-01 19:00:00
3
bar
0
2021-01-01 19:00:00
4
bar
0
2021-01-01 19:00:00
5
bar
0
2021-01-01 19:00:00
6
bar
0
2021-01-01 19:00:00
7
bar
0
2021-01-01 19:00:00
8
bar
0
2021-01-01 19:00:00
9
bar
0
2021-01-01 19:00:00
10
bar
3
2021-01-01 19:00:00
11
bar
0
2021-01-01 19:00:00
12
bar
0
2021-01-01 19:00:00
13
bar
0
2021-01-01 19:00:00
14
bar
0
2021-01-01 19:00:00
15
bar
0
2021-01-01 19:00:00
16
bar
0
2021-01-01 19:00:00
17
bar
0
2021-01-01 19:00:00
18
bar
0
2021-01-01 19:00:00
19
bar
0
Table:
CREATE TABLE perf_stats
(
id BIGSERIAL NOT NULL PRIMARY KEY,
group_id TEXT NOT NULL,
worker TEXT NOT NULL,
performance DOUBLE PRECISION NOT NULL DEFAULT 0,
created TIMESTAMP NOT NULL
);
CREATE INDEX IDX_PERFSTATS_CREATED on perf_stats(group_id, created);
Sample Data:
INSERT INTO perf_stats (group_id, worker, performance, created)
VALUES('foo', 'bar', 2, '1.1.2021 19:30:00');
INSERT INTO perf_stats (group_id, worker, performance, created)
VALUES('foo', 'bar', 4, '1.1.2021 19:31:00');
I've experimented with generate_series but did not get it working so far.

You seem to want to generate rows for all the minutes. You can use generate_series() for this and then a LEFT JOIN:
SELECT date_trunc('hour', gs.ts) AS created,
(extract(minute FROM gs.ts)::int / 3) minute_part,
worker,
AVG(performance)
FROM generate_series('2021-01-01 19:00:00'::timestamp, '2021-01-01 20:00:00'::timestamp, interval '3 minute') gs(ts) LEFT JOIN
perf_stats ps
ON ps.group_id = 'foo' AND
ps.worker = 'bar' AND
ps.created >= gs.ts AND
ps.created < gs.ts + interval '3 minute'
GROUP BY 1, 2, worker
ORDER BY 1, 2, worker;

Related

How to dynamically add rows with values of last 12 months of data when particular period is missing within the particular last 12 month period?

This is the input Table:
ITEM
QTY
DATEPERIOD
A
2
1/1/2020 0:00
A
3
2/1/2020 0:00
A
4
3/1/2020 0:00
A
1
4/1/2020 0:00
A
2
5/1/2020 0:00
A
2
6/1/2020 0:00
A
2
8/1/2020 0:00
A
2
10/1/2020 0:00
A
2
12/1/2020 0:00
A
2
1/1/2021 0:00
A
3
2/1/2021 0:00
A
4
3/1/2021 0:00
A
2
5/1/2021 0:00
A
2
6/1/2021 0:00
A
2
8/1/2021 0:00
A
1
9/1/2021 0:00
A
2
10/1/2021 0:00
A
1
11/1/2021 0:00
A
1
12/1/2021 0:00
This input table does not have data of 2021-July, when I have to calculate the data of last 12 month for each rows, I will be able to get data of last 12 months from dec 2021 to Aug 2021.
But since the input table does not have data of 2021-July, using usual query
SUM(qty) OVER (
PARTITION BY item
ORDER BY dateperiod
RANGE BETWEEN INTERVAL '11' MONTH PRECEDING
AND INTERVAL '0' MONTH FOLLOWING
) AS total,
would generate last 12 month data for June 2021. But the expected output is even if the data is not available in July-2021, is it possible to dynamically generate a row as last 12 month data for July-2021 which should be from July 2021 to Aug 2020. The result of the qty is: 19
similarly, the input table is missing data for April 2021. Then the query generate a row as last 12 month data for April-2021 which should be from April 2021 to May 2020. The result of the qty is: 19
So the expected output will be in the form of
ITEM
DATEPERIOD
Output
A
1/1/2020
2
A
2/1/2020
5
A
3/1/2020
9
A
4/1/2020
10
A
5/1/2020
12
A
6/1/2020
14
A
7/1/2020
14
A
8/1/2020
16
A
9/1/2020
16
A
10/1/2020
18
A
11/1/2020
18
A
12/1/2020
20
A
1/1/2021
20
A
2/1/2021
20
A
3/1/2021
20
A
4/1/2021
19
A
5/1/2021
19
A
6/1/2021
19
A
7/1/2021
19
A
8/1/2021
19
A
9/1/2021
20
A
10/1/2021
20
A
11/1/2021
21
A
12/1/2021
20
Please let me know if this is possible
You can use a hierarchical query to generate a calendar and the use an OUTER JOIN to join it to your data (however, since you are doing it for each item then you probably want a PARTITIONed OUTER JOIN):
WITH calendar (month) AS (
SELECT ADD_MONTHS(min_dp, LEVEL - 1) AS month
FROM (
SELECT MIN(dateperiod) AS min_dp,
MAX(dateperiod) AS max_dp
FROM table_name
)
CONNECT BY ADD_MONTHS(min_dp, LEVEL - 1) <= max_dp
)
SELECT item,
c.month AS dateperiod,
COALESCE(t.qty, 0) AS qty,
SUM(t.qty) OVER (
PARTITION BY t.item
ORDER BY c.month
RANGE BETWEEN INTERVAL '11' MONTH PRECEDING
AND INTERVAL '0' MONTH FOLLOWING
) AS total
FROM calendar c
LEFT OUTER JOIN table_name t
PARTITION BY (t.item)
ON (c.month = t.dateperiod);
Which, for your sample data:
CREATE TABLE table_name (ITEM, QTY, DATEPERIOD) AS
SELECT 'A', 2, DATE '2020-01-01' FROM DUAL UNION ALL
SELECT 'A', 3, DATE '2020-02-01' FROM DUAL UNION ALL
SELECT 'A', 4, DATE '2020-03-01' FROM DUAL UNION ALL
SELECT 'A', 1, DATE '2020-04-01' FROM DUAL UNION ALL
SELECT 'A', 2, DATE '2020-05-01' FROM DUAL UNION ALL
SELECT 'A', 2, DATE '2020-06-01' FROM DUAL UNION ALL
SELECT 'A', 2, DATE '2020-08-01' FROM DUAL UNION ALL
SELECT 'A', 2, DATE '2020-10-01' FROM DUAL UNION ALL
SELECT 'A', 2, DATE '2020-12-01' FROM DUAL UNION ALL
SELECT 'A', 2, DATE '2021-01-01' FROM DUAL UNION ALL
SELECT 'A', 3, DATE '2021-02-01' FROM DUAL UNION ALL
SELECT 'A', 4, DATE '2021-03-01' FROM DUAL UNION ALL
SELECT 'A', 2, DATE '2021-05-01' FROM DUAL UNION ALL
SELECT 'A', 2, DATE '2021-06-01' FROM DUAL UNION ALL
SELECT 'A', 2, DATE '2021-08-01' FROM DUAL UNION ALL
SELECT 'A', 1, DATE '2021-09-01' FROM DUAL UNION ALL
SELECT 'A', 2, DATE '2021-10-01' FROM DUAL UNION ALL
SELECT 'A', 1, DATE '2021-11-01' FROM DUAL UNION ALL
SELECT 'A', 1, DATE '2021-12-01' FROM DUAL;
Outputs:
ITEM
DATEPERIOD
QTY
TOTAL
A
2020-01-01 00:00:00
2
2
A
2020-02-01 00:00:00
3
5
A
2020-03-01 00:00:00
4
9
A
2020-04-01 00:00:00
1
10
A
2020-05-01 00:00:00
2
12
A
2020-06-01 00:00:00
2
14
A
2020-07-01 00:00:00
0
14
A
2020-08-01 00:00:00
2
16
A
2020-09-01 00:00:00
0
16
A
2020-10-01 00:00:00
2
18
A
2020-11-01 00:00:00
0
18
A
2020-12-01 00:00:00
2
20
A
2021-01-01 00:00:00
2
20
A
2021-02-01 00:00:00
3
20
A
2021-03-01 00:00:00
4
20
A
2021-04-01 00:00:00
0
19
A
2021-05-01 00:00:00
2
19
A
2021-06-01 00:00:00
2
19
A
2021-07-01 00:00:00
0
19
A
2021-08-01 00:00:00
2
19
A
2021-09-01 00:00:00
1
20
A
2021-10-01 00:00:00
2
20
A
2021-11-01 00:00:00
1
21
A
2021-12-01 00:00:00
1
20
db<>fiddle here

Get all rows from one table stream and the row before in time from an other table

Suppose I have one table (table_1) and one table stream (stream_1) that gets changes made to table_1, in my case only inserts of new rows. And once I have acted on these changes, the rowes will be removed from stream_1 but remain in table_1.
From that I would like to calculate delta values for var1 (var1 - lag(var1) as delta_var1) partitioned on a customer and just leave var2 as it is. So the data in table_1 could look something like this:
timemessage
customerid
var1
var2
2021-04-01 06:00:00
1
10
5
2021-04-01 07:00:00
2
100
7
2021-04-01 08:00:00
1
20
10
2021-04-01 09:00:00
1
40
3
2021-04-01 15:00:00
2
150
5
2021-04-01 23:00:00
1
50
6
2021-04-02 06:00:00
2
180
2
2021-04-02 07:00:00
1
55
9
2021-04-02 08:00:00
2
200
4
And the data in stream_1 that I want to act on could looks like this:
timemessage
customerid
var1
var2
2021-04-01 23:00:00
1
50
6
2021-04-02 06:00:00
2
180
2
2021-04-02 07:00:00
1
55
9
2021-04-02 08:00:00
2
200
4
But to be able to calculate delta_var1 for all customers I would need the previous row in time for each customer before the ones in stream_1.
For example: To be able to calculate how much var1 has increased for customerid = 1 between 2021-04-01 09:00:00 and 2021-04-01 23:00:00 I want to include the 2021-04-01 09:00:00 row for customerid = 1 in my output.
So I would like to create a select containing all rows in stream_1 + the previous row in time for each customerid from table_1: The wanted output is the following in regard to the mentioned table_1 and stream_1.
timemessage
customerid
var1
var2
2021-04-01 09:00:00
1
40
3
2021-04-01 15:00:00
2
150
5
2021-04-01 23:00:00
1
50
6
2021-04-02 06:00:00
2
180
2
2021-04-02 07:00:00
1
55
9
2021-04-02 08:00:00
2
200
4
So given you have the "last value per day" in your wanted output, you are want a QUALIFY to keep only the wanted rows and using ROW_NUMBER partitioned by customerid and timemessage. Assuming the accumulator it positive only you can order by accumulatedvalue thus:
WITH data(timemessage, customerid, accumulatedvalue) AS (
SELECT * FROM VALUES
('2021-04-01', 1, 10)
,('2021-04-01', 2, 100)
,('2021-04-02', 1, 20)
,('2021-04-03', 1, 40)
,('2021-04-03', 2, 150)
,('2021-04-04', 1, 50)
,('2021-04-04', 2, 180)
,('2021-04-05', 1, 55)
,('2021-04-05', 2, 200)
)
SELECT * FROM data
QUALIFY ROW_NUMBER() OVER (PARTITION BY customerid,timemessage ORDER BY accumulatedvalue DESC) = 1
ORDER BY 1,2;
gives:
TIMEMESSAGE CUSTOMERID ACCUMULATEDVALUE
2021-04-01 1 10
2021-04-01 2 100
2021-04-02 1 20
2021-04-03 1 40
2021-04-03 2 150
2021-04-04 1 50
2021-04-04 2 180
2021-04-05 1 55
2021-04-05 2 200
if you can trust your data and data in table2 starts right after data in table1 then you can just get the last records for each customer from table1 and union with table2:
select * from table1
qualify row_number() over (partitioned by customerid order by timemessage desc) = 1
union all
select * from table2
if not
select a.* from table1 a
join table2 b
on a.customerid = b.customerid
and a.timemessage < b.timemessage
qualify row_number() over (partitioned by a.customerid order by a.timemessage desc) = 1
union all
select * from table2
also you can add a condition to not look to data for more than 1 day (or 1 hour or whatever safe interval is to look at) for better performance

Rank data with history of recurrence

Lets say I'm tracking a user's location, and I capture the following information:
Date
UserId
CurrentLocation
I can do a fairly easy transformation on this data to form a new table to get their last known position, if one exists, which I'll include below to show us moving from one point to another.
I want to create a grouping for each user's current position, and increment it whenever their position has changed from a previous value. If a user leaves a position, and later comes back to it, I want that to be treated as a new value, and not lump it in with the group when they were first at this location.
The problem with using RANK or DENSE_RANK to do this is that I'm ordering by the currentPos, which obviously won't work.
I thought I could use LAG() to look at the previous data, but this doesn't allow you to aggregate the previous record's LAG() with the current row.
Here's an example using RANK()
WITH dummyData(id, occuredOn, userId, currentPos, lastPos) AS (
SELECT 01, '2021-01-01 00:00:00', 23, 'A', null
UNION ALL
SELECT 22, '2021-01-01 01:30:00', 23, 'A', 'A'
UNION ALL
SELECT 43, '2021-01-01 04:00:00', 23, 'B', 'A'
UNION ALL
SELECT 55, '2021-01-02 00:00:00', 23, 'C', 'B'
UNION ALL
SELECT 59, '2021-01-02 04:40:00', 23, 'B', 'C'
UNION ALL
SELECT 68, '2021-01-02 08:00:00', 23, 'C', 'B'
UNION ALL
SELECT 69, '2021-01-02 09:00:00', 23, 'D', 'C'
UNION ALL
SELECT 11, '2021-01-01 01:00:00', 43, 'X', 'X'
UNION ALL
SELECT 18, '2021-01-01 02:00:00', 43, 'Y', 'X'
UNION ALL
SELECT 32, '2021-01-02 00:00:00', 43, 'Z', 'Y'
)
SELECT *
, DENSE_RANK() OVER (PARTITION BY userId ORDER BY currentPos) locationChangeGroup
FROM dummyData
ORDER BY userId ASC, occuredOn ASC
Here's what it outputs
id
occurredOn
userId
currentPos
lastPos
locationChangeGroup
01
2021-01-01 00:00:00
23
A
NULL
1
22
2021-01-01 01:30:00
23
A
A
1
43
2021-01-01 04:00:00
23
B
A
2
55
2021-01-02 00:00:00
23
C
B
3
59
2021-01-02 04:40:00
23
B
C
2
68
2021-01-02 08:00:00
23
C
B
3
69
2021-01-02 09:00:00
23
D
C
4
11
2021-01-01 01:00:00
43
X
X
1
18
2021-01-01 02:00:00
43
Y
X
2
32
2021-01-02 00:00:00
43
Z
Y
3
Here's what I want
id
occurredOn
userId
currentPos
lastPos
locationChangeGroup
01
2021-01-01 00:00:00
23
A
NULL
1
22
2021-01-01 01:30:00
23
A
A
1
43
2021-01-01 04:00:00
23
B
A
2
55
2021-01-02 00:00:00
23
C
B
3
59
2021-01-02 04:40:00
23
B
C
4
68
2021-01-02 08:00:00
23
C
B
5
69
2021-01-02 09:00:00
23
D
C
6
11
2021-01-01 01:00:00
43
X
X
1
18
2021-01-01 02:00:00
43
Y
X
2
32
2021-01-02 00:00:00
43
Z
Y
3
I know I could do this with a CURSOR, but I'd rather not resort to that.
T-SQL is fine, but I'm trying to stay away from any stored procs or functions, as it will require a larger effort of generating database migration scripts and the rigamarole of our processes that entails.
Any suggestions?
I think this is a gap-and-islands problem. For this purpose, you can use lag() and a cumulative sum:
select dd.*,
sum(case when prev_currentpos = currentpos then 0 else 1 end) over
(partition by userid
order by occurredon
) as locationChangeGroup
from (select dd.*,
lag(currentpos) over (partition by userid order by occurredon) as prev_currentpos
from dummydata dd
) dd

Oracle, SQL, how to get intervals between dates

I need help with a problem. Actually, I do not know if it will be possible to solve it directly in SQL.
I have a list of works. Each work has a start date and ending date, with this format
YYYY/MM/DD HH24:MI:SS
I need to calculate the cost of those jobs, the hour price depends on the time intervals in which the work has been done:
Nigth time: 22:00 to 6:00, for example: 20 €/h
Normal time: the rest 17 €/h
So, if I have a sample like this:
wo start end
21 2017/11/16 21:25:00 2017/11/16 22:55:00
22 2017/11/17 05:45:00 2017/11/17 07:05:00
23 2017/11/18 23:00:00 2017/11/19 1:10:00
24 2017/11/17 18:00:00 2017/11/17 19:00:00
I would need to calculate the intervals of the dates between the 22h and 6h and the rest to multiply them by their corresponding price
wo rest(minutes) night(minutes)
21 35 55
22 15 65
23 0 130
24 1 0
Thank for your help in advance.
Heh. If you really wish it :)
Fifth record (started at 2016-10-30) had been added for testing purposes.
SQL> with
2 src as (select timestamp '2017-11-16 21:25:00' b, timestamp '2017-11-16 22:55:00' f from dual union all
3 select timestamp '2017-11-17 05:45:00' b, timestamp '2017-11-17 07:05:00' f from dual union all
4 select timestamp '2017-11-18 23:00:00' b, timestamp '2017-11-19 1:10:00' f from dual union all
5 select timestamp '2017-11-17 18:00:00' b, timestamp '2017-11-17 19:00:00' f from dual union all
6 select timestamp '2016-10-30 00:00:00' b, timestamp '2016-11-03 23:00:00' f from dual),
7 srd as (select b, f, f - b t from src),
8 mmm as (select min(trunc(b)) b, max(trunc(f)) f from src),
9 rws as (select b + 6/24 + rownum - 1 b, b + 22/24 + rownum - 1 f from mmm connect by level <= f - b + 1),
10 mix as (select s.b, s.f, s.t, r.b rb, r.f rf from srd s, rws r where s.f >= r.b (+) and r.f (+) >= s.b),
11 clc as (select b, f, t, nvl(numtodsinterval(sum((least(f, rf) + 0) - (greatest(b, rb) + 0)), 'DAY'), interval '0' second) d from mix group by b, f, t)
12 select
13 to_char(b, 'dd.mm.yyyy hh24:mi') as "datetime begin",
14 to_char(f, 'dd.mm.yyyy hh24:mi') as "datetime finish",
15 cast(t as interval day to second(0)) as "total time",
16 cast(d as interval day to second(0)) as "daytime",
17 cast(t - d as interval day to second(0)) as "nighttime"
18 from
19 clc
20 order by
21 1, 2;
datetime begin datetime finish total time daytime nighttime
------------------ ------------------ -------------- -------------- --------------
16.11.2017 21:25 16.11.2017 22:55 +00 01:30:00 +00 00:35:00 +00 00:55:00
17.11.2017 05:45 17.11.2017 07:05 +00 01:20:00 +00 01:05:00 +00 00:15:00
17.11.2017 18:00 17.11.2017 19:00 +00 01:00:00 +00 01:00:00 +00 00:00:00
18.11.2017 23:00 19.11.2017 01:10 +00 02:10:00 +00 00:00:00 +00 02:10:00
30.10.2016 00:00 03.11.2016 23:00 +04 23:00:00 +03 08:00:00 +01 15:00:00
A different approach is more brute force one, but it allows to distinct the interval configuration from the reporting.
It goes in three stept:
1) define the rate type for aech minute of the day (change the granularity if required)
create table day_config as
with helper as (
select
rownum -1 minute_id
from dual connect by level <= 24*60),
helper2 as (
select
minute_id,
trunc(minute_id/60) hour_no,
mod(minute_id,60) minute_no
from helper)
select
minute_id,hour_no, minute_no,
case when hour_no >= 22 or hour_no <= 5 then 0 else 1 end rate_id
from helper2;
select * from day_config order by minute_id;
MINUTE_ID HOUR_NO MINUTE_NO RATE_ID
---------- ---------- ---------- ----------
0 0 0 0
1 0 1 0
2 0 2 0
3 0 3 0
4 0 4 0
5 0 5 0
6 0 6 0
7 0 7 0
8 0 8 0
9 0 9 0
Here rate_id means nigth, rate_id 1 means a day.
Advantage is, that you can introduce as much rate types as required.
2) expand the configuration for the required interval e.g. to whole year.
So now we have for each minute of the year the configuration, which rate is to be applied.
create or replace view year_config as
select my_date + MINUTE_ID / (24*60) minute_ts , MINUTE_ID, HOUR_NO, MINUTE_NO, RATE_ID from day_config
cross join
(select DATE '2017-01-01' + rownum -1 as my_date from dual connect by level <= 365)
order by 1,2;
select * from (
select * from year_config
order by 1)
where rownum <= 5;
MINUTE_TS MINUTE_ID HOUR_NO MINUTE_NO RATE_ID
------------------- ---------- ---------- ---------- ----------
01-01-2017 00:00:00 0 0 0 0
01-01-2017 00:01:00 1 0 1 0
01-01-2017 00:02:00 2 0 2 0
01-01-2017 00:03:00 3 0 3 0
01-01-2017 00:04:00 4 0 4 0
3) the reporting is as easy as joining to our config table constraining the interval (half open) and grouping in the RATE.
select b, f,RATE_ID, count(*) minute_cnt
from tst join year_config c on c.MINUTE_TS >= tst.b and c.MINUTE_TS < tst.f
group by b, f,RATE_ID
order by b, f,RATE_ID;
B F RATE_ID MINUTE_CNT
------------------- ------------------- ---------- ----------
16-11-2017 21:25:00 16-11-2017 22:55:00 0 55
16-11-2017 21:25:00 16-11-2017 22:55:00 1 35
17-11-2017 05:45:00 17-11-2017 07:05:00 0 15
17-11-2017 05:45:00 17-11-2017 07:05:00 1 65
17-11-2017 18:00:00 17-11-2017 19:00:00 1 60
18-11-2017 23:00:00 19-11-2017 01:10:00 0 130
The easiest way is probably to get all minutes worked in a recursive WITH clause and then see in which time range the minutes fall. As Oracle doesn't have a TIME datatype unfortunately, we'll have to work with times strings ('00'00' till '23:59').
with shifts as
(
select 'night' as shift, '00:00' as starttime, '05:59' as endtime, 20 as cost from dual
union all
select 'normal' as shift, '06:00' as starttime, '21:59' as endtime, 17 as cost from dual
union all
select 'night' as shift, '22:00' as starttime, '23:59' as endtime, 20 as cost from dual
)
, workminutes(wo, workminute, thetime, endtime) as
(
select wo, to_char(starttime, 'hh24:mi') as workminute, starttime as thetime, endtime
from mytable
union all
select
wo,
to_char(thetime + interval '1' minute, 'hh24:mi') as workminute,
thetime + interval '1' minute as thetime,
endtime
from workminutes
where thetime + interval '1' minute < endtime
)
select
wo,
count(case when s.shift = 'normal' then 1 end) as normal_time,
coalesce(sum(case when m.workminute between '06:00' and '21:59' then s.cost end), 0)
as normal_cost,
count(case when s.shift = 'night' then 1 end) as night_time,
coalesce(sum(case when m.workminute not between '06:00' and '21:59' then s.cost end), 0)
as night_cost,
count(*) as total_time,
coalesce(sum(s.cost), 0)
as total_cost
from workminutes m
join shifts s on m.workminute between s.starttime and s.endtime
group by wo
order by wo;
Output:
WO NORMAL_TIME NORMAL_COST NIGHT_TIME NIGHT_COST TOTAL_TIME TOTAL_COST
21 35 595 55 1100 90 1695
22 65 1105 15 300 80 1405
23 0 0 130 2600 130 2600
24 60 1020 0 0 60 1020
25 4800 81600 2340 46800 7140 128400
(This query looks a lot nicer of course, if you have a real shifts table and don't have to make one up on-the-fly. Also, you may not need all those seven columns I have in my result.)

Transposing SQLite rows and columns with average per hour

I have a table in SQLite called param_vals_breaches that looks like the following:
id param queue date_time param_val breach_count
1 c a 2013-01-01 00:00:00 188 7
2 c b 2013-01-01 00:00:00 156 8
3 c c 2013-01-01 00:00:00 100 2
4 d a 2013-01-01 00:00:00 657 0
5 d b 2013-01-01 00:00:00 23 6
6 d c 2013-01-01 00:00:00 230 12
7 c a 2013-01-01 01:00:00 100 0
8 c b 2013-01-01 01:00:00 143 9
9 c c 2013-01-01 01:00:00 12 2
10 d a 2013-01-01 01:00:00 0 1
11 d b 2013-01-01 01:00:00 29 5
12 d c 2013-01-01 01:00:00 22 14
13 c a 2013-01-01 02:00:00 188 7
14 c b 2013-01-01 02:00:00 156 8
15 c c 2013-01-01 02:00:00 100 2
16 d a 2013-01-01 02:00:00 657 0
17 d b 2013-01-01 02:00:00 23 6
18 d c 2013-01-01 02:00:00 230 12
I want to write a query that will show me a particular queue (e.g. "a") with the average param_val and breach_count for each param on an hour by hour basis. So transposing the data to get something that looks like this:
Results for Queue A
Hour 0 Hour 0 Hour 1 Hour 1 Hour 2 Hour 2
param avg_param_val avg_breach_count avg_param_val avg_breach_count avg_param_val avg_breach_count
c xxx xxx xxx xxx xxx xxx
d xxx xxx xxx xxx xxx xxx
is this possible? I'm not sure how to go about it. Thanks!
SQLite does not have a PIVOT function but you can use an aggregate function with a CASE expression to turn the rows into columns:
select param,
avg(case when time = '00' then param_val end) AvgHour0Val,
avg(case when time = '00' then breach_count end) AvgHour0Count,
avg(case when time = '01' then param_val end) AvgHour1Val,
avg(case when time = '01' then breach_count end) AvgHour1Count,
avg(case when time = '02' then param_val end) AvgHour2Val,
avg(case when time = '02' then breach_count end) AvgHour2Count
from
(
select param,
strftime('%H', date_time) time,
param_val,
breach_count
from param_vals_breaches
where queue = 'a'
) src
group by param;
See SQL Fiddle with Demo