Oracle SQL - Creating a view that groups data by timestamp range - sql

I have a table that contains song's names, their genres and the date when they were added.
I'd like to create a view which groups these table's records based on their timestamps. Each group should include records that were inserted at most one second later than the first record of the group.
Here's an example of my table:
+------+----------+-------+-------------------------+
| ID | name | genre | date_added |
+------+----------+-------+-------------------------+
| 1 | aaaa | aaaa | 21/05/21 14:21:54,010 |
| 2 | bbb | bbbb | 21/05/21 14:21:54,020 |
| 3 | qqq | cccc | 21/05/21 14:21:54,500 |
| 4 | ccc | dddd | 21/05/21 14:22:00,000 |
| 5 | www | eeee | 21/05/21 14:22:01,000 |
| 6 | s | ffff | 21/05/21 14:23:00,000 |
+------+----------+-------+-------------------------+
Here's an example of the expected view:
+------+-------------+----------+------------------------+
| ID | first_genre | ids | first_date |
+------+-------------+----------+------------------------+
| 1 | aaaa | [1,2,3] | 21/05/21 14:21:54,010 |
| 2 | dddd | [4,5] | 21/05/21 14:21:54,020 |
| 3 | qqq | [6] | 21/05/21 14:21:54,500 |
+------+-------------+----------+------------------------+
What I'm trying to do is select a record, get its date_added column, then proceed to check the consecutive records to see if they are within a 1 second range from the record that was just selected. If they are, it gets added to this group. Otherwise, I start a new group.
I haven't found a similar solution that creates a list like the one I wanted to, so that's why I'm asking this question. I'm a beginner at both SQL and Oracle SQL, so I don't know how to properly work with timestamp ranges, especially if grouping is involved.
Any help would be really appreciated.

You can't truncate timestamp up to the second, but you can cast it to date. The precision of date is seconds so any fractional seconds will be truncated.
CREATE TABLE test_data(id, sname, genre, date_added) AS
(
SELECT 1, 'aaaa', 'aaaa', TO_TIMESTAMP('21/05/21 14:21:54,010','DD/MM/YY HH24:MI:SS,FF3') FROM DUAL UNION ALL
SELECT 2, 'bbb', 'bbbb', TO_TIMESTAMP('21/05/21 14:21:54,010','DD/MM/YY HH24:MI:SS,FF3') FROM DUAL UNION ALL
SELECT 3, 'qqq', 'cccc', TO_TIMESTAMP('21/05/21 14:21:54,500','DD/MM/YY HH24:MI:SS,FF3') FROM DUAL UNION ALL
SELECT 4, 'ccc', 'dddd', TO_TIMESTAMP('21/05/21 14:22:00,000','DD/MM/YY HH24:MI:SS,FF3') FROM DUAL UNION ALL
SELECT 5, 'www', 'eeee', TO_TIMESTAMP('21/05/21 14:22:01,000','DD/MM/YY HH24:MI:SS,FF3') FROM DUAL UNION ALL
SELECT 6, 's', 'ffff', TO_TIMESTAMP('21/05/21 14:22:01,000','DD/MM/YY HH24:MI:SS,FF3') FROM DUAL
);
CREATE OR REPLACE VIEW genre_list_by_date (
date_added,
id_list,
first_genre
) AS
SELECT
cast(date_added as date),
LISTAGG(id, ', ') WITHIN GROUP(ORDER BY id) AS id_list,
MIN(genre) AS first_genre
FROM
test_data
GROUP BY
cast(date_added as date);
select
TO_CHAR(date_added,'DD/MM/YY HH24:MI:SS'),
id_list,
first_genre
from genre_list_by_date;
date_added id_list first_genre
21/05/21 14:21:54 1, 2, 3 aaaa
21/05/21 14:22:00 4 dddd
21/05/21 14:22:01 5, 6 eeee

Related

Hierarchical query with some joins

I am struggling to write a performing query which would consist of the data from one sub-select, and hierarchically retrieved data from another table based on the rows from that first sub-select.
So, I have some data retrieved from multiple tables with joins, which finally boils down to the following:
CREATE TABLE TBL1 (UUID, MiscData, KeyToLookup, ConditionClause ) AS
SELECT 13, 'ATM', 12345, null FROM DUAL UNION ALL
SELECT 447, 'Balance Inquiry', 67890, 'BALANCE_INQUIRY_FEE' FROM DUAL UNION ALL
SELECT 789, 'Credit', 22321, 'CREDIT_FEE' FROM DUAL;
Now, I have another table which stores the hierarchical structure of fees:
CREATE TABLE TBL2 ( TariffDomainID, FeeType, UpperTariffDomainID, ID ) AS
SELECT 1543, 'WHATEVER_FEE', 154, 1 FROM DUAL UNION ALL
SELECT 1543, 'BALANCE_INQUIRY_FEE', 154, 2 FROM DUAL UNION ALL
SELECT 154, 'SMTHELSE_FEE', 15, 3 FROM DUAL UNION ALL
SELECT 154, 'CREDIT_FEE', 15, 4 FROM DUAL UNION ALL
SELECT 15, 'BALANCE_INQUIRY_FEE', null, 5 FROM DUAL;
And there is a way to link the first selection to the lowest row in hierarchy of the second table, there are few joins but finally it's like this:
CREATE TABLE TBL3 ( ID, FirstTblKey, SecondTblKey ) AS
SELECT 1, 67890, 1543 FROM DUAL UNION ALL
SELECT 2, 22321, 1543 FROM DUAL;
The important point is that it's not guaranteed there will be a row with this KeyToLookup directly in the second table, as directed by the TBL3.
E.g. in the example above:
row TBL1.UUID=789 is linked via TBL3 to TBL2 row with TariffDomainID=1543,
but there is no row in TBL2 with TariffDomainID=1543 and FeeType=CREDIT_FEE;
however TBL2 contains a link to the same table but upper level, UpperTariffDomainID=154,
and there is a row in TBL2 with TariffDomainID=154 and FeeType=CREDIT_FEE.
In the end I need to connect the info from TBL1 with the all occurrences of this key in TBL2 hierarchically, numerated by depth of hierarchy.
So I expect to get this:
| UUID | MiscData | KeyToLookup | ConditionClause | TariffDomainIDWithPresence | Depth |
|------|-----------------|-------------|---------------------|----------------------------|-------|
| 13 | ATM | 12345 | null | null | null |
| 447 | Balance Inquiry | 67890 | BALANCE_INQUIRY_FEE | 1543 | 1 |
| 447 | Balance Inquiry | 67890 | BALANCE_INQUIRY_FEE | 15 | 3 |
| 789 | Credit | 22321 | CREDIT_FEE | 154 | 2 |
Could anyone please teach me how to make such a hierarchical query?
You can use a hierarchical query joined to the other two tables:
SELECT DISTINCT
t1.uuid,
t1.miscdata,
t1.keytolookup,
t1.conditionclause,
t2.tariffdomainid,
t2.depth
FROM tbl1 t1
LEFT OUTER JOIN tbl3 t3
ON ( t1.keytolookup = t3.firsttblkey )
OUTER APPLY (
SELECT tariffdomainid,
LEVEL AS depth
FROM tbl2 t2
WHERE t2.tariffdomainid = t3.secondtblkey
START WITH
t2.feetype = t1.conditionclause
CONNECT BY
PRIOR TariffDomainID = UpperTariffDomainID
) t2
ORDER BY
uuid,
depth
Which, for the sample data, outputs:
UUID | MISCDATA | KEYTOLOOKUP | CONDITIONCLAUSE | TARIFFDOMAINID | DEPTH
---: | :-------------- | ----------: | :------------------ | -------------: | ----:
13 | ATM | 12345 | null | null | null
447 | Balance Inquiry | 67890 | BALANCE_INQUIRY_FEE | 1543 | 1
447 | Balance Inquiry | 67890 | BALANCE_INQUIRY_FEE | 1543 | 3
789 | Credit | 22321 | CREDIT_FEE | 1543 | 2
(Note: you need the DISTINCT as there are multiple 1543 and 154 entries in TBL2 so the hierarchical query can take multiple paths to get from the start to the end condition. If your actual data does not have these duplicates then you should be able to remove the DISTINCT clause.)
db<>fiddle here

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

Oracle: How can I pivot an EAV table with a dynamic cardinality for certain keys?

I have the following Entity–attribute–value (EAV) table in Oracle:
| ID | Key | Value |
|----|-------------|--------------|
| 1 | phone_num_1 | 111-111-1111 |
| 1 | phone_num_2 | 222-222-2222 |
| 1 | contact_1 | friend |
| 1 | contact_2 | family |
| 1 | first_name | mike |
| 1 | last_name | smith |
| 2 | phone_num_1 | 333-333-3333 |
| 2 | phone_num_2 | 444-444-4444 |
| 2 | contact_1 | family |
| 2 | contact_2 | friend |
| 2 | first_name | john |
| 2 | last_name | adams |
| 3 | phone_num_1 | 555-555-5555 |
| 3 | phone_num_2 | 666-666-6666 |
| 3 | phone_num_3 | 777-777-7777 |
| 3 | contact_1 | work |
| 3 | contact_2 | family |
| 3 | contact_3 | friend |
| 3 | first_name | mona |
| 3 | last_name | lisa |
Notice that some keys are indexed and therefore have an association with other indexed keys. For example, phone_num_1 is to be associated with contact_1.
Note: There is no hard limit to the number of indexes. There can be 10, 20, or even 50 phone_num_*, but it's guaranteed that for each phone_num_N, there is a corresponding contact_N
This is my desired result:
| ID | Phone_Num | Contact | First_Name | Last_Name |
|----|--------------|---------|------------|-----------|
| 1 | 111-111-1111 | friend | mike | smith |
| 1 | 222-222-2222 | family | mike | smith |
| 2 | 333-333-3333 | family | john | adams |
| 2 | 444-444-4444 | friend | john | adams |
| 3 | 555-555-5555 | work | mona | lisa |
| 3 | 666-666-6666 | family | mona | lisa |
| 3 | 777-777-7777 | friend | mona | lisa |
What have I tried/looked at:
I have looked into the pivot function of Oracle; however, I don't believe that can solve my problem since I don't have a fixed number of attributes that I want to pivot on.
I've looked at these posts:
SQL Query to return multiple key value pairs from a single table in one row
Pivot rows to columns without aggregate
Question:
Is what I'm tying to accomplish at all possible purely with SQL? If so, how can it be done? If not, please explain why.
Any help is much appreciated and here's the with table to help you get started:
with
table_1 ( id, key, value ) as (
select 1,'phone_num_1','111-111-1111' from dual union all
select 1,'phone_num_2','222-222-2222' from dual union all
select 1,'contact_1','friend' from dual union all
select 1,'contact_2','family' from dual union all
select 1,'first_name','mike' from dual union all
select 1,'last_name','smith' from dual union all
select 2,'phone_num_1','333-333-3333' from dual union all
select 2,'phone_num_2','444-444-4444' from dual union all
select 2,'contact_1','family' from dual union all
select 2,'contact_2','friend' from dual union all
select 2,'first_name','john' from dual union all
select 2,'last_name','adams' from dual union all
select 3,'phone_num_1','555-555-5555' from dual union all
select 3,'phone_num_2','666-666-6666' from dual union all
select 3,'phone_num_3','777-777-7777' from dual union all
select 3,'contact_1','work' from dual union all
select 3,'contact_2','family' from dual union all
select 3,'contact_3','friend' from dual union all
select 3,'first_name','mona' from dual union all
select 3,'last_name','lisa' from dual
)
select * from table_1;
This is not a dynamic pivot as you have a fixed set of keys - you just need to separate the enumeration of the keys from the keys themselves first.
You need to:
Separate the phone_num and contact key prefixes from the enumerated item; then
Pivot the common keys that have no enumeration so that they are associated with each enumerated key; and finally,
Pivot a second time to get the enumerated keys in a row together.
Oracle Setup:
CREATE TABLE table_1 ( id, key, value ) as
select 1,'phone_num_1','111-111-1111' from dual union all
select 1,'phone_num_2','222-222-2222' from dual union all
select 1,'contact_1','friend' from dual union all
select 1,'contact_2','family' from dual union all
select 1,'first_name','mike' from dual union all
select 1,'last_name','smith' from dual union all
select 2,'phone_num_1','333-333-3333' from dual union all
select 2,'phone_num_2','444-444-4444' from dual union all
select 2,'contact_1','family' from dual union all
select 2,'contact_2','friend' from dual union all
select 2,'first_name','john' from dual union all
select 2,'last_name','adams' from dual union all
select 3,'phone_num_1','555-555-5555' from dual union all
select 3,'phone_num_2','666-666-6666' from dual union all
select 3,'phone_num_3','777-777-7777' from dual union all
select 3,'contact_1','work' from dual union all
select 3,'contact_2','family' from dual union all
select 3,'contact_3','friend' from dual union all
select 3,'first_name','mona' from dual union all
select 3,'last_name','lisa' from dual
Query:
SELECT *
FROM (
SELECT id,
CASE
WHEN key LIKE 'phone_num_%' THEN 'phone_num'
WHEN key LIKE 'contact_%' THEN 'contact'
ELSE key
END AS key,
CASE
WHEN key LIKE 'phone_num_%'
OR key LIKE 'contact_%'
THEN TO_NUMBER( SUBSTR( key, INSTR( key, '_', -1 ) + 1 ) )
ELSE NULL
END AS item,
value,
MAX( CASE key WHEN 'first_name' THEN value END )
OVER ( PARTITION BY id ) AS first_name,
MAX( CASE key WHEN 'last_name' THEN value END )
OVER ( PARTITION BY id ) AS last_name
FROM table_1
)
PIVOT( MAX( value ) FOR key IN ( 'contact' AS contact, 'phone_num' AS phone_num ) )
WHERE item IS NOT NULL
ORDER BY id, item
Output:
ID | ITEM | FIRST_NAME | LAST_NAME | CONTACT | PHONE_NUM
-: | ---: | :--------- | :-------- | :------ | :-----------
1 | 1 | mike | smith | friend | 111-111-1111
1 | 2 | mike | smith | family | 222-222-2222
2 | 1 | john | adams | family | 333-333-3333
2 | 2 | john | adams | friend | 444-444-4444
3 | 1 | mona | lisa | work | 555-555-5555
3 | 2 | mona | lisa | family | 666-666-6666
3 | 3 | mona | lisa | friend | 777-777-7777
db<>fiddle here
If you can refactor the table then a simple improvement would be to add an extra column to hold the enumeration of the keys and use NULL when it is a value common to every enumeration:
CREATE TABLE table_1 ( id, key, line, value ) as
select 1, 'phone_num', 1, '111-111-1111' from dual union all
select 1, 'phone_num', 2, '222-222-2222' from dual union all
select 1, 'contact', 1, 'friend' from dual union all
select 1, 'contact', 2, 'family' from dual union all
select 1, 'first_name', NULL, 'mike' from dual union all
select 1, 'last_name', NULL, 'smith' from dual
Then your set of keys is always fixed and you do not need to extract the enumeration value from the key.
This is ugly, but I think does what you need
select t1.* , t2.value, t3.n, t3.f
from table_1 t1
inner join table_1 t2 on t1.id = t2.id and REPLACE(t1.key, 'phone_num_', '') = REPLACE(t2.key, 'contact_', '')
inner join (
select ID, min(case when Key = 'first_name' then Value end) as n, min(case when Key = 'last_name' then Value end) as f
from table_1
group by ID
) t3 on t1.id = t3.id
where
t1.Key not in('first_name','last_name')
SELECT id,
phone,
contact,
first_value(last) IGNORE NULLS over (partition BY id order by id DESC range BETWEEN CURRENT row AND unbounded following ) last_name,
first_value(FIRST) IGNORE NULLS over (partition BY id order by id DESC range BETWEEN CURRENT row AND unbounded following ) first_name
FROM
(SELECT id,
value,
row_number() over ( partition BY id,SUBSTR(KEY,1 ,instr(KEY,'',1)-1) order by KEY) rn,
SUBSTR(KEY,1 ,instr(KEY,'',1) -1) KEY
FROM table_1
) pivot ( MAX(value) FOR KEY IN ( 'phone' AS phone,'last' AS last,'first' AS FIRST,'contact' AS contact))
ORDER BY id;

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