基于String在SQL(Snowflake)中选择一行

Posted

技术标签:

【中文标题】基于String在SQL(Snowflake)中选择一行【英文标题】:Selecting a row in SQL (Snowflake) based on String 【发布时间】:2021-11-09 23:21:14 【问题描述】:

所以我在这里有一个顽固的人,我已经绞尽脑汁一段时间了。

假设我有一张如下表:

ID      Group                 Timestamp   Data
001         A   2021-04-13 12:51:12.063   content121
001  A-Direct   2021-04-13 12:52:13.063   content121
002  A-Direct   2021-04-13 12:50:14.063   content133
003  B-Direct   2021-04-13 12:55:12.063   content132
003         B   2021-04-13 12:56:11.063   content142
003        BA   2021-04-13 12:57:22.063   content153
004         D   2021-04-13 12:10:23.063   content113
004         C   2021-04-13 12:11:43.063   content144
005         C   2021-04-13 12:12:12.063   content111
005         A   2021-04-13 12:13:23.063   content100
005  D-Direct   2021-04-13 12:15:23.063   content121
006         A   2021-04-13 12:51:12.063   content121
006  B-Direct   2021-04-13 12:52:13.063   content121
007  A-Direct   2021-04-13 12:51:12.063   content121
007         A   2021-04-13 12:52:13.063   content121
008  B-Direct   2021-04-13 12:55:12.063   content132
008         B   2021-04-13 12:56:11.063   content142
008  B-Direct   2021-04-13 12:57:22.063   content153
009  B-Direct   2021-04-13 12:55:12.063   content132
009  C-Direct   2021-04-13 12:56:11.063   content142
009  D-Direct   2021-04-13 12:57:22.063   content153

所以我需要一个表,其中每一行都包含一个不同的 ID。但是 ID 的选择标准有点复杂。

默认选择应该是最近的条目,通过TIMESTAMP 选择。

但复杂性来自任何具有-Direct 行的 ID。具体来说,如果一行有多个条目,一个是(例如)A,另一个是A-Direct,我们需要A。只有当字母匹配时才会出现这种情况。从ID = 006 的案例中可以看出,我们想要B-Direct,因为它的对应项是A

所以我正在寻找的核心逻辑是

如果 ID 有以相同字符串开头的行,并且其中一个以 -Direct 结尾,则将其替换为已删除的 -Direct

最终输出:

ID      Group
001         A
002  A-Direct
003        BA
004         C
005  D-Direct
006  B-Direct
007         A
008         B
009  D-Direct

为了更清楚起见,以下是每个 ID 发生的情况的概述:

ID 001:A 后跟 A-Direct,所以我们将 A-Direct 替换为 A ID 002:A-Direct 是唯一的结果,简单! ID 003:BABB-Direct 是不同的,因此我们坚持使用最新的 BA。 ID 004:不直接,所以我们只取最近的,C ID 005: D-Direct 是最新的,但是因为没有 D ,所以我们坚持使用 D-Direct ID 006: B-Direct 是最新的,但是因为没有 B ,所以我们坚持使用 B-Direct ID 007:A-Direct 后面跟着A,所以我们只取最近的一个,没问题。 ID 008:BB-Direct(x2)出现在这里,因此我们可以使用B。 ID 009:所有选项都是直接的,所以我们使用最新的,D-Direct

我可以弄清楚如何获得最新的,但是根据上述标准,我不确定如何调整

WITH data AS (
    select d.*,
        rank() over (
            partition by ID
            order by TIMESTAMP DESC
        ) as num
    FROM table d
)
select ID, TIMESTAMP
    from data
    where num = 1

【问题讨论】:

每个 id 可以有多个 *-direct 条目吗?还是每个 id 具有相同组的多行? @EdmCoff,是的!参见 ID 008,有两个 B-Direct。但是两者的优先级都比B低。我还添加了一个ID 009来进一步说明。如果 Direct 上有两个不同的组具有相同的 ID(B-Direct AND C-Direct)或(B-Direct AND B-Direct),那么它只是最新的。 【参考方案1】:

我可能会从以下内容开始。它不是超级漂亮,因此可能有更好的解决方案,但我认为它可以满足您的需求。

WITH data AS (
    select d.*,
        rank() over (
            partition by ID
            order by TIMESTAMP DESC
        ) as num
    FROM table d
)
select ID, 
 CASE 
  WHEN EXISTS (SELECT * FROM table t WHERE t.id = d.id AND t.group || '-Direct' = d.group) 
   THEN replace(d.group, '-Direct') 
   ELSE d.group 
 END group
    from data d
    where num = 1

这将获取每个 id 的最新 ID(使用您当前的代码),但 select 子句中的 case/exists 语句会检查是否存在没有“-Direct”的匹配项,如果有,我们从字符串中删除“-Direct”。

【讨论】:

这不适用于我的数据。是什么||做什么? || 等价于concat。所以t.group || '-Direct' = d.group 正在检查是否存在与 d.group(例如“B-Direct”)匹配的 t.group(例如“B”)【参考方案2】:

使用:

SELECT ID
   ,CASE WHEN MIN(group) OVER(PARTITION BY ID, REPLACE(group, '-Direct'))
             = MAX(group) OVER(PARTITION BY ID, REPLACE(group, '-Direct'))
         THEN group
         ELSE REPLACE(group, '-Direct')
    END AS grp
FROM tab
QUALIFY RANK() OVER(PARTITION BY ID ORDER BY TIMESTAMP DESC) = 1;

Qualify 确保获取每个时间戳的最新值,并且 case 表达式处理“-Direct”覆盖。

【讨论】:

以上是关于基于String在SQL(Snowflake)中选择一行的主要内容,如果未能解决你的问题,请参考以下文章

在 Snowflake 中处理多个 SQL 语句的存储过程

在没有 JavaScript 的情况下将 Oracle PL/SQL 移植到 Snowflake

Snowflake 中的 SQL Server 等效表类型是啥

仅使用 REST 和 SQL 命令批量插入到 Snowflake

在 Snowflake 中使用 SQL 进行漏斗分析

在 SnowFlake DB 中并行执行存储过程中的 SQL 语句