Я пытаюсь сопоставить результаты запроса с JSON с помощью row_to_json()
функции, добавленной в PostgreSQL 9.2.
У меня возникли проблемы с определением наилучшего способа представления соединенных строк как вложенных объектов (отношения 1: 1)
Вот что я пробовал (код настройки: таблицы, образцы данных, а затем запрос):
-- some test tables to start out with:
create table role_duties (
id serial primary key,
name varchar
);
create table user_roles (
id serial primary key,
name varchar,
description varchar,
duty_id int, foreign key (duty_id) references role_duties(id)
);
create table users (
id serial primary key,
name varchar,
email varchar,
user_role_id int, foreign key (user_role_id) references user_roles(id)
);
DO $$
DECLARE duty_id int;
DECLARE role_id int;
begin
insert into role_duties (name) values ('Script Execution') returning id into duty_id;
insert into user_roles (name, description, duty_id) values ('admin', 'Administrative duties in the system', duty_id) returning id into role_id;
insert into users (name, email, user_role_id) values ('Dan', 'someemail@gmail.com', role_id);
END$$;
Сам запрос:
select row_to_json(row)
from (
select u.*, ROW(ur.*::user_roles, ROW(d.*::role_duties)) as user_role
from users u
inner join user_roles ur on ur.id = u.user_role_id
inner join role_duties d on d.id = ur.duty_id
) row;
Я обнаружил, что если бы использовал ROW()
, то мог бы разделить полученные поля на дочерний объект, но, похоже, это ограничено одним уровнем. Я не могу вставить больше AS XXX
утверждений, так как считаю, что мне нужно в этом случае.
Мне предоставляются имена столбцов, потому что я привожу их к соответствующему типу записи, например ::user_roles
, в случае результатов этой таблицы.
Вот что возвращает этот запрос:
{
"id":1,
"name":"Dan",
"email":"someemail@gmail.com",
"user_role_id":1,
"user_role":{
"f1":{
"id":1,
"name":"admin",
"description":"Administrative duties in the system",
"duty_id":1
},
"f2":{
"f1":{
"id":1,
"name":"Script Execution"
}
}
}
}
Что я хочу сделать, так это сгенерировать JSON для объединений (опять же 1: 1 в порядке) таким образом, чтобы я мог добавлять объединения и представлять их как дочерние объекты родителей, к которым они присоединяются, то есть следующим образом:
{
"id":1,
"name":"Dan",
"email":"someemail@gmail.com",
"user_role_id":1,
"user_role":{
"id":1,
"name":"admin",
"description":"Administrative duties in the system",
"duty_id":1
"duty":{
"id":1,
"name":"Script Execution"
}
}
}
}
Любая помощь приветствуется. Спасибо за прочтение.
Ответы:
Обновление: В PostgreSQL 9.4 это улучшает много с введением
to_json
,json_build_object
,json_object
иjson_build_array
, хотя это многословным из - за необходимости назвать все поля в явном виде :select json_build_object( 'id', u.id, 'name', u.name, 'email', u.email, 'user_role_id', u.user_role_id, 'user_role', json_build_object( 'id', ur.id, 'name', ur.name, 'description', ur.description, 'duty_id', ur.duty_id, 'duty', json_build_object( 'id', d.id, 'name', d.name ) ) ) from users u inner join user_roles ur on ur.id = u.user_role_id inner join role_duties d on d.id = ur.duty_id;
Для более старых версий читайте дальше.
Это не ограничивается одной строкой, это просто немного больно. Вы не можете использовать псевдонимы для составных типов строк
AS
, поэтому вам нужно использовать выражение подзапроса с псевдонимом или CTE для достижения эффекта:select row_to_json(row) from ( select u.*, urd AS user_role from users u inner join ( select ur.*, d from user_roles ur inner join role_duties d on d.id = ur.duty_id ) urd(id,name,description,duty_id,duty) on urd.id = u.user_role_id ) row;
производит через http://jsonprettyprint.com/ :
Вы захотите использовать,
array_to_json(array_agg(...))
когда у вас есть отношения 1: много, кстати.Вышеупомянутый запрос в идеале должен быть записан как:
select row_to_json( ROW(u.*, ROW(ur.*, d AS duty) AS user_role) ) from users u inner join user_roles ur on ur.id = u.user_role_id inner join role_duties d on d.id = ur.duty_id;
... но
ROW
конструктор PostgreSQL не принимаетAS
псевдонимы столбцов. К сожалению.К счастью, они оптимизируют то же самое. Сравните планы:
ROW
конструктора с удаленными псевдонимами, поэтому он выполняетПоскольку CTE являются забором оптимизации, перефразирование версии вложенного подзапроса для использования связанных CTE (
WITH
выражений) может не работать так же хорошо и не приведет к тому же плану. В этом случае вы как бы застряли с уродливыми вложенными подзапросами, пока мы не получим некоторые улучшенияrow_to_json
или способROW
более напрямую переопределить имена столбцов в конструкторе.В любом случае, в общем, принцип заключается в том, что там, где вы хотите создать объект json со столбцами
a, b, c
, и вы хотите, чтобы вы могли просто написать недопустимый синтаксис:вместо этого вы можете использовать скалярные подзапросы, возвращающие значения типа строки:
(SELECT x FROM (SELECT a AS name1, b AS name2, c AS name3) x) AS outername
Или же:
(SELECT x FROM (SELECT a, b, c) AS x(name1, name2, name3)) AS outername
Кроме того, имейте в виду, что вы можете составлять
json
значения без дополнительных кавычек, например, если вы поместите вывод ajson_agg
в arow_to_json
, внутреннийjson_agg
результат не будет цитироваться как строка, он будет включен непосредственно как json.например, в произвольном примере:
SELECT row_to_json( (SELECT x FROM (SELECT 1 AS k1, 2 AS k2, (SELECT json_agg( (SELECT x FROM (SELECT 1 AS a, 2 AS b) x) ) FROM generate_series(1,2) ) AS k3 ) x), true );
вывод:
Обратите внимание, что
json_agg
продукт[{"a":1,"b":2}, {"a":1,"b":2}]
,, не был экранирован снова, как этоtext
было бы.Это означает, что вы можете составлять операции json для построения строк, вам не всегда нужно создавать чрезвычайно сложные составные типы PostgreSQL, а затем вызывать
row_to_json
выходные данные.источник
json_build_object
сделает мою жизнь намного проще, но почему-то я не уловил этого, когда увидел примечания к выпуску. Иногда для начала вам просто нужен конкретный пример.json_build_object
немного больше - это настоящий переломный момент.Я добавляю это решение, потому что принятый ответ не предполагает отношения N: N. иначе: коллекции коллекций объектов
Если у вас есть отношения N: N, то clausula -
with
это ваш друг. В моем примере я хотел бы построить древовидное представление следующей иерархии.Следующий запрос представляет объединения.
SELECT reqId ,r.description as reqDesc ,array_agg(s.id) s.id as suiteId , s."Name" as suiteName, tc.id as tcId , tc."Title" as testCaseTitle from "Requirement" r inner join "Has" h on r.id = h.requirementid inner join "TestSuite" s on s.id = h.testsuiteid inner join "Contains" c on c.testsuiteid = s.id inner join "TestCase" tc on tc.id = c.testcaseid GROUP BY r.id, s.id;
Поскольку вы не можете выполнять несколько агрегатов, вам нужно использовать «WITH».
with testcases as ( select c.testsuiteid,ts."Name" , tc.id, tc."Title" from "TestSuite" ts inner join "Contains" c on c.testsuiteid = ts.id inner join "TestCase" tc on tc.id = c.testcaseid ), requirements as ( select r.id as reqId ,r.description as reqDesc , s.id as suiteId from "Requirement" r inner join "Has" h on r.id = h.requirementid inner join "TestSuite" s on s.id = h.testsuiteid ) , suitesJson as ( select testcases.testsuiteid, json_agg( json_build_object('tc_id', testcases.id,'tc_title', testcases."Title" ) ) as suiteJson from testcases group by testcases.testsuiteid,testcases."Name" ), allSuites as ( select has.requirementid, json_agg( json_build_object('ts_id', suitesJson.testsuiteid,'name',s."Name" , 'test_cases', suitesJson.suiteJson ) ) as suites from suitesJson inner join "TestSuite" s on s.id = suitesJson.testsuiteid inner join "Has" has on has.testsuiteid = s.id group by has.requirementid ), allRequirements as ( select json_agg( json_build_object('req_id', r.id ,'req_description',r.description , 'test_suites', allSuites.suites ) ) as suites from allSuites inner join "Requirement" r on r.id = allSuites.requirementid ) select * from allRequirements
Что он делает, так это создает объект JSON в небольшой коллекции элементов и объединяет их по каждой
with
клаузуле.Результат:
источник
Мое предложение по обеспечению ремонтопригодности в долгосрочной перспективе - использовать VIEW для создания грубой версии вашего запроса, а затем использовать функцию, как показано ниже:
CREATE OR REPLACE FUNCTION fnc_query_prominence_users( ) RETURNS json AS $$ DECLARE d_result json; BEGIN SELECT ARRAY_TO_JSON( ARRAY_AGG( ROW_TO_JSON( CAST(ROW(users.*) AS prominence.users) ) ) ) INTO d_result FROM prominence.users; RETURN d_result; END; $$ LANGUAGE plpgsql SECURITY INVOKER;
В данном случае объект prominence.users - это представление. Так как я выбрал users. *, Мне не придется обновлять эту функцию, если мне нужно обновить представление, чтобы включить больше полей в запись пользователя.
источник