Reputation: 13577
I am using PostgreSQL 9.4.
I'm trying to establish many-to-many relation between two tables: "user" and "friend". As far as I know, the only foreign key can establish only one-to-one relation, so, I'm using "mediator" - additional table "user_friend" to establish many-to-many relations.
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";
It works fine. Now I need to get a user and all his friends. This query will return ids of all user's friends:
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
So, I'm trying to modify this query to retrieve all the data from friend
table instead of retrieving only ids from user-friend
table:
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
But here I'm receiving the error: ERROR: more than one row returned by a subquery used as an expression
.
Upvotes: 1
Views: 545
Reputation: 380
I have little or no experience with PostgreSQL. But I do have some experience with relational databases and I don't quite follow the design that you have chosen. From what I understand, you have 'Users' who could be friends. Here friends are also users aren't they? So wouldn't a 'user' and 'user_friend' table be enough? 'user' and 'user_friend' would have a one to many relationship. I have tried to replicate that in schema as follows:
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);
And to the best of my understanding, I was able to get the desired result:
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 fiddle:http://sqlfiddle.com/#!15/708dd/6/0
Upvotes: 1
Reputation: 491
There is an error when the following where clause comparison returns more than one row"
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
...
change '=' operator to 'in' and mark sure no null values on the friend column:
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
...
Explain Query feature in pgAdmin can help figure out how long does each join and sub-query takes to completer the whole statement
Upvotes: 3