SQL select all rows within a group by - sql

With the following table design:
Table "devices"
model
serial_number
active
A
11111
1
A
22222
1
A
33333
1
A
44444
0
B
XXXXX
1
B
YYYYY
1
I would like to retrieve the model, a count of the number of active devices (active = 1) for each model, and a list of all serial numbers for each model.
Expected output would be something like this:
[{
"model": "A",
"count": 3,
"serials": ["11111", "22222", "33333"]
}, {
"model": "B",
"count": 2,
"serials": ["XXXXX", "YYYYY"]
}]
I am able to retrieve the (grouped) models and count but how do I get the serial numbers?
SELECT model, count(*) as count
FROM devices
WHERE active = 1
GROUP BY model
I suspect I need a sub-query but I can't wrap my head around this. Thanks.

You can try to use FOR JSON PATH with STRING_AGG function which will group connect string serial_number from each model
QUOTENAME will help you make [] array brackets
SELECT model,
count(*) 'count',
JSON_QUERY(QUOTENAME(STRING_AGG('"' + STRING_ESCAPE(serial_number, 'json') + '"', ','))) serial_number
FROM devices
WHERE active = 1
GROUP BY model
FOR JSON PATH
sqlfiddle
If you don't want to get JSON result you might use STRING_AGG function directly.
SELECT model,
count(*) 'count',
STRING_AGG(serial_number, ',') serial_number
FROM devices
WHERE active = 1
GROUP BY model

Related

Creating a JSON list from multiple table rows

I have data in a BigQuery table where every row is an item in an ecommerce order, for example here are rows showing the purchase of three items across two orders:
Order number
Product
Quantity
001
ABC
1
001
DEF
2
002
GHI
1
I need to create a JSON list for every order in the below format, which using the example data above would look like this for order 001:
[ {product_id: "ABC", quantity:1},{product_id: "DEF", quantity:2} ]
How can I achieve this format in BigQuery SQL?
Consider below
SELECT order_number,
TO_JSON_STRING(ARRAY_AGG(STRUCT(product as product_id, quantity))) json_value
FROM orders
GROUP BY order_number
if applied to sample data in your question - output is
As #aleix-cc pointed out, this can be easily done with the TO_JSON function which is currently in preview:
WITH orders as (
SELECT "001" as order_number, "ABC" as product, 1 as quantity UNION ALL
SELECT "001", "DEF", 2 UNION ALL
SELECT "002", "GHI", 1
)
SELECT
order_number,
TO_JSON(ARRAY_AGG(STRUCT(product, quantity))) json_value
FROM orders
GROUP BY order_number
Edit: Since this is a function in preview, you should go with Mikhail's answer and use TO_JSON_STRING.

Using json_build_object in PostgreSQL v14.x with the result of a SELECT statement

I'm trying to create a VIEW of a JSON object, with a varying number of key/value pairs, in PostgreSQL v14.x, from the results of a SELECT statement.
Using json_agg is returning an array of objects - a key of each rating possibility as they occur, and a value which is the count of all the ratings selected from a table of reviews. Instead of an array, I need an object that has multiple key/value pairs, where the value corresponds to the aggregated count() of the ratings column(s), grouped by product_id. Trying to reuse json_build_object isn't working as expected.
Using:
CREATE VIEW reviews.meta AS
SELECT product_id, json_agg(json_build_object(reviews.list.rating, reviews.list.rating))
FROM reviews.list
GROUP BY reviews.list.product_id
ORDER BY product_id;
returns:
product_id | reviews_count
---------------------------
1 | [{"5" : 5}, {"4" : 4}]
2 | [{"4" : 4}, {"4" : 4}, {"3" : 3}, {"5" : 5}, {"2" : 2}]
But I'm looking for:
product_id | reviews_count
---------------------------
1 | {"5" : 1, "4" : 1}
2 | {"4" : 2, "3" : 1, "5" : 1, "2" : 1}
A dynamically created object:
in rows by product_id
where the values are quantities of Integer ratings (1-5) as they appear in the reviews.list table
in an object rather than an array of objects
I am new to SQL / PL/pgSQL language.
You need two levels of aggregation, one to get the counts, and one to package the counts up. It is easy to do that by nesting one query inside the FROM of another:
CREATE or replace VIEW meta AS
SELECT product_id, jsonb_object_agg(rating, count)
FROM (select product_id, rating, count(*) from list group by product_id, rating) foo
GROUP BY product_id
ORDER BY product_id;

PostgreSQL get json as record

I would like to be able to get a json object as a record.
SELECT select row_number() OVER () AS gid, feature->'properties' FROM dataset
The output of this query looks like this:
gid
?column? json
1
{"a": "1", "b":"2", "c": "3"}
2
{"a": "3", "b":"2", "c": "1"}
3
{"a": "1"}
The desired result :
gid
a
b
c
1
1
2
3
2
3
2
1
3
1
null
null
I can't use json_to_record because i don't know the number of fields. However, all my fields are text.
There is no generic way to do that, because the number, type and name of columns have to be known at query parse time. So you would have to do:
SELECT row_number() OVER () AS gid,
CAST(feature #>> '{properties,a}' AS integer) AS a,
CAST(feature #>> '{properties,b}' AS integer) AS b,
CAST(feature #>> '{properties,c}' AS integer) AS c
FROM dataset;
Essentially, you have to know the columns ahead of time and hard code them in the query.

How to create SQL query to sort JSON array using 1 attribute?

I have a table with a column that contains a JSON body that has arrays that I want to sort based on a attribute associated with that array.
I have tried selecting the array name and displaying the attribute which will display the entire array
The column name is my_column and the JSON is formatted as follows -
{
"num": "123",
"Y/N": "Y",
"array1":[
{
"name": "Bob",
"sortNum": 123
},
{
"name": "Tim Horton",
"sortNum": 456
}
]
}
I want the output to be based on the highest value of sortNum so the query should display the attributes for Tim Horton. The code I have played around with is below but get an error when trying to query based on sortNum.
SELECT my_column
FROM
my_table,
jsonb_array_elements(my_column->'array1') elem
WHERE elem->>'sortNum' = INT
Order by the filed 'sortNum' of the array element descending and use LIMIT 1 to only get the top record.
SELECT jae.e
FROM my_table t
CROSS JOIN LATERAL jsonb_array_elements(t.my_column->'array1') jae (e)
ORDER BY jae.e->'sortNum' DESC
LIMIT 1;
Edit:
If you want to sort numerically rather than lexicographically, get the element as text and cast it to an integer prior sorting on it.
SELECT jae.e
FROM my_table t
CROSS JOIN LATERAL jsonb_array_elements(t.my_column->'array1') jae (e)
ORDER BY (jae.e->>'sortNum')::integer DESC
LIMIT 1;
This answer assumes that your table has a column (or maybe a combination of columns) that can be use to uniquely identify a record. Let's call it myid.
To start with, we can use json_array_elements to split the JSON array into rows, as follows:
select myid, x.value, x->>'sortNum'
from
mytable,
json_array_elements(mycolumn->'array1') x
;
This returns:
myid | value | sortnum
---------------------------------------------------------
1 | {"name":"Bob","sortNum":123} | 123
1 | {"name":"Tim Horton","sortNum":456} | 456
Now, we can turn this to a subquery, and use ROW_NUMBER() to filter in the array element with the highest sortNum attribute:
select value
from (
select
x.value,
row_number() over(partition by myid order by x->>'sortNum' desc) rn
from
mytable,
json_array_elements(mycolumn->'array1') x
) y
where rn = 1;
Yields:
value
-----------------------------------
{"name":"Tim Horton","sortNum":456}
Demo on DB Fiddle

Sql Server - Matching exact ids or more

I have a "messages" table with columns: "id", "title",
"categories" table with columns: "id", "title",
and "messages_categories" link table with columns: "message_id","category_id".
lets assume we have messages with ids of
1,2,3
and categories with ids of
1,2,3
messages_categories with data of
message: 1, category: 1
message: 2, category: 1
message: 2, category: 2
message: 3, category: 1
message: 3, category: 2
message: 3, category: 3
I want to find the exact match or more for example
if I search for category 1 i'll get messages 1,2,3
if I search for category 1,2 i'll get messages 2,3
if I search for category 1,2,3 i'll get only message 3
i'm using a lot of ids so join for every category can be too much.
I figured out I can use "having" statement with "sum" and "count" to find the exact rows
but it's not good enough.
Appreciate any help, Nevo.
If you need "more" then use NOT EXISTS, so called relational division with reminder.
SELECT DISTINCT messages_id
FROM messages_categories r1
WHERE NOT EXISTS
(SELECT *
FROM
(SELECT 1 as cat_id UNION SELECT 2 ) S -- id of categories needed
WHERE NOT EXISTS
(SELECT *
FROM messages_categories AS r2
WHERE (r1.messages_id = r2.messages_id)
AND (r2.category_id = S.cat_id)));
Something along the lines of this might work:
SELECT DISTINCT x.message_id
FROM (
**query-that-gives-the-exact-rows-but-isnt-good-enough**
) AS x