【问题标题】:How to first join tables and then filter rows to keep the greatest per group如何首先连接表然后过滤行以保持每组最大
【发布时间】:2022-11-27 20:22:01
【问题描述】:

我正在尝试写一个 SELECT 声明第一的加入两个表和然后通过保持每组的最大值来过滤行。

例子

以下两个表格描述了动物园中的访客。

  • visitors 表格包括所有去过动物园的独特人物;每人一排。
  • activity_log 表格描述了每位游客在参观期间在动物园做了什么;每排一排活动.

生成数据(可重现)

以下 SQL 代码兼容MySQL:

-- visitors
CREATE TABLE visitors(
                      visitor_id       INTEGER  NOT NULL PRIMARY KEY, 
                      country_of_birth VARCHAR(7) NOT NULL
                      );
                      
INSERT INTO visitors(visitor_id, country_of_birth) VALUES
                    (1,          'Bolivia'),
                    (2,          'UK'),
                    (3,          'UK'),
                    (4,          'Bolivia'),
                    (5,          'UK'),
                    (6,          'UK'),
                    (7,          'France'),
                    (8,          'USA'),
                    (9,          'UK'),
                    (10,         'France');

-- activity_log
CREATE TABLE activity_log(
                          visitor_id         INTEGER  NOT NULL,
                          FOREIGN KEY (visitor_id) REFERENCES visitors(visitor_id),
                          activity_time      DATETIME  NOT NULL,
                          activity_name      VARCHAR(14) NOT NULL,
                          what_was_purchased VARCHAR(8)
                          );
                       
INSERT INTO activity_log(visitor_id, activity_time,          activity_name,     what_was_purchased) VALUES
                        (1,          '2020-09-03 11:15:00',  'visit lions',     NULL),
                        (1,          '2020-09-03 10:30:00',  'use restroom',    NULL),
                        (1,          '2020-09-03 10:10:00',  'visit reptiles',  NULL),
                        (1,          '2020-09-03 10:45:00',  'purchase',        'coffee'),
                        (2,          '2021-02-10 15:30:00',  'visit giftshop',  NULL),
                        (2,          '2021-02-10 15:02:00',  'visit zebras',    NULL),
                        (2,          '2021-02-10 15:45:00',  'visit giraffes',  NULL),
                        (3,          '2021-07-07 13:04:00',  'visit reptiles',  NULL),
                        (3,          '2021-07-07 13:50:00',  'visit bears',     NULL),
                        (3,          '2021-07-07 13:40:00',  'purchase',        'icecream'),
                        (3,          '2021-07-07 14:12:00',  'purchase',        'coffee'),
                        (4,          '2021-08-19 11:33:00',  'visit monkeys',   NULL),
                        (4,          '2021-08-19 11:18:00',  'visit lions',     NULL),
                        (4,          '2021-08-19 11:47:00',  'use restroom',    NULL),
                        (5,          '2022-04-12 10:55:00',  'visit zebras',    NULL),
                        (5,          '2022-04-12 11:42:00',  'purchase',        'coffee'),
                        (5,          '2022-04-12 10:45:00',  'purchase',        'hotdog'),
                        (5,          '2022-04-12 11:27:00',  'purchase',        'popcorn'),
                        (6,          '2022-04-12 14:00:00',  'purchase',        'icecream'),
                        (7,          '2022-05-09 12:38:00',  'use restroom',    NULL),
                        (7,          '2022-05-09 12:52:00',  'visit reptiles',  NULL),
                        (7,          '2022-05-09 12:30:00',  'visit zebras',    NULL),
                        (8,          '2022-07-07 15:00:00',  'purchase',        'popcorn'),
                        (8,          '2022-07-07 15:10:00',  'visit birds',     NULL),
                        (9,          '2022-07-11 12:13:00',  'purchase',        'popcorn'),
                        (9,          '2022-07-11 11:23:00',  'purchase',        'coffee'),
                        (9,          '2022-07-11 11:00:00',  'visit lions',     NULL),
                        (9,          '2022-07-11 11:54:00',  'visit monkeys',   NULL),
                        (10,         '2022-08-31 9:30:00',   'use restroom',    NULL);

我要的查询

所有购买过东西的英国游客的表格,以及那是什么。如果一个人购买了不止一件东西,请显示最后购买的物品。因此,一个包含 2 列的表:(1) visitor_id,(2) what_was_purchased。

期望的输出

#> +------------+--------------------+
#> | visitor_id | what_was_purchased |
#> +------------+--------------------+
#> |          3 | coffee             |
#> |          5 | coffee             |
#> |          6 | icecream           |
#> |          9 | popcorn            |
#> +------------+--------------------+#> 

我的尝试

我已经走了这么远,甚至这个似乎也不行:

SELECT * 
FROM visitors AS v
LEFT JOIN activity_log AS al ON v.visitor_id = al.visitor_id 
      AND v.country_of_birth = 'UK' 
      AND al.visitor_id IN (
                        SELECT  visitor_id
                        FROM activity_log
                        GROUP BY visitor_id
                        HAVING SUM(CASE WHEN what_was_purchased IS NULL THEN 0 ELSE 1 END)  > 0
                       );

-- +------------+------------------+------------+---------------------+----------------+--------------------+
-- | visitor_id | country_of_birth | visitor_id | activity_time       | activity_name  | what_was_purchased |
-- +------------+------------------+------------+---------------------+----------------+--------------------+
-- |          1 | Bolivia          |       NULL | NULL                | NULL           | NULL               |
-- |          2 | UK               |       NULL | NULL                | NULL           | NULL               |
-- |          3 | UK               |          3 | 2021-07-07 13:04:00 | visit reptiles | NULL               |
-- |          3 | UK               |          3 | 2021-07-07 13:50:00 | visit bears    | NULL               |
-- |          3 | UK               |          3 | 2021-07-07 13:40:00 | purchase       | icecream           |
-- |          3 | UK               |          3 | 2021-07-07 14:12:00 | purchase       | coffee             |
-- |          4 | Bolivia          |       NULL | NULL                | NULL           | NULL               |
-- |          5 | UK               |          5 | 2022-04-12 10:55:00 | visit zebras   | NULL               |
-- |          5 | UK               |          5 | 2022-04-12 11:42:00 | purchase       | coffee             |
-- |          5 | UK               |          5 | 2022-04-12 10:45:00 | purchase       | hotdog             |
-- |          5 | UK               |          5 | 2022-04-12 11:27:00 | purchase       | popcorn            |
-- |          6 | UK               |          6 | 2022-04-12 14:00:00 | purchase       | icecream           |
-- |          7 | France           |       NULL | NULL                | NULL           | NULL               |
-- |          8 | USA              |       NULL | NULL                | NULL           | NULL               |
-- |          9 | UK               |          9 | 2022-07-11 12:13:00 | purchase       | popcorn            |
-- |          9 | UK               |          9 | 2022-07-11 11:23:00 | purchase       | coffee             |
-- |          9 | UK               |          9 | 2022-07-11 11:00:00 | visit lions    | NULL               |
-- |          9 | UK               |          9 | 2022-07-11 11:54:00 | visit monkeys  | NULL               |
-- |         10 | France           |       NULL | NULL                | NULL           | NULL               |
-- +------------+------------------+------------+---------------------+----------------+--------------------+
-- 19 rows in set (0.00 sec)

解释我的语法

  • 我做了

    LEFT JOIN activity_log AS al ON v.visitor_id = al.visitor_id 
          AND v.country_of_birth = 'UK'
    

    基于 this answer,在连接之前已经只有 UK 行。如您所见,效果并不理想,因为我还有其他国家/地区的NULL。但我想我可以用 WHERE 子句过滤那些。 (但是,我不知道为什么它没有像参考答案中那样被删除)。

  • 我做了

    AND al.visitor_id IN (
                            SELECT  visitor_id
                            FROM activity_log
                            GROUP BY visitor_id
                            HAVING SUM(CASE WHEN what_was_purchased IS NULL THEN 0 ELSE 1 END)  > 0
                           );
    

    过滤在加入之前至少购买过一次的人。在这里,ID 为2 的访客也是空的,应该被删除。

怎么办?

为了这个问题,让我们忽略 NULL 行并假装通过“仅UK”和“至少一次购买”过滤成功:

-- pseudo result I manually edited
-- +------------+------------------+------------+---------------------+----------------+--------------------+
-- | visitor_id | country_of_birth | visitor_id | activity_time       | activity_name  | what_was_purchased |
-- +------------+------------------+------------+---------------------+----------------+--------------------+
-- |          3 | UK               |          3 | 2021-07-07 13:04:00 | visit reptiles | NULL               |
-- |          3 | UK               |          3 | 2021-07-07 13:50:00 | visit bears    | NULL               |
-- |          3 | UK               |          3 | 2021-07-07 13:40:00 | purchase       | icecream           |
-- |          3 | UK               |          3 | 2021-07-07 14:12:00 | purchase       | coffee             |           |
-- |          5 | UK               |          5 | 2022-04-12 10:55:00 | visit zebras   | NULL               |
-- |          5 | UK               |          5 | 2022-04-12 11:42:00 | purchase       | coffee             |
-- |          5 | UK               |          5 | 2022-04-12 10:45:00 | purchase       | hotdog             |
-- |          5 | UK               |          5 | 2022-04-12 11:27:00 | purchase       | popcorn            |
-- |          6 | UK               |          6 | 2022-04-12 14:00:00 | purchase       | icecream           |
-- |          9 | UK               |          9 | 2022-07-11 12:13:00 | purchase       | popcorn            |
-- |          9 | UK               |          9 | 2022-07-11 11:23:00 | purchase       | coffee             |
-- |          9 | UK               |          9 | 2022-07-11 11:00:00 | visit lions    | NULL               |
-- |          9 | UK               |          9 | 2022-07-11 11:54:00 | visit monkeys  | NULL               |
-- +------------+------------------+------------+---------------------+----------------+--------------------+

我怎样才能让每个人只获得与上次购买对应的行(如activity_time列所示)?请注意,人体内的时间是混乱的。我已经看到 this answer,它似乎就在现场,但我不知道如何将它合并到现有查询中。

【问题讨论】:

    标签: mysql sql group-by greatest-n-per-group


    【解决方案1】:

    假设您使用的是最新版本的 MySql,典型的方法是使用行号窗口函数方法:

    with l as (
        select l.visitor_id, l.what_was_purchased, 
          Row_Number() over(partition by l.visitor_id order by l.activity_time desc) rn
      from activity_log l
      join visitors v on v.visitor_id = l.visitor_id and v.country_of_birth = 'UK'
      where l.activity_name = 'purchase'
    )
    select visitor_id, what_was_purchased
    from l
    where rn = 1;
    

    Demo Fiddle

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2019-05-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-07-18
      • 2021-06-21
      相关资源
      最近更新 更多