假设我有一个表C引用表A和B中的行:
id, a_id, b_id, ...
和一个简单的查询:
SELECT * FROM C WHERE a_id=X AND b_id=Y
我想区分以下情况:
上述查询将在所有这些情况下返回空结果。
如果有一个父表,我可以像LEFT JOIN
那样:
SELECT * FROM A LEFT JOIN C ON a.id = c.a_id WHERE c.a_id = X
然后检查结果是否为空(A中没有行),有一行为NULL c.id
(A中的行存在,但C中没有行)或1+行非NULL c.id
(A中的行存在且C中至少有一行存在)。有点乱,但它有效,但我想知道是否有更好的方法,特别是如果有多个父表?
例如:
C是“人拥有的东西”,A是“人”,B是“事物的类型”。当有人问“给我一个Bill所拥有的游戏列表”,并且C中没有这样的记录时,我想只返回一个空列表,只要“Bill”和“games”都存在于相应的表中,如果其中任何一个没有错误代码。因此,如果表C中没有与“比尔”和“游戏”匹配的记录,我想说“我不知道比尔是谁”而不是“比尔没有游戏”如果我没有表A中关于比尔的记录。
答案 0 :(得分:1)
create table a(a_id integer not null primary key);
create table b(b_id integer not null primary key);
create table c(a_id integer not null references a(a_id)
, b_id integer not null references b(b_id)
, primary key (a_id,b_id)
);
insert into a(a_id) values(0),(2),(4),(6);
insert into b(b_id) values(0),(3),(6);
insert into c(a_id,b_id) values(6,6);
PREPARE omg(integer,integer) AS
SELECT EXISTS(SELECT * FROM a where a.a_id = $1) AS a_exists
, EXISTS(SELECT * FROM b where b.b_id = $2) AS b_exists
, EXISTS(SELECT * FROM c where c.a_id = $1 and c.b_id = $2) AS c_exists
;
EXECUTE omg(1,1);
EXECUTE omg(2,1);
EXECUTE omg(1,3);
EXECUTE omg(6,6);
- 使用可选的有效负载:
PREPARE omg2(integer,integer) AS
SELECT val.a_id AS va_id
, val.b_id AS vb_id
, EXISTS(SELECT * FROM a WHERE a.a_id = $1) AS a_exists
, EXISTS(SELECT * FROM b WHERE b.b_id = $2) AS b_exists
, EXISTS(select * FROM c WHERE c.ca_id = val.a_id AND c.cb_id = val.b_id ) AS c_exists
, a.*
, b.*
, c.*
FROM (values ($1,$2)) val(a_id,b_id)
LEFT JOIN a ON a.a_id = val.a_id
LEFT JOIN b ON b.b_id = val.b_id
LEFT JOIN c ON c.ca_id = val.a_id AND c.cb_id = val.b_id
;
EXECUTE omg2(1,1);
EXECUTE omg2(2,1);
EXECUTE omg2(1,3);
EXECUTE omg2(6,6);
答案 1 :(得分:1)
我认为我设法使用以下两个功能获得了满意的解决方案:
绑定到列的子选择,允许我检查行是否存在,(重要的是)获取NULL值(例如SELECT (SELECT id FROM a WHERE id = 1) as a_id)
)
初始数据:
CREATE TABLE people
(
id integer not null primary key,
name text not null
);
CREATE TABLE thing_types
(
id integer not null primary key,
name text not null
);
CREATE TABLE things
(
id integer not null primary key,
person_id integer not null references people(id),
thing_type_id integer not null references thing_types(id),
name text not null
);
INSERT INTO people VALUES (1, 'Bill');
INSERT INTO thing_types VALUES (1, 'game');
INSERT INTO things VALUES (1, 1, 1, 'Duke Nukem');
INSERT INTO things VALUES (2, 1, 1, 'Warcraft 2');
查询:
WITH v AS (
SELECT (SELECT id FROM people WHERE id=<person_id_param>) AS person_id,
(SELECT id FROM thing_types WHERE id=<thing_type_param>) AS thing_type_id
)
SELECT v.person_id, v.thing_type_id, things.name
FROM
v LEFT JOIN things
ON v.person_id = things.person_id AND v.thing_type_id = things.thing_type_id
此查询将始终返回至少一行,我只需要检查第一行的三列中的哪一列(如果有)为NULL。
如果两个父表id都有效并且有一些记录,则它们都不是NULL:
person_id thing_type_id name
-------------------------------------
1 1 Duke Nukem
1 1 Warcraft 2
如果person_id
或thing_type_id
无效,我会得到一行name
为NULL且person_id
或thing_type_id
为NULL:
person_id thing_type_id name
-------------------------------------
NULL 1 NULL
如果person_id
和thing_type_id
都有效,但things
中没有记录,我会得到一行person_id
和thing_type_id
都不为空,但name
为NULL:
person_id thing_type_id name
-------------------------------------
1 1 NULL
由于NOT NULL
上有things.name
约束,我知道此案例只能表示things
中没有匹配的记录。如果things.name
中允许使用NULL,我可以包含things.id
,并检查是否为NULL。
答案 2 :(得分:0)
你有3个案例,第三个案件有点复杂但可以通过在a和b之间使用交叉连接来实现,联盟中的所有三个案例都可以是这样的
select a_id, b_id , 'case 1' from c
where not exists (select 1 from a where a.a_id=c.a_id)
union all
select a_id, b_id ,'case 2' from c
where not exists (select 1 from b where b.b_id=c.b_id)
union all
select a_id, b_id, 'case 3' from a cross join b
where exists (select 1 from c where c.a_id=a.a_id)
and exists (select 1 from c where c.b_id=b.b_id)
and not exists (select 1 from c where c.b_id=b.b_id and c.a_id=a.a_id)