How can I write a SQL query to map rows into columns? - sql

Consider the following query from a database,
SELECT propertyname,propertyvalue,value,
FROM db
which returns
| propertyname | propertyvalue | value |
+--------------------+----------------+----------+
| AnimalNumber | 1 | 1.3 |
| Group | 1 | 1.3 |
| TimePoint | 24 days | 1.3 |
| Treatment method | vehicle | 1.3 |
| Treatment Conc | 0 | 1.3 |
| AnimalNumber | 2 | 0.5 |
| Group | 3 | 0.5 |
| TimePoint | 7 days | 0.5 |
| Treatment method | vehicle | 0.5 |
| Treatment Conc | 0 | 0.5 |
We can see it's the case that multiple rows map to the same 'value' datapoint. Really, it should be the case that each of those 5 properties (animal number, group, time point, treatment method, treatment conc) should be columns and the first 5 data points should be condensed into a single row with a value=1.3. In other words, it should be something like
| AnimalNumber | Group | TimePoint | Treatment method | Treatment Conc | value
+--------------+-------+-----------+------------------+-----------------+------+
| 1 | 1 | 24 days | vehicle | 0 | 1.3 |
| 2 | 3 | 7 days | vehicle | 0 | 0.5 |
Also note that it is not necessarily the case that there is a 1-1 mapping to value, multiple combinations of animal number, group, timepoint, treatment method, and treatment conc can map to to the same value. So grouping by value I do not believe is the proper approach. Also note that all data provided here is, of course, fabricated and not real.

You can use PIVOT:
SELECT *
FROM table_name
PIVOT(
MAX(propertyvalue)
FOR propertyname IN (
'AnimalNumber' AS animalnumber,
'Group' AS "GROUP",
'TimePoint' AS TimePoint,
'Treatment method' AS TreatementMethod,
'Treatment Conc' AS TreatmentConc
)
)
Which, for your sample data:
CREATE TABLE table_name ( propertyname, propertyvalue, value ) AS
SELECT 'AnimalNumber', '1', 1.3 FROM DUAL UNION ALL
SELECT 'Group', '1', 1.3 FROM DUAL UNION ALL
SELECT 'TimePoint', '24 days', 1.3 FROM DUAL UNION ALL
SELECT 'Treatment method', 'vehicle', 1.3 FROM DUAL UNION ALL
SELECT 'Treatment Conc', '0', 1.3 FROM DUAL UNION ALL
SELECT 'AnimalNumber', '2', 0.5 FROM DUAL UNION ALL
SELECT 'Group', '3', 0.5 FROM DUAL UNION ALL
SELECT 'TimePoint', '7 days', 0.5 FROM DUAL UNION ALL
SELECT 'Treatment method', 'vehicle', 0.5 FROM DUAL UNION ALL
SELECT 'Treatment Conc', '0', 0.5 FROM DUAL;
Outputs:
VALUE
ANIMALNUMBER
GROUP
TIMEPOINT
TREATEMENTMETHOD
TREATMENTCONC
0.5
2
3
7 days
vehicle
0
1.3
1
1
24 days
vehicle
0
db<>fiddle here

one way is to group by value :
select max(propertyvalue) filter(where propertyname='AnimalNumber') AnimalNumber
, max(propertyvalue) filter(where propertyname='TimePoint') TimePoint
, max(propertyvalue) filter(where propertyname='Treatment method') "Treatment method"
, max(propertyvalue) filter(where propertyname='Treatment Conc') "Treatment Conc"
, max(propertyvalue) filter(where propertyname='Group') "Group"
from tablename
group by value

Related

oracle group by when value is zero, aggregate = 0

i am trying to build a query but it's taking me too much time to resolve it.
Oracle database v18
this is my table1
Date1
tagname
Value
01/01/2021 0:01
a
2
01/01/2021 0:02
a
4
01/01/2021 0:01
b
2
01/01/2021 0:02
b
4
01/01/2021 0:01
c
2
01/01/2021 0:02
c
4
02/01/2021 0:01
a
0
02/01/2021 0:02
a
0
02/01/2021 0:01
b
2
02/01/2021 0:02
b
4
02/01/2021 0:01
c
2
02/01/2021 0:02
c
4
i am doing an average by day
select avg(value) value, tagname, to_date(date1,'dd/MM/yyyy')
from table1
group by date1, tagname
Result:
Date1
tagname
Value
01/01/2021
a
3
01/01/2021
b
3
01/01/2021
c
3
02/01/2021
a
0
02/01/2021
b
3
02/01/2021
c
3
now i need to add a new tagname
select sum(value), 'newtag' tagname
from result
where tagname= 'a' or tagname = 'b' or tagname= 'c'
group by date1
but when a=0 newtag value = 0
How could i archieve this?
example
Date1
tagname
Value
01/01/2021
a
3
01/01/2021
b
3
01/01/2021
c
3
01/01/2021
newtag
9
02/01/2021
a
0
02/01/2021
b
3
02/01/2021
c
3
02/01/2021
newtag
0
could i use case in this query?
thanks in advance
Edit: table1 have more tagnames, but only need to sum(a+b+c)
So, this is easy to do with UNION ALL, of course. I guess your concern is that you do not want to read through your table twice (once to calculate date/tag aggregates and again to calculate date aggregates).
Anytime you want to aggregate query results at multiple levels, you should at least consider GROUPING SETS functionality.
The trick in your case isn't the multiple-level aggregates. Rather, it is that you want the 2nd level aggregate (by date) to be the SUM() of aggregates calculated at the first level (by date/tag).
To do that, you can use a window function to compute the AVG() by date/tag before any aggregates are done at all. That makes it possible to SUM() them later. Here is a working example (Oracle 12.1):
-- Create table with test data
create table my_table1 (Date1, tagname, Value) AS (
SELECT TO_DATE('01/01/2021 0:01','DD/MM/YYYY HH24:MI'), 'a', 2 FROM DUAL UNION ALL
SELECT TO_DATE('01/01/2021 0:02','DD/MM/YYYY HH24:MI'), 'a', 4 FROM DUAL UNION ALL
SELECT TO_DATE('01/01/2021 0:01','DD/MM/YYYY HH24:MI'), 'b', 2 FROM DUAL UNION ALL
SELECT TO_DATE('01/01/2021 0:02','DD/MM/YYYY HH24:MI'), 'b', 4 FROM DUAL UNION ALL
SELECT TO_DATE('01/01/2021 0:01','DD/MM/YYYY HH24:MI'), 'c', 2 FROM DUAL UNION ALL
SELECT TO_DATE('01/01/2021 0:02','DD/MM/YYYY HH24:MI'), 'c', 4 FROM DUAL UNION ALL
SELECT TO_DATE('02/01/2021 0:01','DD/MM/YYYY HH24:MI'), 'a', 0 FROM DUAL UNION ALL
SELECT TO_DATE('02/01/2021 0:02','DD/MM/YYYY HH24:MI'), 'a', 0 FROM DUAL UNION ALL
SELECT TO_DATE('02/01/2021 0:01','DD/MM/YYYY HH24:MI'), 'b', 2 FROM DUAL UNION ALL
SELECT TO_DATE('02/01/2021 0:02','DD/MM/YYYY HH24:MI'), 'b', 4 FROM DUAL UNION ALL
SELECT TO_DATE('02/01/2021 0:01','DD/MM/YYYY HH24:MI'), 'c', 2 FROM DUAL UNION ALL
SELECT TO_DATE('02/01/2021 0:02','DD/MM/YYYY HH24:MI'), 'c', 4 FROM DUAL
)
;
-- Compute the averages and the use GROUPING SETS to use those those
-- averages conditionally at multiple levels of aggregation
with date_tag_summary as (
select trunc(date1) date1, tagname, avg(value) avg_value
from my_table1
group by trunc(date1), tagname )
select date1,
case when grouping(tagname)=1 then 'newtag' ELSE tagname END tagname,
case when grouping(tagname)=1 AND COUNT(DECODE(avg_value,0,1,NULL)) > 0 THEN 0
when grouping(tagname)=1 THEN sum(avg_value)
ELSE min(avg_value) END value
from date_tag_summary
group by grouping sets ( (date1, tagname), (date1) )
order by 1,2;
+-----------+---------+-------+
| DATE1 | TAGNAME | VALUE |
+-----------+---------+-------+
| 01-JAN-21 | a | 3 |
| 01-JAN-21 | b | 3 |
| 01-JAN-21 | c | 3 |
| 01-JAN-21 | newtag | 9 |
| 02-JAN-21 | a | 0 |
| 02-JAN-21 | b | 3 |
| 02-JAN-21 | c | 3 |
| 02-JAN-21 | newtag | 0 |
+-----------+---------+-------+
And, to illustrate that the data is not being read twice, here is the execution plan for that query:
-----------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | | | 6 (100)| |
| 1 | SORT ORDER BY | | 3 | 63 | 6 (50)| 00:00:01 |
| 2 | SORT GROUP BY ROLLUP| | 3 | 63 | 6 (50)| 00:00:01 |
| 3 | VIEW | | 9 | 189 | 4 (25)| 00:00:01 |
| 4 | HASH GROUP BY | | 9 | 117 | 4 (25)| 00:00:01 |
| 5 | TABLE ACCESS FULL| MY_TABLE1 | 12 | 156 | 3 (0)| 00:00:01 |
-----------------------------------------------------------------------------------
One method generates the rows using a cross join and then brings in the existing results:
select d.date1, t.tagname, avg(value) value
from (select distinct to_date(date1, 'dd/MM/yyyy') as date1 from table1
) d cross join
(select 'a' as tagname from dual union all
select 'b' as tagname from dual union all
select 'c' as tagname from dual union all
select 'd' as tagname from dual
) t
table1 t1
on to_date(t1.date1, 'dd/MM/yyyy') = d.date1 and
t1.tagname = t.tagname
group by date1, tagname
You may use grouping sets and then replace avg total for group with sum of averages, calculated by analytic function.
select /*+ gather_plan_statistics */
trunc(date1) as dt
, case grouping_id(tagname)
when 0
then tagname
else 'newtag'
end as tagname
, case grouping_id(tagname)
when 0
then avg(value)
else (
/*Total sum except total avg*/
sum(avg(value)) over(
partition by trunc(date1)
) - avg(value))
* decode(min(avg(value)) over(partition by trunc(date1)), 0, 0, 1)
end as val
from a
group by grouping sets( (trunc(date1), tagname), trunc(date1))
DT | TAGNAME | VAL
:-------- | :------ | --:
01-JAN-21 | a | 3
01-JAN-21 | b | 3
01-JAN-21 | c | 3
01-JAN-21 | newtag | 9
02-JAN-21 | a | 0
02-JAN-21 | b | 3
02-JAN-21 | c | 3
02-JAN-21 | newtag | 0
db<>fiddle here
you can use following query. Of course it is set in SQL
;WITH cte AS
(SELECT convert(date,date1) as date1,tagname,avg(value) value
FROM table1
GROUP BY convert(date,date1),tagname)
select date1,tagname,
case when tagname = 'newtag'
then
case (select cte.value from cte where cte.date1 = result.date1 and cte.tagname = 'a')
when 0 then 0
else (select top 1 sum(c.value) from cte c where convert(date,c.date1,103) = result.date1)
end
else value end
from
(select date1,tagname,value ,ROW_NUMBER() over(partition by date1,tagname order by date1) as seq
from
(
select convert(date,date1) as date1,tagname,avg(value) as value
from table1
group by convert(date,date1),tagname
union all
select convert(date,date1),'newtag', 0
from table1
group by convert(date,date1),tagname
) T
) result
where result.seq = 1
order by convert(date,date1)
first average by day
with avgday as (select avg(value) value, tagname, to_date(date1,'dd/MM/yyyy')
from table1 group by date1, tagname)
transform row's to columns and doing a case to filter and operate.
with query1 as (SELECT * FROM avgday PIVOT ( MAX(value) FOR tagname IN ('a','b','c')))
select date1, case
when query1.a=0
then 0
else a + b + c end value,
'newtag' tagname
from query1
I finally came up with a solution, sure it's not the best answer, but it solves my problem

Filtering a table via another table's values

I have 2 tables:
Value
+----+-------+
| id | name |
+----+-------+
| 1 | Peter |
| 2 | Jane |
| 3 | Joe |
+----+-------+
Filter
+----+---------+------+
| id | valueid | type |
+----+---------+------+
| 1 | 1 | A |
| 2 | 1 | B |
| 3 | 1 | C |
| 4 | 1 | D |
| 5 | 2 | A |
| 6 | 2 | C |
| 7 | 2 | E |
| 8 | 3 | A |
| 9 | 3 | D |
+----+---------+------+
I need to retrieve the values from the Value table where the related Filter table does not contain the type 'B' or 'C'
So in this quick example this would be only Joe.
Please note this is a DB2 DB and i have limited permissions to run selects only.
Or also a NOT IN (<*fullselect*) predicate:
Only that my result is 'Joe', not 'Jane' - and the data constellation would point to that ...
WITH
-- your input, sans reserved words
val(id,nam) AS (
SELECT 1,'Peter' FROM sysibm.sysdummy1
UNION ALL SELECT 2,'Jane' FROM sysibm.sysdummy1
UNION ALL SELECT 3,'Joe' FROM sysibm.sysdummy1
)
,
filtr(id,valueid,typ) AS (
SELECT 1,1,'A' FROM sysibm.sysdummy1
UNION ALL SELECT 2,1,'B' FROM sysibm.sysdummy1
UNION ALL SELECT 3,1,'C' FROM sysibm.sysdummy1
UNION ALL SELECT 4,1,'D' FROM sysibm.sysdummy1
UNION ALL SELECT 5,2,'A' FROM sysibm.sysdummy1
UNION ALL SELECT 6,2,'C' FROM sysibm.sysdummy1
UNION ALL SELECT 7,2,'E' FROM sysibm.sysdummy1
UNION ALL SELECT 8,3,'A' FROM sysibm.sysdummy1
UNION ALL SELECT 9,3,'D' FROM sysibm.sysdummy1
)
-- real query starts here
SELECT
*
FROM val
WHERE id NOT IN (
SELECT valueid FROM filtr WHERE typ IN ('B','C')
)
;
-- out id | nam
-- out ----+-------
-- out 3 | Joe
Or also, a failing left join:
SELECT
val.*
FROM val
LEFT JOIN (
SELECT valueid FROM filtr WHERE typ IN ('B','C')
) filtr
ON filtr.valueid = val.id
WHERE valueid IS NULL
You can use EXISTS, as in:
select *
from value v
where not exists (
select null from filter f
where f.valueid = v.id and f.type in ('B', 'C')
);
Result:
ID NAME
--- -----
3 Joe
See running example at db<>fiddle.

Conditional operations without using SWITCH CASE

I have a couple of complex tables. But their mapping is something like:
TABLE_A:
_________________________________
| LINK_ID | TYPE_ID |
_________________________________
| adfasdnf23454 | TYPE 1 |
| 43fsdfsdcxsh7 | TYPE 1 |
| dfkng037sdfbd | TYPE 1 |
| sd09734fdfhsf | TYPE 2 |
| khsadf94u5dfc | TYPE 2 |
| piukvih66wfa8 | TYPE 3 |
_________________________________
TABLE_B:
_____________________________________________
| LINK_ID | CODE_ID | CODE_VALUE |
_____________________________________________
| adfasdnf23454 | 7 | test 1 |
| fgubk65esdfj7 | 6 | test 2 |
| ooogfsg354fds | 7 | test 3 |
| sd09734fdfhsf | 5 | test 4 |
_____________________________________________
The LINK_ID column links these two tables.
My requirement is to have all the records from TABLE_A checked whether they have a specific CODE_ID or not.
If the record has CODE_ID as 7 - populate CODE_VALUE in a column.
If the record has CODE_ID as 6 - populate CODE_VALUE in another column.
If the record doesn't have a CODE_ID show CODE_VALUE as null.
The catch is, TABLE_B may have records that TABLE_A don't. But the final result should contain the records of TABLE_A alone.
PS: SWITCH CASE not suggested since I would require the fields in the same row. (Please see the multiple rows for same LINK_ID in OBTAINED RESULT on using SWITCH CASE)
OBTAINED RESULT:
_______________________________________________
| LINK_ID | CODE_VALUE_1 | CODE_VALUE_1 |
_______________________________________________
| adfasdnf23454 | test 1 | null |
| adfasdnf23454 | null | test 4 |
| sd09734fdfhsf | test 6 | null |
_______________________________________________
EXPECTED RESULT:
__________________________________________________
| LINK_ID | CODE_VALUE_1 | CODE_VALUE_2 |
__________________________________________________
| adfasdnf23454 | test 1 | test 4 |
| 43fsdfsdcxsh7 | null | null |
| dfkng037sdfbd | null | null |
| sd09734fdfhsf | test 6 | null |
| khsadf94u5dfc | null | null |
| piukvih66wfa8 | null | null |
__________________________________________________
Can someone help on this?
One option uses two correlated subqueries:
select
a.link_id,
(select code_value from table_b b where b.link_id = a.link_id and b.code_id = 7) code_value_1,
(select code_value from table_b b where b.link_id = a.link_id and b.code_id = 6) code_value_2
from table_a a
Note that this assumes no duplicate (link_id, code_id) in table_b. You could also write this with two left joins - which is quite the same logic.
Another solution is a single left join, then conditional aggregation:
select
a.link_id,
max(case when b.code_id = 7 then b.code_value end) code_value_1,
max(case when b.code_id = 6 then b.code_value end) code_value_2
from table_a a
left join table_b b on b.link_id = a.link_id and b.code_id in (6, 7)
group by a.link_id
Problematic part of your question is what to do if two entries in B have same link_id and type_id. You can use min, max, last entry (but for that you need ordering column in B). Or you can list them all:
select *
from a left join b using (link_id)
pivot (listagg(code_value, ', ') within group (order by code_value)
for code_id in (6 as code6, 7 as code7))
Data:
create table a (link_id, type_id) as (
select 'a', 'TYPE 1' from dual union all
select 'b', 'TYPE 1' from dual union all
select 'c', 'TYPE 1' from dual union all
select 'd', 'TYPE 2' from dual );
create table b(LINK_ID, CODE_ID, CODE_VALUE) as (
select 'a', 6, 'test 1' from dual union all
select 'a', 7, 'test 2' from dual union all
select 'a', 7, 'test 3' from dual union all
select 'b', 7, 'test 4' from dual union all
select 'd', 6, 'test 5' from dual );
Result:
LINK_ID TYPE_ID CODE6 CODE7
a TYPE 1 test 1 test 2, test 3
b TYPE 1 test 4
c TYPE 1
d TYPE 2 test 5
dbfiddle

BigQuery ARRAY_AGG(STRUCT) splitting values basing on column value

I have a BigQuery table like this:
+------+------------+----------+-------+--------+
| Name | Date | Category | Value | Number |
+------+------------+----------+-------+--------+
| John | 2019-01-03 | Cat1 | AA | 10 |
| John | 2019-01-03 | Cat1 | AB | 11 |
| John | 2019-01-03 | Cat2 | NN | 12 |
| John | 2019-01-03 | Cat2 | MM | 13 |
+------+------------+----------+-------+--------+
The first 2 columns are the key identifier and I need to ARRAY/GROUP the rows basing on those 2 columns.
Here is the sample statement:
WITH data AS (
SELECT "John" name, DATE("2019-01-03") date, "cat1" category, "AA" value, 10 number
UNION ALL
SELECT "John", DATE("2019-01-03"), "cat1", "AB", 11
UNION ALL
SELECT "John", DATE("2019-01-03"), "cat2", "NN", 12
UNION ALL
SELECT "John", DATE("2019-01-03"), "cat2", "MM", 13
)
SELECT * FROM data
The basic version of the query is very simple:
SELECT
name,
date,
ARRAY_AGG(
STRUCT<category STRING, value STRING, number INT64>(category,value,number)
) AS items
FROM data
GROUP BY 1,2
but in my case I need to distinct the values (on 2 different columns) the value-number grouped values based on category column
I don't know if a dynamic column definition can be made, basing on the DISTINCT values of the category values, but in a simplier case I can use fixed values cat1 and cat2
Here an example of the output I described:
+------+------------+--------------------+---------------------+--------------------+---------------------+
| Name | Date | cat1_grouped.value | cat1_grouped.number | cat2_grouped.value | cat2_grouped.number |
+------+------------+--------------------+---------------------+--------------------+---------------------+
| John | 2019-01-03 | AA | 10 | NN | 12 |
| | | AB | 11 | MM | 13 |
| | | | | | |
+------+------------+--------------------+---------------------+--------------------+---------------------+
Below is working example - for BigQuery Standard SQL
#standardSQL
WITH `project.dataset.table` AS (
SELECT 'John' name, DATE '2019-01-03' dt, 'Cat1' category, 'AA' value, 10 number UNION ALL
SELECT 'John', '2019-01-03', 'Cat1', 'AB', 11 UNION ALL
SELECT 'John', '2019-01-03', 'Cat2', 'NN', 12 UNION ALL
SELECT 'John', '2019-01-03', 'Cat2', 'MM', 13
)
SELECT name, dt,
ARRAY_CONCAT_AGG(IF(category = 'Cat1', arr, [])) cat1_grouped,
ARRAY_CONCAT_AGG(IF(category = 'Cat2', arr, [])) cat2_grouped
FROM (
SELECT name, dt, category,
ARRAY_AGG(STRUCT<value STRING, number INT64>(value, number)) arr
FROM `project.dataset.table`
GROUP BY name, dt, category
)
GROUP BY name, dt
with result
Row name dt cat1_grouped.value cat1_grouped.number cat2_grouped.value cat2_grouped.number
1 John 2019-01-03 AA 10 NN 12
AB 11 MM 13

BigQuery/SQL: Sum over intervals indicated by a secondary table

Suppose I have two tables: intervals contains index intervals (its columns are i_min and i_max) and values contains indexed values (with columns i and x). Here's an example:
values: intervals:
+---+---+ +-------+-------+
| i | x | | i_min | i_max |
+-------+ +---------------+
| 1 | 1 | | 1 | 4 |
| 2 | 0 | | 6 | 6 |
| 3 | 4 | | 6 | 6 |
| 4 | 9 | | 6 | 6 |
| 6 | 7 | | 7 | 9 |
| 7 | 2 | | 12 | 17 |
| 8 | 2 | +-------+-------+
| 9 | 2 |
+---+---+
I want to sum the values of x for each interval:
result:
+-------+-------+-----+
| i_min | i_max | sum |
+---------------------+
| 1 | 4 | 13 | // 1+0+4+9
| 6 | 6 | 7 |
| 6 | 6 | 7 |
| 6 | 6 | 7 |
| 7 | 9 | 6 | // 2+2+2
| 12 | 17 | 0 |
+-------+-------+-----+
In some SQL engines, this could be done using:
SELECT
i_min,
i_max,
(SELECT SUM(x)
FROM values
WHERE i BETWEEN intervals.i_min AND intervals.i_max) AS sum_x
FROM
intervals
except that type of query is not allowed by BigQuery ("Subselect not allowed in SELECT clause." or "LEFT OUTER JOIN cannot be used without a condition that is an equality of fields from both sides of the join." depending on the syntax used).
There must be a way to do this with window functions, but I can't figure out how — all examples I've seen have the partition as part of the table. Is there an option that doesn't use CROSS JOIN? If not, what's the most efficient way to do this CROSS JOIN?
Some notes on my data:
Both tables contain many (10⁸-10⁹) rows.
There might be repetitions in intervals, not in i.
But two intervals in intervals are either the same, either entirely disjoint (no overlaps).
The union of all intervals is typically close to the set of all values of i (so it forms a partition of this space).
Intervals might be large (say, i_max-i_min < 10⁶).
Try below - BigQuery Standard SQL
#standardSQL
SELECT
i_min, i_max, SUM(x) AS sum_x
FROM (
SELECT i_min, i_max, ROW_NUMBER() OVER() AS line FROM `project.dataset.intervals`
) AS intervals
JOIN (SELECT i, x FROM `project.dataset.values` UNION ALL SELECT NULL, 0) AS values
ON values.i BETWEEN intervals.i_min AND intervals.i_max OR values.i IS NULL
GROUP BY i_min, i_max, line
-- ORDER BY i_min
you can play/test with dummy data as below
#standardSQL
WITH intervals AS (
SELECT 1 AS i_min, 4 AS i_max UNION ALL
SELECT 6, 6 UNION ALL
SELECT 6, 6 UNION ALL
SELECT 6, 6 UNION ALL
SELECT 7, 9 UNION ALL
SELECT 12, 17
),
values AS (
SELECT 1 AS i, 1 AS x UNION ALL
SELECT 2, 0 UNION ALL
SELECT 3, 4 UNION ALL
SELECT 4, 9 UNION ALL
SELECT 6, 7 UNION ALL
SELECT 7, 2 UNION ALL
SELECT 8, 2 UNION ALL
SELECT 9, 2
)
SELECT
i_min, i_max, SUM(x) AS sum_x
FROM (SELECT i_min, i_max, ROW_NUMBER() OVER() AS line FROM intervals) AS intervals
JOIN (SELECT i, x FROM values UNION ALL SELECT NULL, 0) AS values
ON values.i BETWEEN intervals.i_min AND intervals.i_max OR values.i IS NULL
GROUP BY i_min, i_max, line
-- ORDER BY i_min