如何将表与父/子列连接在一起,并仅检索具有匹配父或子的行,但不检索两者(第2部分)?

时间:2014-08-11 04:43:24

标签: sql postgresql

这是对question I posted last week

的跟进

上一个问题的摘要: 我想加入两个表格,然后选择拥有booking_codeparent_booking_code的人并显示结果。

在我们的代码中多次尝试实现答案后,我们发现我们还希望优先考虑booking_code人。也就是说,如果我们有下表和数据('kelvin'和'michael'的条目与之前不同):

保留

booking_code | description
-------------+-------------
alpha        | alpha code
beta         | beta code
gamma        | gamma code
omega        | omega code

来宾(注意:-表示NULL

name        | booking_code   | parent_booking_code
------------+----------------+----------------------
andrew      | alpha          | -
kelvin      | -              | beta             ***
michael     | beta           | -                ***
nancy       | -              | beta
olaf        | gamma          | -
patricia    | -              | gamma
quincy      | -              | omega
raphael     | kappa          | -
stanley     | -              | kappa
timmy       | -              | delta

预期输出: 我们希望查询给我们:

   name   | code
----------+-------
 andrew   | alpha
 michael  | beta
 olaf     | gamma
 quincy   | omega

Previous solution会为代码kelvin指定名称beta,因为我们正在挑选具有“最小”名称的人。

这是我们尝试过的一件事,它没有给我们andrew - alpha

SELECT
    g.name,
    reservations.booking_code AS code
FROM
(
    SELECT *, booking_code AS bc
    FROM guests AS g1
    WHERE booking_code IN
        (SELECT DISTINCT parent_booking_code FROM guests WHERE parent_booking_code IS NOT NULL)
    UNION
    SELECT *, parent_booking_code AS bc
    FROM guests AS g2
    WHERE parent_booking_code NOT IN (SELECT booking_code FROM guests WHERE booking_code IS NOT NULL)
) AS g

JOIN
    reservations ON
        g.bc = reservations.booking_code ;

/*
  name   | code  
---------+-------
 olaf    | gamma
 quincy  | omega
 michael | beta
(3 rows)
*/

可能是因为我们没有对UNION的记录booking_code而没有相应的parent_booking_code。但是,我们认为如果我们添加它,UNION会很大,而且会很慢。

我们也试过这个,但无法弄清楚如何通过不同的代码过滤记录:

SELECT
    guests.name AS name,
    guests.combo_code AS code,
    MIN(guests.ranking) AS rank
FROM
    reservations
JOIN
    (SELECT 
        COALESCE (booking_code, parent_booking_code) AS combo_code,
        CASE WHEN booking_code IS NOT NULL THEN 1
        ELSE 2
        END AS ranking,
        guests.*
     FROM guests
    ) AS guests ON
        guests.combo_code = reservations.booking_code
GROUP BY name, code
ORDER BY rank, name;
/*
   name   | code  | rank 
----------+-------+------
 andrew   | alpha |    1
 michael  | beta  |    1
 olaf     | gamma |    1
 kelvin   | beta  |    2
 nancy    | beta  |    2
 patricia | gamma |    2
 quincy   | omega |    2
(7 rows)
 */

用于创建表和数据的SQL查询:

CREATE TABLE reservations (
   booking_code VARCHAR(15),
   description VARCHAR(15)
);

INSERT INTO reservations VALUES
('alpha', 'alpha code'), ('beta', 'beta code'), ('gamma', 'gamma code'), ('omega', 'omega code') ;

CREATE TABLE guests (
    name VARCHAR(20),
    booking_code VARCHAR(15),
    parent_booking_code VARCHAR(15)
);

INSERT INTO guests VALUES
('andrew', 'alpha', NULL), ('kelvin', NULL, 'beta'), ('michael', 'beta', NULL), ('nancy', NULL, 'beta'),
('olaf', 'gamma', NULL), ('patricia', NULL, 'gamma'), ('quincy', NULL, 'omega'), ('raphael', 'kappa', NULL),
('stanley', NULL, 'kappa'), ('timmy', NULL, 'delta') ;

2 个答案:

答案 0 :(得分:1)

试试这个

with cte as (
SELECT
      guests.name AS name,
      guests.combo_code AS code,
      MIN(guests.ranking) AS rank
FROM
      reservations
JOIN
(SELECT 
    COALESCE (booking_code, parent_booking_code) AS combo_code,
    CASE WHEN booking_code IS NOT NULL THEN 1
    ELSE 2
    END AS ranking,
    guests.*
 FROM guests
) AS guests ON
    guests.combo_code = reservations.booking_code
GROUP BY name, code
ORDER BY rank, name
) , cte2 as (
select code,min(rank) as rank from cte group by code 
)
select name,cte.code,cte.rank from cte inner join cte2 on cte.rank=cte2.rank where   
cte.rank=cte2.rank and cte.code=cte2.code

答案 1 :(得分:0)

让我们从上一个问题中提供的解决方案中删除GROUP BY,ORDER BY和聚合,并在投影中添加一个额外的字段,告诉我们用户是否实际具有hi-priority booking_code。

SELECT
    guests.name AS name,
    reservations.booking_code AS code,
    COALESCE(reservations.booking_code = guests.booking_code, 0) AS has_good
FROM
    reservations
JOIN
    guests ON
        reservations.booking_code = guests.booking_code OR
        reservations.booking_code = guests.parent_booking_code

COALESCE就在这里,如果guests.booking_code为NULL,则表达式的结果为0而不是NULL。在您的DBMS中,COALESCE可能被称为ISNULL或IFNULL。

现在让我们将它编组为BY代码,然后选择max(has_good)。我们不选择客人名称,因为我们还不需要它。它给我们的是,我们现在知道每个代码是否有该代码的高pri用户:

SELECT
    reservations.booking_code AS code,
    MAX(COALESCE(reservations.booking_code = guests.booking_code, 0)) AS has_good
FROM
    reservations
JOIN
    guests ON
        reservations.booking_code = guests.booking_code OR
        reservations.booking_code = guests.parent_booking_code
GROUP BY code;

有了这些信息,我们现在可以稍微更改原始查询,这样如果有一位客人对给定代码进行了高价预订,我们会选择这样的客人,否则我们会选择任何一位客人:< / p>

SELECT
    MIN(guests.name) AS name,
    reservations.booking_code AS code
FROM
    reservations
JOIN
    guests ON
        reservations.booking_code = guests.booking_code OR
        reservations.booking_code = guests.parent_booking_code
JOIN (
    SELECT
        reservations.booking_code AS code,
        MAX(COALESCE(reservations.booking_code = guests.booking_code, 0)) AS has_good
    FROM
        reservations
    JOIN
        guests ON
            reservations.booking_code = guests.booking_code OR
            reservations.booking_code = guests.parent_booking_code
    GROUP BY code
) AS availability ON availability.code = reservations.booking_code
WHERE availability.has_good = COALESCE(reservations.booking_code = guests.booking_code, 0)
GROUP BY code;

请注意,此查询只是您上一个问题的查询,并且是针对我们上面构建的子查询加入的。然后它会过滤掉没有高优先级预订代码的用户,如果已知代码至少有一个用户拥有该代码。

我在你的样本数据集上测试了它,我相信它应该适用于一般情况。

编辑:作者在评论中询问是否可以删除JOIN中的一个预订表。确实,这是一个稍微修改过的查询,只能从预订中选择一次:

SELECT
    MIN(guests.name) AS name,
    availability.code AS code
FROM
(
    SELECT
        reservations.booking_code AS code,
        MAX(COALESCE(reservations.booking_code = guests.booking_code, 0)) AS has_good
    FROM
        reservations
    JOIN
        guests ON
            reservations.booking_code = guests.booking_code OR
            reservations.booking_code = guests.parent_booking_code
    GROUP BY code
) AS availability
JOIN
    guests ON
        availability.code = guests.booking_code OR
        availability.code = guests.parent_booking_code
WHERE availability.has_good = COALESCE(availability.code = guests.booking_code, 0)
GROUP BY code;