Postgres:如何从具有多对多关系的子表中检索数据?

时间:2015-12-19 05:36:08

标签: sql postgresql

我正在使用PostgreSQL 9.4。

我试图在两个表之间建立多对多关系:" user"和朋友"。据我所知,唯一的外键只能建立一对一的关系,因此,我使用" mediator" - 附加表" user_friend"建立多对多的关系。

CREATE TABLE "public"."user" (
    "email" varchar(36) NOT NULL COLLATE "default",
    "password" varchar(16) NOT NULL COLLATE "default",
    "id" uuid NOT NULL DEFAULT uuid_generate_v4(),
    CONSTRAINT "User_pkey" PRIMARY KEY ("id") NOT DEFERRABLE INITIALLY IMMEDIATE
)
WITH (OIDS=FALSE);
ALTER TABLE "public"."user" OWNER TO "postgres";
CREATE UNIQUE INDEX  "users_id_key" ON "public"."user" USING btree("id" ASC NULLS LAST);


CREATE TABLE "public"."friend" (
    "id" uuid NOT NULL DEFAULT uuid_generate_v4(),
    "name" varchar(36) NOT NULL COLLATE "default",
    CONSTRAINT "ability_pkey" PRIMARY KEY ("id") NOT DEFERRABLE INITIALLY IMMEDIATE,
)
WITH (OIDS=FALSE);
ALTER TABLE "public"."friend" OWNER TO "postgres";
CREATE UNIQUE INDEX  "ability_id_key" ON "public"."friend" USING btree("id" ASC NULLS LAST);


CREATE TABLE "public"."user_friend" (
    "id" uuid NOT NULL DEFAULT uuid_generate_v4(),
    "owner" uuid NOT NULL,
    "friend" uuid NOT NULL,
    CONSTRAINT "ability_relation_pkey" PRIMARY KEY ("id") NOT DEFERRABLE INITIALLY IMMEDIATE,
    CONSTRAINT "owner" FOREIGN KEY ("owner") REFERENCES "public"."user" ("id") ON UPDATE NO ACTION ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE,
    CONSTRAINT "friend" FOREIGN KEY ("friend") REFERENCES "public"."friend" ("id") ON UPDATE NO ACTION ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE
)
WITH (OIDS=FALSE);
ALTER TABLE "public"."user_friend" OWNER TO "postgres";

工作正常。现在我需要得到一个用户和他所有的朋友。此查询将返回所有用户朋友的ID:

select row_to_json(t)
from (
    select public.user.email, (
        select array_to_json(array_agg(row_to_json(ability_relations)))
      from (
        select public.user_friend.friend
        from public.user_friend
        where public.user_friend.owner=public.user.id 
      ) ability_relations
    ) as abilities
    from public.user
) t

因此,我尝试修改此查询以从friend表中检索所有数据,而不是仅从user-friend表中检索ID:

select row_to_json(t)
from (
    select public.user.email, (
        select array_to_json(array_agg(row_to_json(ability_relations)))
      from (
        select * 
        from public.friend
        where public.friend.id=(
            select public.user_friend.friend
            from public.user_friend
            where public.user_friend.owner=public.user.id
          )     
      ) ability_relations
    ) as abilities
    from public.user
) t

但在这里我收到错误:ERROR: more than one row returned by a subquery used as an expression

  1. 我应该如何更改此查询以将用户及其朋友检索为JSON对象?
  2. 像这样的查询是否可以广泛使用,或者它太复杂而无法频繁使用它并可能对性能产生负面影响?

2 个答案:

答案 0 :(得分:3)

以下where子句比较返回多行"

时出错
select row_to_json(t)
from (
...
   where public.friend.id=(
                select public.user_friend.friend
                from public.user_friend
                where public.user_friend.owner=public.user.id
...

改变' ='运营商到' in'并确保在朋友列上没有空值:

select row_to_json(t)
from (
...
   where public.friend.id in(
                select public.user_friend.friend
                from public.user_friend
                where public.user_friend.owner=public.user.id
...

pgAdmin中的解释查询功能可以帮助确定每个连接和子查询完成整个语句所需的时间

Explain Query feature in pgAdmin

答案 1 :(得分:1)

我对PostgreSQL的经验很少或根本没有。但我确实有一些关系数据库的经验,我不太关注你选择的设计。根据我的理解,你有'用户'可能是朋友。这里的朋友也是用户不是吗?那么'user'和'user_friend'表就不够了吗? 'user'和'user_friend'会有一对多的关系。我试图在模式中复制它,如下所示:

CREATE TABLE "public"."user" (
    "id" uuid NOT NULL DEFAULT uuid_generate_v4(),
    "password" varchar(16) NOT NULL COLLATE "default",
    "name" varchar(36) NOT NULL COLLATE "default",
    "email" varchar(36) NOT NULL COLLATE "default",
    CONSTRAINT "User_pkey" PRIMARY KEY ("id") NOT DEFERRABLE INITIALLY IMMEDIATE
)
WITH (OIDS=FALSE);
CREATE UNIQUE INDEX  "users_id_key" ON "public"."user" USING btree("id" ASC NULLS LAST);

CREATE TABLE "public"."user_friend" (
    "owner" uuid NOT NULL,
    "friend" uuid NOT NULL,
    CONSTRAINT "owner" FOREIGN KEY ("owner") REFERENCES "public"."user" ("id") ON UPDATE NO ACTION ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE,
    CONSTRAINT "friend" FOREIGN KEY ("friend") REFERENCES "public"."user" ("id") ON UPDATE NO ACTION ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE
)
WITH (OIDS=FALSE);

据我所知,我能够得到理想的结果:

select row_to_json(t)
from (
    select public_user.email, (
        select array_to_json(array_agg(row_to_json(ability_relations)))
      from (
        select * 
        from public.user
        where public.user.id in(
            select public.user_friend.friend
            from public.user_friend
            where public.user_friend.owner=public_user.id
          )     
      ) ability_relations
    ) as abilities
    from public.user as public_user
) t

SQL小提琴:http://sqlfiddle.com/#!15/708dd/6/0