如何在一个SELECT查询中区分“不存在子行”和“不存在父行”?

时间:2016-08-05 11:44:48

标签: sql postgresql

假设我有一个表C引用表A和B中的行:

id, a_id, b_id, ...

和一个简单的查询:

SELECT * FROM C WHERE a_id=X AND b_id=Y

我想区分以下情况:

  • A中没有行,其中id = X
  • B中没有行,其中id = Y
  • A和B中的这两行都存在,但是在A中没有行,其中a_id = X和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中关于比尔的记录。

3 个答案:

答案 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)

  • Common Table Expressions

初始数据:

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_idthing_type_id无效,我会得到一行name为NULL且person_idthing_type_id为NULL:

person_id  thing_type_id  name
-------------------------------------
     NULL              1         NULL

如果person_idthing_type_id都有效,但things中没有记录,我会得到一行person_idthing_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)