【问题标题】:Converting this SQL query to use a window function, or any other more efficient method将此 SQL 查询转换为使用窗口函数或任何其他更有效的方法
【发布时间】:2020-12-25 05:29:25
【问题描述】:

我有一个巨大的表格,其中包含所有客户的详细信息,例如他们的电话号码和电子邮件地址。该表可以为每个客户包含多行,表明他们在某些时候更改了他们的电话号码、电子邮件地址或其他个人资料信息。每行都有一个 day 列,表示配置文件更改发生的日期(即当天的配置文件状态)。

我想提取每个客户曾经在其个人资料中拥有的每个电话号码和电子邮件地址,并添加一个布尔标志(称为“实时”),指示当前与他们的个人资料相关联的电话号码和电子邮件地址(即我们在表中为该客户提供的最新记录)。

这是我当前的查询:

SELECT DISTINCT
    customer_id,
    phone_number,
    email AS email_address,
    CASE
        WHEN day = (
            SELECT
                MAX(temp.day)
            FROM
                customer AS temp
            WHERE
                customer.customer_id = temp.customer_id
        )
        THEN true
        ELSE false
    END AS live
FROM
    customer

考虑到数百万不同的客户有数十亿行,我认为这是非常低效的。我怎样才能改进这个查询来实现我想要的(可能是一个窗口函数?),或者完全不同的方式可以更好地实现我想要的?

【问题讨论】:

  • 请澄清:如果客户有第一个电话号码A,然后将其更改为B,最后又将其恢复为A(电子邮件保持不变),您是否希望看到三行这个客户还是只有两个?
  • @MarmiteBomber 是的,会有 3 行
  • 好的@KOB,如果你想保留所有行,你应该从查询中删除 DISTINCT...

标签: sql optimization aggregate window-functions presto


【解决方案1】:

您可以使用row_number() 设置标志:

SELECT 
    customer_id,
    phone_number,
    email AS email_address,
    (ROW_NUMBER() over(partition by customer_id ORDER BY day DESC) = 1) as is_live
FROM customer

我不确定 Presto 是否将条件理解为布尔值 - 如果不是,您可以这样做:

CASE WHEN ROW_NUMBER() over(partition by customer_id ORDER BY day DESC) = 1
    THEN true
    ELSE false
END as is_live

【讨论】:

  • 我更喜欢这个答案,因为据我所知,行是否唯一并不重要。使用 RANK() 而不是 ROW_NUMBER() 会有什么好处吗?
  • @KOB: rank() 是否允许平局(也就是说,如果您有多个记录,每个 customer_idday)。如果没有关系,ROW_NUMBER()RANK() 是相同的。
  • phone_number被重用时,该查询无法处理,即 A -> B -> A 将产生 3 行,而不是两个 B + A(live) as预计。
  • @MarmiteBomber:OP 不想过滤行,他们想标记它们。此外,row_number() 每个customer_id 只提供一个1
  • @MarmiteBomber:我想提取每个客户在他们的个人资料中拥有的每个电话号码和电子邮件地址,并添加一个布尔标志(称为“实时”)指示哪个电话号码并且电子邮件目前与他们的个人资料相关联对我来说听起来很清楚
【解决方案2】:

如果记录是唯一的,您可以使用:

SELECT customer_id, phone_number, email AS email_address,
       (CASE WHEN RANK() OVER (PARTITION BY customer_id ORDER BY day DESC) = 1
             THEN true ELSE false
        END) as is_live
FROM customer c;

Presto 支持布尔值,因此您不需要 CASE 表达式。但我保留了它,因为你的代码中有它。

【讨论】:

  • 如果包含“day”列,它们是唯一的,因为每个客户每天最多有一条记录。这样可以吗?
  • 你能解释一下为什么我会使用 RANK() 而不是 ROW_NUMBER() 吗?
  • @KOB。 . .在这种情况下,它们可能是等效的。但是,问题的措辞向我表明,您可能在数据中有联系。如果一个客户有多个最近日期的记录,那么所有记录都标记为true,因为您没有指定如何打破关系。
【解决方案3】:

这是您最初的相关子查询翻译成 Group Max,结果与 RANK 版本相同:

CASE
    WHEN day = MAX(temp.day) over (partition by customer_id)
    THEN true
    ELSE false
END AS live

【讨论】:

    【解决方案4】:

    要显示每个客户曾经在其个人资料中拥有的每个电话号码和电子邮件地址,其含义是这两种资源的每个组合仅一次,请使用以下查询。

    这里是示例日期

    create table tab as
    select 1 customer_id, 'A' phone_number, '1@1' email, DATE'2020-01-01' day  from dual union all
    select 1 customer_id, 'B' phone_number, '1@1' email, DATE'2020-02-01' day  from dual union all
    select 1 customer_id, 'A' phone_number, '1@1' email, DATE'2020-03-01' day  from dual union all
    select 2 customer_id, 'C' phone_number, '1@1' email, DATE'2020-01-01' day  from dual union all
    select 2 customer_id, 'C' phone_number, '1@1' email, DATE'2020-04-01' day  from dual 
    ;
    

    查询

    with live as (
    select CUSTOMER_ID, PHONE_NUMBER, EMAIL, DAY ,
    case when row_number() over (partition by CUSTOMER_ID order by day DESC) = 1 then 1 else 0 end is_live
    from tab) 
    select   CUSTOMER_ID, PHONE_NUMBER, EMAIL, 
    case when max(is_live) = 1 then 'true' else 'false' end is_life
    from live
    group by CUSTOMER_ID, PHONE_NUMBER, EMAIL
    

    生产的

    CUSTOMER_ID P EMA IS_LI
    ----------- - --- -----
              1 B 1@x false
              1 A 1@x true 
              2 C 2@x true 
    

    子查询标识最后(实时)记录,GROUP BY 查询仅生成资源的唯一组合(类似,但比您的解决方案中的 SELECT DISTINCT 更好 - 如果相同的资源在实时和非实时行)。

    【讨论】:

      猜你喜欢
      • 2014-06-10
      • 1970-01-01
      • 1970-01-01
      • 2023-02-05
      • 1970-01-01
      • 2021-12-24
      • 1970-01-01
      • 1970-01-01
      • 2021-10-09
      相关资源
      最近更新 更多