Postgres 9.3 maakt bijvoorbeeld uitvoer
id | name | emails
-----------------------------------------------------------
1 | Ryan | [{"id":3,"user_id":1,"email":"[email protected]"},{"id":4,"user_id":1,"email":"[email protected]"}]
2 | Nick | [null]
Omdat ik een LEFT JOIN gebruik, zullen er gevallen zijn waarin er geen overeenkomst in de rechtertabel is, daarom worden lege (null) waarden vervangen door de rechtertabelkolommen. Als resultaat krijg ik [null]
als een van de JSON-aggregaten.
Hoe kan ik null
negeren/verwijderen zodat ik een lege JSON-array []
heb als de rechtertabelkolom null is?
Proost!
Antwoord 1, autoriteit 100%
In 9.4 kun je coalesce en een aggregatiefilterexpressie gebruiken.
SELECT C.id, C.name,
COALESCE(json_agg(E) FILTER (WHERE E.user_id IS NOT NULL), '[]') AS emails
FROM contacts C
LEFT JOIN emails E ON C.id = E.user_id
GROUP BY C.id, C.name
ORDER BY C.id;
De filterexpressie voorkomt dat het aggregaat de rijen verwerkt die null zijn omdat niet aan de linker join-voorwaarde is voldaan, zodat u een database null krijgt in plaats van de json [null]. Als u eenmaal een database-nul heeft, kunt u samenvoegen zoals gewoonlijk gebruiken.
http://www.postgresql.org/docs/9.4/static /sql-expressions.html#SYNTAX-AGGREGATES
Antwoord 2, autoriteit 16%
zoiets zou kunnen zijn?
select
c.id, c.name,
case when count(e) = 0 then '[]' else json_agg(e) end as emails
from contacts as c
left outer join emails as e on c.id = e.user_id
group by c.id
je kunt ook groeperen voordat je lid wordt (ik heb liever deze versie, het is wat duidelijker):
select
c.id, c.name,
coalesce(e.emails, '[]') as emails
from contacts as c
left outer join (
select e.user_id, json_agg(e) as emails from emails as e group by e.user_id
) as e on e.user_id = c.id
Antwoord 3, autoriteit 4%
Als dit een PostgreSQL-bug is, hoop ik dat deze in 9.4 is opgelost. Heel vervelend.
SELECT C.id, C.name,
COALESCE(NULLIF(json_agg(E)::TEXT, '[null]'), '[]')::JSON AS emails
FROM contacts C
LEFT JOIN emails E ON C.id = E.user_id
GROUP BY C.id;
Persoonlijk doe ik het COALESCE-bit niet, maar retourneer gewoon de NULL. Uw oproep.
Antwoord 4, autoriteit 2%
Ik heb dit antwoordgebruikt (sorry, ik kan niet naar je gebruikersnaam linken) maar ik denk dat ik het heb verbeterd het een beetje.
Voor de array-versie kunnen we
- verwijder de overbodige dubbele selectie
- gebruik json_aggin plaats van de
array_to_json(array_agg())
roept
en krijg dit:
CREATE OR REPLACE FUNCTION public.json_clean_array(p_data JSON)
RETURNS JSON
LANGUAGE SQL IMMUTABLE
AS $$
-- removes elements that are json null (not sql-null) or empty
SELECT json_agg(value)
FROM json_array_elements(p_data)
WHERE value::text <> 'null' AND value::text <> '""';
$$;
Voor 9.3, voor de objectversie, kunnen we:
- verwijder de niet-gebruikte
WITH
-clausule - verwijder de overbodige dubbele selectie
en krijg dit:
CREATE OR REPLACE FUNCTION public.json_clean(p_data JSON)
RETURNS JSON
LANGUAGE SQL IMMUTABLE
AS $$
-- removes elements that are json null (not sql-null) or empty
SELECT ('{' || string_agg(to_json(key) || ':' || value, ',') || '}') :: JSON
FROM json_each(p_data)
WHERE value::TEXT <> 'null' AND value::TEXT <> '""';
$$;
Voor 9.4 hoeven we de string-assembly-dingen niet te gebruiken om het object te bouwen, omdat we de nieuw toegevoegde json_object_agg
CREATE OR REPLACE FUNCTION public.json_clean(p_data JSON)
RETURNS JSON
LANGUAGE SQL IMMUTABLE
AS $$
-- removes elements that are json null (not sql-null) or empty
SELECT json_object_agg(key, value)
FROM json_each(p_data)
WHERE value::TEXT <> 'null' AND value::TEXT <> '""';
$$;
Antwoord 5
Waarschijnlijk minder performant dan de oplossing van Roman Pekar, maar een beetje netter:
select
c.id, c.name,
array_to_json(array(select email from emails e where e.user_id=c.id))
from contacts c
Antwoord 6
Ik heb mijn eigen functie gemaakt voor het filteren van json-arrays:
CREATE OR REPLACE FUNCTION public.json_clean_array(data JSON)
RETURNS JSON
LANGUAGE SQL
AS $$
SELECT
array_to_json(array_agg(value)) :: JSON
FROM (
SELECT
value
FROM json_array_elements(data)
WHERE cast(value AS TEXT) != 'null' AND cast(value AS TEXT) != ''
) t;
$$;
Ik gebruik het als
select
friend_id as friend,
json_clean_array(array_to_json(array_agg(comment))) as comments
from some_entity_that_might_have_comments
group by friend_id;
werkt natuurlijk alleen in postgresql 9.3. Ik heb ook een soortgelijke voor objectvelden:
CREATE OR REPLACE FUNCTION public.json_clean(data JSON)
RETURNS JSON
LANGUAGE SQL
AS $$
SELECT
('{' || string_agg(to_json(key) || ':' || value, ',') || '}') :: JSON
FROM (
WITH to_clean AS (
SELECT
*
FROM json_each(data)
)
SELECT
*
FROM json_each(data)
WHERE cast(value AS TEXT) != 'null' AND cast(value AS TEXT) != ''
) t;
$$;
EDIT: je kunt hier een paar hulpprogramma’s zien (een paar zijn oorspronkelijk niet van mij, maar ze zijn overgenomen van andere stackoverflow-oplossingen):
https://gist.github.com/le-doude/8b0e89d71a32efd21283
Antwoord 7
Een beetje anders, maar kan nuttig zijn voor anderen:
Als alle objecten in de array dezelfde structuur hebben (bijvoorbeeld omdat u jsonb_build_object
gebruikt om ze te maken), kunt u een “NULL-object met dezelfde structuur” definiëren om te gebruiken in array_remove
:
...
array_remove(
array_agg(jsonb_build_object('att1', column1, 'att2', column2)),
to_jsonb('{"att1":null, "att2":null}'::json)
)
...
Antwoord 8
Deze manier werkt, maar er moet een betere manier zijn 🙁
SELECT C.id, C.name,
case when exists (select true from emails where user_id=C.id) then json_agg(E) else '[]' end
FROM contacts C
LEFT JOIN emails E ON C.id = E.user_id
GROUP BY C.id, C.name;