Zwrócony wynik PostgreSQL jako tablica JSON?

134

Chciałbym, aby PostgreSQL zwracał wynik zapytania jako jedną tablicę JSON. Dany

create table t (a int primary key, b text);

insert into t values (1, 'value1');
insert into t values (2, 'value2');
insert into t values (3, 'value3');

Chciałbym coś podobnego do

[{"a":1,"b":"value1"},{"a":2,"b":"value2"},{"a":3,"b":"value3"}]

lub

{"a":[1,2,3], "b":["value1","value2","value3"]}

(właściwie lepiej byłoby znać oba). Próbowałem takich rzeczy

select row_to_json(row) from (select * from t) row;
select array_agg(row) from (select * from t) row;
select array_to_string(array_agg(row), '') from (select * from t) row;

I czuję, że jestem blisko, ale tak naprawdę nie ma. Czy powinienem zajrzeć do innej dokumentacji oprócz 9.15. Funkcje i operatory JSON ?

Nawiasem mówiąc, nie jestem pewien swojego pomysłu. Czy to zwykła decyzja projektowa? Myślę, że mógłbym oczywiście wziąć wynik (na przykład) pierwszego z 3 powyższych zapytań i lekko nim manipulować w aplikacji przed przekazaniem go klientowi, ale jeśli PostgreSQL może bezpośrednio utworzyć końcowy obiekt JSON, byłoby prostsze, ponieważ nadal nie uwzględniłem żadnej zależności od żadnej biblioteki JSON w mojej aplikacji.

inżynier X
źródło
1
PG 9.4, teraz dostępna w wersji beta 1, ma ulepszoną obsługę formatu JSON, w tym binarne we / wy. Jeśli jesteś na komputerze deweloperskim, możesz to sprawdzić.
Patrick,
@Patrick: dziękuję, wygląda na to, że json_object () to nowa funkcja w wersji 9.4 i spróbuję czegoś takiego jak SELECT json_object (array_agg (ta), array_agg (tb)) FROM t, gdybym to miał
inżynier

Odpowiedzi:

266

TL; DR

SELECT json_agg(t) FROM t

dla tablicy obiektów JSON i

SELECT
    json_build_object(
        'a', json_agg(t.a),
        'b', json_agg(t.b)
    )
FROM t

dla obiektu JSON tablic.

Lista obiektów

W tej sekcji opisano sposób generowania tablicy obiektów JSON, przy czym każdy wiersz jest konwertowany na pojedynczy obiekt. Wynik wygląda następująco:

[{"a":1,"b":"value1"},{"a":2,"b":"value2"},{"a":3,"b":"value3"}]

9.3 i nowsze

json_aggFunkcja produkuje ten wynik po wyjęciu z pudełka. Automatycznie wymyśla, jak przekonwertować swoje dane wejściowe na JSON i agreguje je w tablicę.

SELECT json_agg(t) FROM t

Nie ma jsonb(wprowadzonej w 9.4) wersji json_agg. Możesz zagregować wiersze w tablicę, a następnie przekonwertować je:

SELECT to_jsonb(array_agg(t)) FROM t

lub połącz json_aggz obsadą:

SELECT json_agg(t)::jsonb FROM t

Moje testy sugerują, że najpierw agregowanie ich w tablicę jest trochę szybsze. Podejrzewam, że dzieje się tak, ponieważ rzutowanie musi przeanalizować cały wynik JSON.

9.2

9.2 nie ma funkcji json_agglub to_json, więc musisz użyć starszego array_to_json:

SELECT array_to_json(array_agg(t)) FROM t

Opcjonalnie możesz dołączyć row_to_jsonwywołanie do zapytania:

SELECT array_to_json(array_agg(row_to_json(t))) FROM t

To konwertuje każdy wiersz na obiekt JSON, agreguje obiekty JSON jako tablicę, a następnie konwertuje tablicę na tablicę JSON.

Nie byłem w stanie dostrzec żadnej znaczącej różnicy w wydajności między nimi.

Przedmiot list

W tej sekcji opisano sposób generowania obiektu JSON, w którym każdy klucz jest kolumną w tabeli, a każda wartość jest tablicą wartości kolumny. Oto wynik, który wygląda następująco:

{"a":[1,2,3], "b":["value1","value2","value3"]}

9.5 i nowsze

Możemy wykorzystać json_build_objectfunkcję:

SELECT
    json_build_object(
        'a', json_agg(t.a),
        'b', json_agg(t.b)
    )
FROM t

Możesz także zagregować kolumny, tworząc pojedynczy wiersz, a następnie przekonwertować go na obiekt:

SELECT to_json(r)
FROM (
    SELECT
        json_agg(t.a) AS a,
        json_agg(t.b) AS b
    FROM t
) r

Zwróć uwagę, że aliasowanie tablic jest absolutnie wymagane, aby zapewnić, że obiekt ma żądane nazwy.

Który z nich jest jaśniejszy, to kwestia opinii. Jeśli korzystasz z tej json_build_objectfunkcji, zdecydowanie zalecam umieszczenie jednej pary klucz / wartość w wierszu, aby poprawić czytelność.

Możesz również użyć array_aggzamiast json_agg, ale moje testy wskazują, że json_aggjest nieco szybszy.

Brak jsonbwersji json_build_objectfunkcji. Możesz agregować w jeden wiersz i konwertować:

SELECT to_jsonb(r)
FROM (
    SELECT
        array_agg(t.a) AS a,
        array_agg(t.b) AS b
    FROM t
) r

W przeciwieństwie do innych zapytań o tego rodzaju wynik, array_aggwydaje się być nieco szybszy podczas używania to_jsonb. Podejrzewam, że jest to spowodowane analizowaniem narzutów i sprawdzaniem poprawności wyniku JSON json_agg.

Lub możesz użyć wyraźnej obsady:

SELECT
    json_build_object(
        'a', json_agg(t.a),
        'b', json_agg(t.b)
    )::jsonb
FROM t

to_jsonbWersja pozwala uniknąć obsady i jest szybszy, według moich badań; ponownie podejrzewam, że jest to spowodowane narzutem związanym z analizowaniem i sprawdzaniem wyniku.

9.4 i 9.3

json_build_objectFunkcja była nowa do 9,5, więc trzeba kruszywo i skonwertować do obiektu w poprzednich wersjach:

SELECT to_json(r)
FROM (
    SELECT
        json_agg(t.a) AS a,
        json_agg(t.b) AS b
    FROM t
) r

lub

SELECT to_jsonb(r)
FROM (
    SELECT
        array_agg(t.a) AS a,
        array_agg(t.b) AS b
    FROM t
) r

w zależności od tego, czy chcesz, jsonczy jsonb.

(9.3 nie ma jsonb.)

9.2

W 9.2 nawet nie to_jsonistnieje. Musisz użyć row_to_json:

SELECT row_to_json(r)
FROM (
    SELECT
        array_agg(t.a) AS a,
        array_agg(t.b) AS b
    FROM t
) r

Dokumentacja

Znajdź dokumentację dotyczącą funkcji JSON w funkcjach JSON .

json_aggznajduje się na stronie funkcji agregujących .

Projekt

Jeśli wydajność jest ważna, upewnij się, że porównujesz swoje zapytania z własnym schematem i danymi, zamiast ufać moim testom.

To, czy jest to dobry projekt, czy nie, zależy tak naprawdę od konkretnej aplikacji. Jeśli chodzi o łatwość konserwacji, nie widzę żadnego szczególnego problemu. Upraszcza kod aplikacji i oznacza, że ​​w tej części aplikacji jest mniej do utrzymania. Jeśli PG może dać dokładnie taki wynik, jakiego potrzebujesz po wyjęciu z pudełka, jedynym powodem, dla którego mogę wymyślić, aby go nie używać, byłyby względy wydajności. Nie odkrywaj na nowo koła i wszystkiego.

Zero

Funkcje agregujące zwykle zwracają, NULLgdy działają na zerowych wierszach. Jeśli jest taka możliwość, możesz użyć, COALESCEaby ich uniknąć. Kilka przykładów:

SELECT COALESCE(json_agg(t), '[]'::json) FROM t

Lub

SELECT to_jsonb(COALESCE(array_agg(t), ARRAY[]::t[])) FROM t

Kredyt do Hannes Landeholm za wskazanie tego

jpmc26
źródło
3
Dziękuję za Twoją odpowiedź. Zainspirowałeś mnie do znalezienia odpowiedzi na moje drugie pytanie, SELECT row_to_json (row (array_agg (ta), array_agg (tb))) FROM t, chociaż wynik ma „f1” i „f2” jako etykiety zamiast a i b.
inżynier
@engineerX Rozszerzyłem moją odpowiedź.
jpmc26
3
W niektórych przypadkach może być niepożądane zwrócenie wartości NULL zamiast pustej tablicy JSON, gdy wewnętrzna funkcja select (z t) zwraca zero wierszy. Jest to spowodowane tym, że funkcje agregujące zawsze zwracają NULL, gdy wybieranie nie obejmuje wierszy i można je rozwiązać przez łączenie: array_to_json (coalesce (array_agg (t), array [] :: record [])).
Hannes Landeholm
3
możesz użyć to_jsonzamiast row_to_jsoniarray_to_json
itsnikolay
Aby wybrać (wiele) określonych kolumn, musisz przekazać je jako pojedynczy argument - okrągłą listę nawiasów SELECT json_agg((column1, column2, ...)) FROM t - zwróć uwagę na dodatkowe nawiasy. To może nie być oczywiste „po wyjęciu z pudełka”.
jave.web
19

Również jeśli chcesz wybrać pole z tabeli i zagregować je jako tablicę.

SELECT json_agg(json_build_object('data_a',a,
                                  'data_b',b,
))  from t;

Wynik nadejdzie.

 [{'data_a':1,'data_b':'value1'}
  {'data_a':2,'data_b':'value2'}]
Himanshu sharma
źródło