I have the following 2 tables: title and photo. I want to return rows from both of these tables that are part of the same group, ordered by timestamp, and grouped into a JSON array.
Example:
groupId | allitems
--------------------------------------------------------------------------------------
1 | [{title1}, {photo1}, {photo2}, {photo3}, {title2}, {photo4}, ...}]
We need the following tables:
title table
titleId | createdDate | groupId | text | ...
---------------------------------------------------------------
1 | 2022-10-01 09:00:00 | 1 | EXAMPLE TITLE 1 | ...
2 | 2022-10-02 08:00:00 | 1 | EXAMPLE TITLE 2 | ...
3 | 2022-10-03 04:00:00 | 2 | EXAMPLE TITLE 3 | ...
photo table
photoId | timestamp | groupId | url | ...
-------------------------------------------------------------
1 | 2022-10-01 10:00:00 | 1 | http://www... | ...
2 | 2022-10-02 03:00:00 | 1 | http://www... | ...
3 | 2022-10-02 11:00:00 | 1 | http://www... | ...
4 | 2022-10-04 01:00:00 | 2 | http://www... | ...
5 | 2022-10-04 05:00:00 | 2 | http://www... | ...
6 | 2022-10-04 06:00:00 | 2 | http://www... | ...
Result
groupId | allitems
----------------------------------------------------------------------------------------------------
1 | [{titleId: 1, createdDate: "2022-10-01 09:00:00", groupId: 1, text: "EXAMPLE TITLE 1", ...},
| {photoId: 1, timestamp: "2022-10-01 10:00:00", groupId: 1, url: "http://www...", ...},
| {photoId: 2, timestamp: "2022-10-02 03:00:00", groupId: 1, url: "http://www...", ...},
| {titleId: 2, createdDate: "2022-10-02 08:00:00", groupId: 1, text: "EXAMPLE TITLE 2", ...}
| {photoId: 3, timestamp: "2022-10-02 08:00:00", groupId: 1, url: "http://www...", ...}]
----------------------------------------------------------------------------------------------------
2 | [{titleId: 3, createdDate: "2022-10-03 04:00:00", groupId: 2, text: "EXAMPLE TITLE 3", ...},
| {photoId: 4, timestamp: "2022-10-04 01:00:00", groupId: 2, url: "http://www...", ...},
| {photoId: 5, timestamp: "2022-10-04 05:00:00", groupId: 2, url: "http://www...", ...},
| {photoId: 6, timestamp: "2022-10-04 06:00:00", groupId: 2, url: "http://www...", ...}]
Each row contains the groupId and all the titles + photos ordered by timestamp, that are in the form of an JSON array.
Any help would be greatly appreciated!
Once you have built your json objects using JSON_BUILD_OBJECT, you can use JSON_AGG to aggregate on your "groupId" value.
You take out both "tab1.createdDate" and "tab2.timestamp" to take care of the order of your elements inside the array, during the aggregation, using an ORDER BY clause.
WITH cte AS (
SELECT groupId,
createdDate AS date_,
JSON_BUILD_OBJECT('titleId' , titleId,
'createdDate', createdDate,
'groupId' , groupId,
'text' , text_) AS json_obj
FROM tab1
UNION ALL
SELECT groupId,
timestamp_,
JSON_BUILD_OBJECT('photoId' , photoId,
'timestamp', timestamp_,
'groupId' , groupId,
'url' , url) AS json_obj
FROM tab2
)
SELECT groupId,
JSON_AGG(json_obj ORDER BY date_) AS all_items
FROM cte
GROUP BY groupId
Check the demo here.
Related
I have a table
-----------------------------------------------------
| id | name | result |
-----------------------------------------------------
| 1 | Maria | {type: download, status: pass} |
| 1 | Maria | {type: login, status: fail} |
| 2 | Tony | {type: download, status: fail} |
| 3 | Kate | {type: download, status: pass} |
| 3 | Kate | {type: login, status: pass} |
| 3 | Kate | {type: logout, status: pass} |
| 4 | Tom | {type: download, status: pass} |
-----------------------------------------------------
And I want to try to get this but not sure that it possible:
-----------------------------------------------------------
| id | name | download | action |
-----------------------------------------------------------
| 1 | Maria | pass | {login: fail} |
| 2 | Tony | fail | |
| 3 | Kate | pass | {login: pass, logout: pass} |
| 4 | Tom | pass | |
-----------------------------------------------------------
I found way to create a new simple column:
SELECT id, name
results.download AS download
FROM
(
SELECT id, name jsonb_agg(result) AS results
FROM my_table
GROUP BY id, name
) AS r
ORDER BY id, name;
but also I want to move login and logout field to action column. Is it possible?
The formatting is not exactly as you want but we have the right information in the right place.
create table my_table (
id int,
name varchar(10),
result text
);
insert into my_table values
(1,'Maria','{ "type": "download", "status": "pass"}'),
(1,'Maria','{ "type": "login", "status": "fail"}'),
(2,'Tony','{ "type": "download", "status": "fail"}'),
(3,'Kate','{ "type": "download", "status": "pass"}'),
(3,'Kate','{ "type": "login", "status": "pass"}'),
(3,'Kate','{ "type": "logout", "status": "pass"}'),
(4,'Tom','{ "type": "download", "status": "pass"}');
✓
7 rows affected
SELECT id, name
results.download AS download
FROM
(
SELECT id, name jsonb_agg(result) AS results
FROM my_table
GROUP BY id, name
) AS r
ORDER BY id, name;
ERROR: syntax error at or near "."
LINE 2: results.download AS download
^
select
id,
name,
result::json->'type' AS type,
result::json->'status' AS status
FROM my_table
id | name | type | status
-: | :---- | :--------- | :-----
1 | Maria | "download" | "pass"
1 | Maria | "login" | "fail"
2 | Tony | "download" | "fail"
3 | Kate | "download" | "pass"
3 | Kate | "login" | "pass"
3 | Kate | "logout" | "pass"
4 | Tom | "download" | "pass"
with cte as
(select
id,
name,
case when cast(result::json->'type' as text) = '"download"'
then cast(result::json->'status' as text) end as "download" ,
case when cast(result::json->'type' as text) in ('"login"','"logout"')
then concat(result::json->'type',': ',result::json->'status') end as "action"
from my_table)
select
id,
name,
max(download),
string_agg(action,', ') "action"
from cte
group by
id,
name
order by id
id | name | max | action
-: | :---- | :----- | :--------------------------------
1 | Maria | "pass" | "login": "fail"
2 | Tony | "fail" | null
3 | Kate | "pass" | "login": "pass", "logout": "pass"
4 | Tom | "pass" | null
db<>fiddle here
You can aggregate the downloads and the "other" items into two separate columns:
with data as (
select id,
name,
jsonb_object_agg('download', result ->> 'status') filter (where result ->> 'type' = 'download') as download,
jsonb_object_agg(result ->> 'type', result ->> 'status') filter (where result ->> 'type' <> 'download') as result
from my_table
group by id, name
)
select id, name,
download ->> 'download' as download,
result
from data;
The CTE returns this:
id | name | download | results
---+-------+----------------------+------------------------------------
1 | Maria | {"download": "pass"} | {"login": "fail"}
2 | Tony | {"download": "fail"} |
3 | Kate | {"download": "pass"} | {"login": "pass", "logout": "pass"}
4 | Tom | {"download": "pass"} |
And the final then just extracts the value for the download key.
Online example
Currently, I have this query:
SELECT DISTINCT t."Resource" AS "Email",
t."project id" AS "project_id",
sum(t."Hours Logged") AS "total hours"
FROM
(SELECT DISTINCT "Resource",
"Hours Logged",
"proposals"."Clickup Id" AS "project id"
FROM "daily hours",
proposals AS "proposals"
WHERE "When Logged" >= (CURRENT_DATE - 7)
AND ("Space Id" = "proposals"."Clickup Id"
OR "Folder Id" = "proposals"."Clickup Id"
OR "List Id" = "proposals"."Clickup Id")) AS t
GROUP BY (t."project id",
t."Resource")
The output of this query is this:
| Email | project_id | total hours |
| ---------------| -------------- | ------------|
| mail#mail.com | 1 | 6 |
| mail2#mail.com | 2 | 5 |
| mail3#mail.com | 1 | 7 |
| mail4#mail.com | 2 | 3 |
| mail#mail.com | 3 | 4 |
So now I want to write a query that sums the total hours which gives an overall total hours and group by project_id then find the percentage using this formula "total hours" / "overall total hours" * 100
So the output will be thus
| Email | project_id | total hours | overall total hours | percentage |
| ---------------| -------------- | ------------| --------------------| -----------|
| mail#mail.com | 1 | 6 | 13 | 46.15
| mail2#mail.com | 2 | 5 | 8 | 62.5
| mail3#mail.com | 1 | 7 | 13 | 53.84
| mail4#mail.com | 2 | 3 | 8 | 37.5
| mail#mail.com | 3 | 4 | 4 | 100
Thanks in advance
You can apply a window function to an aggregate, demo:
with t(Resource,project_id,hours) as (
-- emulates result of the subquery
select 'mail#mail.com ', 1, 6 union all
select 'mail2#mail.com', 2, 5 union all
select 'mail3#mail.com', 1, 7 union all
select 'mail4#mail.com', 2, 3 union all
select 'mail#mail.com ', 1, 6 union all
select 'mail2#mail.com', 2, 5 union all
select 'mail3#mail.com', 1, 7 union all
select 'mail4#mail.com', 2, 3 union all
select 'mail#mail.com ', 3, 4
)
select t.Resource AS "Email",
t.project_id ,
sum(t.hours) AS "total hours",
sum(sum(t.hours)) over (partition by t.project_id) "overall total hours",
sum(t.hours) / sum(sum(t.hours)) over (partition by t.project_id) "percentage"
from t
GROUP BY t.project_id, t.Resource
order by t.Resource,t.project_id
I have 3 tables:
customers:
_______________
| id | name |
|-----|-------|
| 1 | John |
| 2 | Adam |
| 3 | Smith |
---------------
cities:
__________________
| id | city |
|-----|----------|
| 1 | LA |
| 2 | PHI |
| 3 | HOU |
| 4 | DET |
------------------
customers_cities:
________________________________
| id | customer_id | city_id |
|-----|--------------|----------
| 1 | 1 | 1 |
| 2 | 1 | 2 |
| 3 | 2 | 3 |
| 4 | 3 | 2 |
| 5 | 1 | 3 |
--------------------------------
I wanted to aggregate and show in a json format all the cities and their customers listed in them. Some customers can be in different cities too.
Output should look like:
- LA:
|- John
- PHI
|- John
|- Smith
- Hou
|- Adam
|-John
The JSON output looks like this:
"data":{
"customers_in_cities": [
"LA" : [
{
"id": 1,
"name": "John"
}],
"PHI" : [
{
"id": 1,
"name": "John"
},
{
"id": 3,
"name": "Adam"
}],
"HOU" : [
{
"id": 3,
"name": "Adam"
},
{
"id": 1,
"name": "John"
}],
]
}
Something like the above json output.
I tried:
SELECT cus.name AS customer_name, cty.city AS city_name
FROM cities cty
INNER JOIN customers_cities cc ON cc.city_id = cty.id
INNER JOIN customers cus ON cc.customer_id = cus.id;
will appreciate any help
I am going to post the SQL query that took me a couple of hours to find out! actually paying the credit to this post Return as array of JSON objects in SQL (Postgres)
e by Erwin Brandstetter
the sql query goes like this:
SELECT c.*,
json_agg(row_to_json(c)) AS customers_in_cities
FROM cities c
LEFT JOIN customers_cities cc ON cc.city_id = c.id
LEFT JOIN customers cust ON cust.id = cc.customer_id
GROUP BY c.id;
thank you
I have created these sample tables to create a json tree structure so that i can use jqtree to create a tree layout.
I want my json to be in the format
[
{"id":1, "parentid": 0, "name": "Carnivores"},
{"id":2, "parentid": 0, "name": "Herbivores"},
{"id":3, "parentid": 1, "name": "Dogs"},
{"id":4, "parentid": 3, "name": "Labradors"},
{"id":5, "parentid": 3, "name": "Pugs"},
{"id":6, "parentid": 3, "name": "Terriers"}
]
The tables are as follows.
| catg_id | catg_name |
| —————- |————————- |
| 1 | Carnivores |
| 2 | Herbivores |
| animal_catg_id | animal_catg_name | catg_id |
| —————- |————————- |————————- |
| 1 | Dogs | 1 |
| 2 | Cats | 1 |
| 3 | Cows | 2 |
| 4 | Buffalo | 2 |
| animal_id | animal_name | animal_catg_id |
| —————- |————————- | ————————- |
| 1 | labs | 1 |
| 2 | pugs | 1 |
| 3 | terriers | 1 |
| 4 | german | 1 |
| 5 | lion | 2 |
| 6 | tiger | 2 |
I am assuming it would be hierarchical query, i have never written one before, i need some help with that.
I don't know where to start and how to start it.
EDIT
One of the comments in the answers is that the schema design is not clear.
What changes should I do to make to get the data in the json format, so that it maintains the hierarchy
EDIT2
My current query returns this table
Carnivores | Dogs | labs
Carnivores | Dogs | pugs
Carnivores | Dogs | terriers
.......
The JSON you are proposing appears to have no correlation between the IDs you are assigning and the IDs in the tables this will make it difficult to connect anything from the client-side back to the database.
You would be better re-organising your tables so that you can put everything into a single hierarchical structure. Something like a Linnaean Taxonomy:
SQL Fiddle
Oracle 11g R2 Schema Setup:
CREATE TABLE Taxonomies ( ID, PARENT_ID, Category, Taxonomy, Common_Name ) AS
SELECT 1, CAST(NULL AS NUMBER), 'Kingdom', 'Animalia', 'Animal' FROM DUAL
UNION ALL SELECT 2, 1, 'Phylum', 'Chordata', 'Chordate' FROM DUAL
UNION ALL SELECT 3, 2, 'Class', 'Mammalia', 'Mammal' FROM DUAL
UNION ALL SELECT 4, 3, 'Order', 'Carnivora', 'Carnivore' FROM DUAL
UNION ALL SELECT 5, 4, 'Family', 'Felidae', 'Feline' FROM DUAL
UNION ALL SELECT 6, 5, 'Genus', 'Panthera', 'Tiger' FROM DUAL
UNION ALL SELECT 7, 5, 'Genus', 'Felis', 'Cat' FROM DUAL
UNION ALL SELECT 8, 5, 'Genus', 'Lynx', 'Lynx' FROM DUAL
UNION ALL SELECT 9, 4, 'Family', 'Canidae', 'Canid' FROM DUAL
UNION ALL SELECT 10, 9, 'Genus', 'Canis', 'Canine' FROM DUAL
UNION ALL SELECT 11, 10, 'Species', 'Canis Lupus', 'Gray Wolf' FROM DUAL
UNION ALL SELECT 12, 11, 'Sub-Species', 'Canis Lupus Familiaris', 'Domestic Dog' FROM DUAL
UNION ALL SELECT 13, 12, 'Breed', NULL, 'Pug' FROM DUAL
UNION ALL SELECT 14, 12, 'Breed', NULL, 'German Shepherd' FROM DUAL
UNION ALL SELECT 15, 12, 'Breed', NULL, 'Labradors' FROM DUAL
UNION ALL SELECT 16, 7, 'Species', 'Felis Catus', 'Domestic Cat' FROM DUAL
UNION ALL SELECT 17, 8, 'Species', 'Lynx Lynx', 'Eurasian Lynx' FROM DUAL
UNION ALL SELECT 18, 8, 'Species', 'Lynx Rufus', 'Bobcat' FROM DUAL;
Then you can extract the data relatively simply:
Query 1 - Get everything taxonomically related to "Cat":
SELECT *
FROM (
SELECT *
FROM Taxonomies
START WITH Common_Name = 'Cat'
CONNECT BY PRIOR PARENT_ID = ID
ORDER BY LEVEL DESC
)
UNION
SELECT *
FROM (
SELECT *
FROM Taxonomies
START WITH Common_Name = 'Cat'
CONNECT BY PRIOR ID = PARENT_ID
ORDER SIBLINGS BY Common_Name
)
Results:
| ID | PARENT_ID | CATEGORY | TAXONOMY | COMMON_NAME |
|----|-----------|----------|-------------|--------------|
| 1 | (null) | Kingdom | Animalia | Animal |
| 2 | 1 | Phylum | Chordata | Chordate |
| 3 | 2 | Class | Mammalia | Mammal |
| 4 | 3 | Order | Carnivora | Carnivore |
| 5 | 4 | Family | Felidae | Feline |
| 7 | 5 | Genus | Felis | Cat |
| 16 | 7 | Species | Felis Catus | Domestic Cat |
Query 2 - Get everything taxonomically related to "Canine":
SELECT *
FROM (
SELECT *
FROM Taxonomies
START WITH Common_Name = 'Canine'
CONNECT BY PRIOR PARENT_ID = ID
ORDER BY LEVEL DESC
)
UNION
SELECT *
FROM (
SELECT *
FROM Taxonomies
START WITH Common_Name = 'Canine'
CONNECT BY PRIOR ID = PARENT_ID
ORDER SIBLINGS BY Common_Name
)
Results:
| ID | PARENT_ID | CATEGORY | TAXONOMY | COMMON_NAME |
|----|-----------|-------------|------------------------|-----------------|
| 1 | (null) | Kingdom | Animalia | Animal |
| 2 | 1 | Phylum | Chordata | Chordate |
| 3 | 2 | Class | Mammalia | Mammal |
| 4 | 3 | Order | Carnivora | Carnivore |
| 9 | 4 | Family | Canidae | Canid |
| 10 | 9 | Genus | Canis | Canine |
| 11 | 10 | Species | Canis Lupus | Gray Wolf |
| 12 | 11 | Sub-Species | Canis Lupus Familiaris | Domestic Dog |
| 13 | 12 | Breed | (null) | Pug |
| 14 | 12 | Breed | (null) | German Shepherd |
| 15 | 12 | Breed | (null) | Labradors |
Here's an example of a hierarchical query from the Oracle documentation:
SELECT last_name, employee_id, manager_id, LEVEL
FROM employees
START WITH employee_id = 100
CONNECT BY PRIOR employee_id = manager_id
ORDER SIBLINGS BY last_name;
http://docs.oracle.com/cd/B19306_01/server.102/b14200/queries003.htm
Something like this in your case but your schema design isn't clear
SELECT animal_name, level
FROM animals
START WITH parentid is null
CONNECT BY PRIOR id = parentid;
I'm using MS SQL Server 2008, and I would like this:
+------+--------+--------+------------+
| id | Name | Event | Timestamp |
+------+--------+--------+------------+
| 0 | bob | note | 14:20 |
| 1 | bob | time | 14:22 |
| 2 | bob | time | 14:40 |
| 3 | bob | time | 14:45 |
| 4 | bob | send | 14:48 |
| 5 | bob | time | 15:30 |
| 6 | bob | note | 15:35 |
| 7 | bob | note | 18:00 |
+------+--------+--------+------------+
To become this:
+------+--------+--------+------------+
| id | Name | Event | Timestamp |
+------+--------+--------+------------+
| 0 | bob | note | 14:20 |
| 1 | bob | time | 14:22 |
| 4 | bob | send | 14:48 |
| 5 | bob | time | 15:30 |
| 6 | bob | note | 15:35 |
+------+--------+--------+------------+
I.e., rows are "grouped" by the "event" column. Only one of each grouped identical "event" is to be shown.
If one event, e.g. "note" as with id 0, is in the table with no row directly before or after it (row with nearest timestamps) that has an equal "event" value, it is shown;
If more than one row with the same event, e.g. "time" as with id 1-3. comes after each others (i.e. no row with a different "event" has a timestamp that is "between" them), any one of them is shown (doesn't matter for me, all other columns are identical anyway).
Those two are the only rules.
If ids are one after one try do it this way:
select * into #tab from(
select 0 as id, 'bob' as name, 'note' as event, '14:20' as time union
select 1, 'bob', 'time', '14:22' union
select 2, 'bob', 'time', '14:40' union
select 3, 'bob', 'time', '14:45' union
select 4, 'bob', 'send', '14:48' union
select 5, 'bob', 'time', '15:30' union
select 6, 'bob', 'note', '15:35' union
select 7, 'bob', 'note', '18:00'
) t
select t.*
from #tab t
left join #tab t1 on t.id = t1.id + 1 and t1.event = t.event
-- and t1.name = t.name -- if there are more names you are going to need this one as well
where t1.id is null
result:
id name event time
0 bob note 14:20
1 bob time 14:22
4 bob send 14:48
5 bob time 15:30
6 bob note 15:35
Added:
If ids aren't one after one, you can make them to be:
select identity(int, 1, 1) as id, name, event, time
into #tab_ordered_ids
from #tab order by name, id, time