Using the hypothetical schema:
CREATE TABLE obj (id INT, name VARCHAR);
CREATE TABLE objprop (obj_id INT, key VARCHAR, value VARCHAR);
INSERT INTO obj VALUES
(1, 'Object 1'),
(2, 'Object 2'),
(3, 'Object 3');
INSERT INTO objprop VALUES
(1, 'created', '2020-02-16'),
(1, 'updated', '2020-02-28'),
(2, 'created', '2020-02-01');
Could I obtain a list of objects (one per row), and a JSON field that represents object's properties?
I know I can use the ARRAY() function with a subquery to retrieve an array of values, for example:
SELECT id, name, ARRAY(SELECT value FROM objprop where obj_id=id) values FROM obj;
+----+----------+------------------------------+
| id | name | values |
+----+----------+------------------------------+
| 1 | Object 1 | {'2020-02-16', '2020-02-28'} |
| 2 | Object 2 | {'2020-02-01'} |
| 3 | Object 3 | {} |
+----+----------+------------------------------+
But could I make a query that instead of an ARRAY, it would return me a JSON column with the subquery in it? My goal is to obtain for example:
+---+----------+----------------------------------------------------------------------------------------+
| 1 | Object 1 | [{"key": "created", "value": "2020-02-16"}, {"key": "updated", "value": "2020-02-28"}] |
| 2 | Object 2 | [{"key": "created", "value": "2020-02-01"}] |
| 3 | Object 3 | [] |
+---+----------+----------------------------------------------------------------------------------------+
SELECT
id,
name,
COALESCE((
SELECT json_agg(json_build_object('key', key, 'value', value))
FROM objprop where obj_id=id
), '[]'::json) vals
FROM
obj;
Related
This question already has answers here:
PostgreSQL Crosstab Query
(7 answers)
Closed 3 years ago.
I have a many-to-one relationship between Animals and their attributes. Because different Animals have different attributes, I want to be able to select all animals with their attribute name as a column header and NULL values where that animal does not have that attribute.
Like so...
TABLE_ANIMALS
ID | ANIMAL | DATE | MORE COLS....
1 | CAT | 2012-01-10 | ....
2 | DOG | 2012-01-10 | ....
3 | FROG | 2012-01-10 | ....
...
TABLE_ATTRIBUTES
ID | ANIMAL_ID | ATTRIBUE_NAME | ATTRIBUTE_VALUE
1 | 1 | noise | meow
2 | 1 | legs | 4
3 | 1 | has_fur | TRUE
4 | 2 | noise | woof
5 | 2 | legs | 4
6 | 3 | noise | croak
7 | 3 | legs | 2
8 | 3 | has_fur | FALSE
...
QUERY RESULT
ID | ANIMAL | NOISE | LEGS | HAS_FUR
1 | CAT | meow | 4 | TRUE
2 | DOG | woof | 4 | NULL
3 | FROG | croak | 2 | FALSE
How would I do this? To reiterate, it's important that all the columns are there even if one Animal doesn't have that attribute, such as "DOG" and "HAS_FUR" in this example. If it doesn't have the attribute, it should just be null.
How about a simple join, aggregation and group by?
create table table_animals(id int, animal varchar(10), date date);
create table table_attributes(id varchar(10), animal_id int, attribute_name varchar(10), attribute_value varchar(10));
insert into table_animals values (1, 'CAT', '2012-01-10'),
(2, 'DOG', '2012-01-10'),
(3, 'FROG', '2012-01-10');
insert into table_attributes values (1, 1, 'noise', 'meow'),
(2, 1, 'legs', 4),
(3, 1, 'has_fur', TRUE),
(4, 2, 'noise', 'woof'),
(5, 2, 'legs', 4),
(6, 3, 'noise', 'croak'),
(7, 3, 'legs', 2),
(8, 3, 'has_fur', FALSE);
select ta.animal,
max(attribute_value) filter (where attribute_name = 'noise') as noise,
max(attribute_value) filter (where attribute_name = 'legs') as legs,
max(attribute_value) filter (where attribute_name = 'has_fur') as has_fur
from table_animals ta
left join table_attributes tat on tat.animal_id = ta.id
group by ta.animal
Here's a rextester sample
Additionally you can change the aggregation to MAX CASE WHEN... but MAX FILTER WHERE has better performance.
In our database we have a data set sorta like the following:
+----+-------------------------------+
| id | stuff |
+----+-------------------------------+
| 1 | {} |
+----+-------------------------------+
| 2 | {"a": "something", "b": null} |
+----+-------------------------------+
| 3 | {"c": null, "d": null} |
+----+-------------------------------+
I would like to match only, in this case, the one with id = 2, reason being that at least one of the values in the object is not null.
How can this be done with PostgreSQL?
I know one can do something like WHERE stuff != '{}' but that of course only checks for an empty object
Or something like WHERE (stuff->>'a') IS NOT NULL, but the thing is the list of keys in the objects are not hardcoded, could be anything
Use the function jsonb_each_text() or json_each_text(), example:
with my_table(id, jdata) as (
values
(1, '{}'::jsonb),
(2, '{"a": "something", "b": null}'),
(3, '{"c": null, "d": null}')
)
select distinct t.*
from my_table t
cross join jsonb_each_text(jdata)
where value is not null;
id | jdata
----+-------------------------------
2 | {"a": "something", "b": null}
(1 row)
This query (proposed by Abelisto, see the comments) should be more performant on a larger dataset:
select t.*
from my_table t
where exists (
select 1
from jsonb_each_text(jdata)
where value is not null);
I have an orders table which lists stores what parts where deleted, removed and installed on each job.
--Deleted Items on a order.
DECLARE #Deleted TABLE (ID int, Item int);
INSERT INTO #Deleted (ID, Item)
VALUES (1, 12345), (2, 12345);
-- Removed Items on a order
DECLARE #Removed TABLE (ID int, Item int);
INSERT INTO #Removed (ID, Item)
VALUES(3, 12345), (4, 67891);
--Installed items on a order
DECLARE #Installed TABLE (ID int, Item int);
INSERT INTO #Installed(ID, Item)
VALUES(1, 12345), (2, 67891), (3, 12345), (4, 12345), (4, 67891);
I need the following result set:
--Required Result Set
/*
ID | DEL / REM | Installed | Match |
1 | 12345 | 12345 | 1 |
2 | 12345 | 67891 | 0 |
3 | 12345 | 12345 | 1 |
4 | 67891 | 67891 | 1 |
4 | NULL | 12345 | 0 |
*/
The main problem I have with all this is order 4. There is a match on one of the parts installed but not the second. So I need to say where ID = ID AND IF POSSIBLE Item = Item OR just Item if it doesn't match.
I know that the maximum number of alternate installed items are two, so for each order, there will be a maximum of 2 different Item number installed (currently).
I know there also may be multiple deleted or removed items (not depicted above).
Any help would be greatly appreciated.
I have certain table:
CREATE TABLE x(
id BIGSERIAL PRIMARY KEY,
data JSONB
);
INSERT INTO x(data)
VALUES( '{"a":"test", "b":123, "c":null, "d":true}' ),
( '{"a":"test", "b":123, "c":null, "d":"yay", "e":"foo", "f":[1,2,3]}' );
How to query types of each key in that table, so it would give an output something like this:
a | string:2
b | number:2
c | null:2
d | boolean:1 string:1
e | string:1
f | jsonb:1 -- or anything
I only know the way to get the keys and count, but don't know how to get the type of each key:
SELECT jsonb_object_keys(data), COUNT(id) FROM x GROUP BY 1 ORDER BY 1
that would give something like:
a | 2
b | 2
c | 2
d | 2
e | 1
f | 1
EDIT:
As pozs points out, there are two typeof functions: one for JSON and one for SQL. This query is the one you're looking for:
SELECT
json_data.key,
jsonb_typeof(json_data.value),
count(*)
FROM x, jsonb_each(x.data) AS json_data
group by key, jsonb_typeof
order by key, jsonb_typeof;
Old Answer: (Hey, it works...)
This query will return the type of the keys:
SELECT
json_data.key,
pg_typeof(json_data.value),
json_data.value
FROM x, jsonb_each(x.data) AS json_data;
... unfortunately, you'll notice that Postgres doesn't differentiate between the different JSON types. it regards it all as jsonb, so the results are:
key1 | value1 | value
------+--------+-----------
a | jsonb | "test"
b | jsonb | 123
c | jsonb | null
d | jsonb | true
a | jsonb | "test"
b | jsonb | 123
c | jsonb | null
d | jsonb | "yay"
e | jsonb | "foo"
f | jsonb | [1, 2, 3]
(10 rows)
However, there aren't that many JSON primitive types, and the output seems to be unambiguous. So this query will do what you're wanting:
with jsontypes as (
SELECT
json_data.key AS key1,
CASE WHEN left(json_data.value::text,1) = '"' THEN 'String'
WHEN json_data.value::text ~ '^-?\d' THEN
CASE WHEN json_data.value::text ~ '\.' THEN 'Number'
ELSE 'Integer'
END
WHEN left(json_data.value::text,1) = '[' THEN 'Array'
WHEN left(json_data.value::text,1) = '{' THEN 'Object'
WHEN json_data.value::text in ('true', 'false') THEN 'Boolean'
WHEN json_data.value::text = 'null' THEN 'Null'
ELSE 'Beats Me'
END as jsontype
FROM x, jsonb_each(x.data) AS json_data -- Note that it won't work if we use jsonb_each_text here because the strings won't have quotes around them, etc.
)
select *, count(*) from jsontypes
group by key1, jsontype
order by key1, jsontype;
Output:
key1 | jsontype | count
------+----------+-------
a | String | 2
b | Integer | 2
c | Null | 2
d | Boolean | 1
d | String | 1
e | String | 1
f | Array | 1
(7 rows)
You can improve your last query with jsonb_typeof
with jsontypes as (
SELECT
json_data.key AS key1,
jsonb_typeof(json_data.value) as jsontype
FROM x, jsonb_each(x.data) AS json_data
)
select *, count(*)
from jsontypes
group by 1, 2
order by 1, 2;
Imagining I have a table cars with a field data inside:
CARS
name | data
car 1 | { "doors" => "5", "engine" => "1.1" }
car 2 | { "doors" => "3", "engine" => "1.1", "air_conditioning" => "true" }
car 3 | { "doors" => "5", "engine" => "1.4" }
Assuming data keys are dynamic (more can be added), how can I create a pivot table from that data like this:
CROSSTAB
name | doors | engine | air_conditioning
car 1 | 5 | 1.1 |
car 2 | 3 | 1.1 | "true"
car 3 | 5 | 1.4 |
Here's how to get the result you asked for:
CREATE TABLE hstore_test (id bigserial primary key, title text, doors integer, engine text, air_conditioning boolean)
INSERT INTO hstore_test (title, doors, engine, air_conditioning)
VALUES ('Car1', 2, '1.1', false), ('Car2', 4, '1.2', true), ('Car3', 3, '1.3', false), ('Car4', 5, '1.4', null);
DROP TABLE IF EXISTS hstore_persist;
CREATE TABLE hstore_persist AS
SELECT hstore(t) car_data FROM hstore_test AS t;
SELECT car_data->'title' "name", car_data->'doors' doors, car_data->'engine' engine, car_data->'air_conditioning' air_conditioning
FROM hstore_persist
This will result in the table
name | doors | engine | air_conditioning
Car1 | 2 | 1.1 | f
Car2 | 4 | 1.2 | t
Car3 | 3 | 1.3 | f
Car4 | 5 | 1.4 |
There is nothing "crosstab" about it, though. This is just using the accessor methods of an hstore to display the data in the way you show in the example.