Reputation: 5469
I'm using PostgreSQL JSON type field in my database.
JSON format:
[
{"id": "13", "something": "not_important"},
{"id": "5426565165191919", "something": "not_important"},
{"id": "46", "something": "not_important"}
...
]
List of IDs like ["13", "46", "84"]
.
How to make query that tell me that some ID is not exists? (Not important which one.)
Upvotes: 0
Views: 1424
Reputation: 35314
You can do this by left-joining your "expected" ids against the "actual" ids, and then filtering for rows in the result-set where the "actual" ids table failed to join. The only trick is extracting from the JSON the integral id values. This is how it could be done, assuming you are hard-coding all ids right in the SQL:
select
expected.id
from
(select substring(value::varchar from 2 for char_length(value::varchar)-2)::bigint id from json_array_elements('["13","46","84"]'::json)) expected
left join (select (value->>'id')::bigint id from json_array_elements('[{"id":"13"},{"id":"5426565165191919"},{"id":"46"}]'::json)) actual on actual.id=expected.id
where
actual.id is null
;
In the above query, I'm calling json_array_elements()
to extract the individual array elements from each JSON string. Because the target JSON string has object array elements, we must further extract the "id"
values, which can be done with the ->
operator. Note that value
seems to be the default name assigned to the single column output by the json_array_elements()
function.
A quirk is that the extracted JSON (i.e. the value
value) is still of type json
, and it seems you can't cast json
text directly to an integral value such as int
or bigint
, even if the entire json
text consists only of a valid int
/bigint
, but you can go through varchar
to get there:
select '34'::json::bigint; -- "ERROR: cannot cast type json to bigint"
select '34'::json::varchar::bigint; -- works, 34
That's why I had to cast the json
values with ::varchar::bigint
.
Also, I had to extract the expected ids from their double-quoted strings using substring()
, as the ::bigint
cast would otherwise not work.
Also, minor point, I used bigint
and not int
because you've got a rather large number there (5426565165191919).
If you want to just select a boolean value indicating if at least one expected id is missing, you can change the select clause to read:
select
case when count(expected.id)>0 then true else false end some_missing
Upvotes: 0
Reputation: 657182
WITH t(data) AS (
VALUES (' [
{"id": "13", "something": "not_important"},
{"id": "5426565165191919", "something": "not_important"},
{"id": "46", "something": "not_important"}
]'::json)
)
SELECT NOT EXISTS (
SELECT data::text, o::text -- cast to text for display in fiddle
FROM t, json_array_elements(t.data) AS d(o)
RIGHT JOIN unnest('{13,46,84}'::bigint[]) u(id) ON u.id = (d.o->>'id')::bigint
WHERE d.o IS NULL
) AS all_found;
WITH
query) just substitutes for an actual table t
.LEFT JOIN / NOT NULL
to identify missing values. Details:
bigint
is wide enough to hold your id
values. Else, switch to numeric
.More explanation in this related answer:
Upvotes: 2