SQL中分区上的字符串连接?

Posted

技术标签:

【中文标题】SQL中分区上的字符串连接?【英文标题】:String Concat over partition in SQL? 【发布时间】:2021-01-23 04:44:29 【问题描述】:

我想在排名到新列(group by 似乎不起作用)后连接周围的行(在以下示例中只有周围的 2 行),这是我拥有的数据:

架构 (MySQL v8.0)

CREATE TABLE log_table (
  `user_id` VARCHAR(5),
  `date_time` DATETIME,
  `event_name` VARCHAR(10),
  `trivial` int
);

INSERT INTO log_table
  (`user_id`, `date_time`, `event_name`, `trivial`)
VALUES
  ('001', '2020-12-10 10:00:02', 'c', 3),
  ('001', '2020-12-10 10:00:01', 'b', 9),
  ('001', '2020-12-10 10:00:40', 'e', 2),
  ('001', '2020-12-10 10:00:20', 'd', 6),
  ('001', '2020-12-10 10:00:00', 'a', 1),
  ('002', '2020-12-10 10:00:10', 'C', 9),
  ('002', '2020-12-10 10:00:50', 'D', 0),
  ('002', '2020-12-10 10:00:02', 'A', 2),
  ('002', '2020-12-10 10:00:09', 'B', 4);

为了说明我想要做什么。我可以使用 sum 子句对数值求和,如下所示:Query #1

SELECT *,
       SUM(trivial)
         over(
           PARTITION BY user_id
           ORDER BY user_id, date_time ROWS BETWEEN 2 preceding AND 2 following)
       AS
       trivial_new
FROM   log_table; 
user_id date_time event_name trivial trivial_new
001 2020-12-10 10:00:00 a 1 13
001 2020-12-10 10:00:01 b 9 19
001 2020-12-10 10:00:02 c 3 21
001 2020-12-10 10:00:20 d 6 20
001 2020-12-10 10:00:40 e 2 11
002 2020-12-10 10:00:02 A 2 15
002 2020-12-10 10:00:09 B 4 15
002 2020-12-10 10:00:10 C 9 15
002 2020-12-10 10:00:50 D 0 13

View on DB Fiddle

对于字符串字段event_name,我尝试了这个sn-p:Query #2

SELECT *,
       Concat(event_name)
         over(
           PARTITION BY user_id
           ORDER BY user_id, date_time ROWS BETWEEN 2 preceding AND 2 following)
       AS
       event_name_new
FROM   log_table

这是我的预期结果:

user_id date_time event_name trivial event_name_new
001 2020-12-10 10:00:00 a 1 abc
001 2020-12-10 10:00:01 b 9 abcd
001 2020-12-10 10:00:02 c 3 abcde
001 2020-12-10 10:00:20 d 6 bcde
001 2020-12-10 10:00:40 e 2 cde
002 2020-12-10 10:00:02 A 2 ABC
002 2020-12-10 10:00:09 B 4 ABCD
002 2020-12-10 10:00:10 C 9 ABCD
002 2020-12-10 10:00:50 D 0 BCD

但是 Query #2 无法将我带到这里,我已经在 Google 上搜索过,但我只能找到关于 group by 的信息(请参阅 this 和 this 和 this)。

我知道我可以通过使用 LAGLEAD(对于以下行)来解决这个问题,但是我需要连接新列,当我需要连接许多行时,我需要做很多手动工作比如用, 等分隔符连接它们。

我可以在不使用LAGLEAD 的情况下一步完成吗?

【问题讨论】:

根据您提供的示例数据和预期输出,我不清楚使用LAG 有什么问题? @Nick 如果sum 可以在没有LAG 的情况下这样做,我想知道是否可以连接字符串? @Nick 例如,我想将周围的 5 行(在该行之前的 5 行和在该行之后的 5 行)连接到新列(如何)我可以巧妙地做到这一点? 不幸的是,没有办法为字符串连接指定窗口函数......你能用一些更复杂的预期输出数据来编辑你的问题吗? (例如 2 之前,1 之后),这将更容易想出一个解决方案 @Nick 请检查更新的问题。 【参考方案1】:

关联子查询可能是最简单的解决方案:

with l as (
      select l.*,
             cast(row_number() over (partition by user_id order by date_time) as signed) as seqnum
      from log_table l
     )
select l.*,
       (select group_concat(l2.event_name order by l2.date_time separator '')
        from l l2
        where l2.user_id = l.user_id and
              l2.seqnum between l.seqnum - 2 and l.seqnum + 2
       ) as new_event_name
from l;

如果事件名称是一个字符,可以去掉相关子查询,使用字符串操作:

with l as (
      select l.*, full_concat,
             cast(row_number() over (partition by user_id order by date_time) as signed) as seqnum
      from log_table l join
           (select user_id,  group_concat(event_name order by date_time separator '') as full_concat
            from log_table l
            group by user_id
           ) ll
           using (user_id)
     )
select l.*, substr(full_concat, greatest(seqnum - 2, 1), least(5, seqnum + 2))
from l;

Here 是一个 dbfiddle。

【讨论】:

【参考方案2】:

您可以使用 CTE 解决此问题:首先计算每个 user_id 的行号,按 date_time 排序;然后根据行号在该行的最小/最大行号范围内(从行号 - 之前到行号 + 之后)将表加入到自身中。然后你可以在 JOINed 表中 GROUP_CONCAT event_name 字段:

SET @before := 2;
SET @after := 2;

WITH rns AS (
  SELECT *, 
         CAST(ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY date_time) AS SIGNED) AS rn
  FROM log_table
)
SELECT r1.user_id, r1.date_time, r1.event_name,
       GROUP_CONCAT(r2.event_name SEPARATOR '') AS event_name_new
FROM rns r1
JOIN rns r2 ON r2.user_id = r1.user_id
           AND r2.rn BETWEEN r1.rn - @before AND r1.rn + @after
GROUP BY r1.user_id, r1.date_time, r1.event_name
ORDER BY r1.user_id, r1.rn

输出(2 之前和 2 之后):

user_id     date_time               event_name  event_name_new
001         2020-12-10 10:00:00     a           abc
001         2020-12-10 10:00:01     b           abcd
001         2020-12-10 10:00:02     c           abcde
001         2020-12-10 10:00:20     d           bcde
001         2020-12-10 10:00:40     e           cde
002         2020-12-10 10:00:02     A           ABC
002         2020-12-10 10:00:09     B           ABCD
002         2020-12-10 10:00:10     C           ABCD
002         2020-12-10 10:00:50     D           BCD

Demo on db-fiddle

【讨论】:

对我来说太复杂了,让我花点时间消化一下。你能做一些解释吗?比如代码中的一些cmets。 @LernerZhang 看看db-fiddle.com/f/5dKasZPNK7SqjnXvsj7qqX/13,它显示了中间 CTE 的输出,以及如果你取消分组,你从最终查询中得到什么。这可能会有所帮助... @LernerZhang 我得走了……有什么问题请在 cmets 留言,我会尽快回复。 @LernerZhang 睡了之后我意识到可以简化查询。请查看我的编辑。 @LernerZhang 是的,COUNT(*) 不再需要,这是我编辑后不再需要的原始查询的一部分。我已将其从答案中删除。您确实需要 ORDER BY 子句中的 r1.rn,否则行将不会按 date_time 排序。

以上是关于SQL中分区上的字符串连接?的主要内容,如果未能解决你的问题,请参考以下文章

如何设置 SQL Server 连接字符串?

使用 *** 连接 sql server

SMT 通过连接器配置创建 kafka 连接器字符串分区键

在运行 azure app 服务时更改连接字符串

Sql-Server 配置远程连接以及C#连接字符串

基于连接表的分区表