如何在按唯一日期时间排序时删除重复项

Posted

技术标签:

【中文标题】如何在按唯一日期时间排序时删除重复项【英文标题】:How to remove duplicates while sorting by unique datetime 【发布时间】:2021-10-28 02:02:25 【问题描述】:

我正在使用一个相当糟糕的数据源,包含我需要的信息的列位于 varchar(max) 内并被分隔。但是,数据可以跨多行重复,因此我正在尝试删除这些重复项。

这可以通过修剪我感兴趣的列来完成,因为当我重复发生时,“ID”会重新附加到列的末尾。然后我将其区别对待,然后将结果连接起来;不漂亮。

示例数据和我目前使用的查询SQL Fiddle

数据表

| id | callID |                callDateTime |                             history |
|----|--------|-----------------------------|-------------------------------------|
|  1 |      1 | 2021-01-01 10:00:00.0000000 |         Amount: 10, Ref:123, ID:123 |
|  2 |      1 | 2021-01-01 10:01:00.0000000 | Amount: 10, Ref:123, ID:123, ID:123 |
|  3 |      2 | 2021-01-01 11:00:00.0000000 |       Amount:12.44, Ref:SIS, ID:124 |
|  4 |      2 | 2021-01-01 11:02:00.0000000 |       Amount:11.22, Ref:Dad, ID:124 |
|  5 |      2 | 2021-01-01 11:01:00.0000000 |       Amount:11.22, Ref:Mum, ID:124 |
|  6 |      3 | 2021-01-01 12:00:00.0000000 |                   Amount:11, ID:125 |

查询

select CallID, Concat([1],',', [2],',',[3])
from
(
  select CallID, historyEdit, ROW_NUMBER() over (partition by callID order by callID) as rowNum
  from
  (
    select distinct callID, 
    substring(history, 0, charindex(', ID:',history)) historyEdit
    from test
  ) a
 )b
PIVOT(max(historyEdit) for rowNum IN ([1],[2],[3])) piv

结果

| CallID |                                                                   |
|--------|-------------------------------------------------------------------|
|      1 |                                             Amount: 10, Ref:123,, |
|      2 | Amount:11.22, Ref:Dad,Amount:11.22, Ref:Mum,Amount:12.44, Ref:SIS |
|      3 |                                                       Amount:11,, |

问题是我需要确保连接部分按照事件发生的顺序执行。在上面您会看到 CallID 2 的顺序错误,因为信息 3 在信息 2 之前出现,我确实尝试先按 callDateTime 对基表进行排序,然后运行查询,但它似乎确实产生了一些随机结果。有时它会以正确的顺序排列,有时则不会。我认为这是因为我没有在查询中指定任何 order by 子句。

在结果中包含 callDateTime 会导致 distinct 不返回 unqiue 数据行,因为 callDateTime 对于该重复的数据行仍然是唯一的

我正在使用 SQL Server v12

期望的结果

| CallID |                                                                   |
|--------|-------------------------------------------------------------------|
|      1 |                                             Amount: 10, Ref:123,, |
|      2 | Amount:12.44, Ref:SiS,Amount:11.22, Ref:Mum,Amount:11.22, Ref:Dad |
|      3 |                                                       Amount:11,, |

【问题讨论】:

您能更清楚地了解您要完成的工作吗?您要删除什么重复数据? 【参考方案1】:

如果我理解正确,您想为每个 callid 拆分历史并重新组合(不重复)。如果是这样,您可以使用string_split()string_agg()

select callid, string_agg(value, ', ')
from (select distinct t.callid, s.value
      from test t cross apply
           (select trim(s.value) as value
            from string_split(t.history, ',') s
           ) s
     ) st
group by callid;

Here 是一个 dbfiddle。

【讨论】:

结果明智的是,我相信你已经明白我所追求的,虽然我不在这个版本的 SQL Server 上,所以无法访问 string_agg 或 string_split 值得补充的是,历史字段中可能存在“重复”数据,但在正版中存在重复。唯一不真实的情况是,在删除最终的“ID:xxx”部分时,该给定 callID 的整个历史列是否完全重复,这就是我走不同路径的原因 我已经更新了问题中的一些内容以使其更清晰【参考方案2】:

如果您确定以下记录的数量,您可以使用select 中的TOP 子句对记录进行排序,然后再转置结果:

select callID, historyEdit
   from
   (
     select distinct top 100000 callID, callDateTime,
     substring(history, 0, charindex(', ID:',history)) historyEdit
     from test
     order by callDateTime
   )t

请查看结果here

【讨论】:

callDateTime 将导致 distinct 仍然返回重复数据,因为 callDateTime 对于该行是唯一的 @Chris 但如果我没记错的话,您也可以选择其中一个使用 group by (最小或最大 callDateTime)。请查看here【参考方案3】:

计算字符串的一种方法是交叉应用,使用序号拆分器将“历史”列分成可以枚举的组件。结果非常接近问题中提供的内容。也许提供的预期结果不准确具有代表性?像这样的

序数分离器described here

CREATE FUNCTION [dbo].[DelimitedSplit8K_LEAD]
--===== Define I/O parameters
        (@pString VARCHAR(8000), @pDelimiter CHAR(1))
RETURNS TABLE WITH SCHEMABINDING AS
 RETURN
  WITH E1(N) AS (
                 SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL 
                 SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL 
                 SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1
                ),                          --10E+1 or 10 rows
       E2(N) AS (SELECT 1 FROM E1 a, E1 b), --10E+2 or 100 rows
       E4(N) AS (SELECT 1 FROM E2 a, E2 b), --10E+4 or 10,000 rows max
 cteTally(N) AS (--==== This provides the "zero base" and limits the number of rows right up front
                     -- for both a performance gain and prevention of accidental "overruns"
                 SELECT 0 UNION ALL
                 SELECT TOP (DATALENGTH(ISNULL(@pString,1))) ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) FROM E4
                ),
cteStart(N1) AS (--==== This returns N+1 (starting position of each "element" just once for each delimiter)
                 SELECT t.N+1
                   FROM cteTally t
                  WHERE (SUBSTRING(@pString,t.N,1) = @pDelimiter OR t.N = 0) 
                )
--===== Do the actual split. The ISNULL/NULLIF combo handles the length for the final element when no delimiter is found.
 SELECT ItemNumber = ROW_NUMBER() OVER(ORDER BY s.N1),
        Item = SUBSTRING(@pString,s.N1,ISNULL(NULLIF((LEAD(s.N1,1,1) OVER (ORDER BY s.N1) - 1),0)-s.N1,8000))
   FROM cteStart s
;

查询

with
unq_cte as (
    select distinct callID
    from #test),
exp_cte as (
    select callID, callDateTime , dl.*,
           row_number() over (partition by callID, dl.Item order by callDateTime) as rn
    from #test t
         cross apply dbo.DelimitedSplit8K_LEAD(t.history, ',') dl)
select t.callID, 
       stuff((select ',' + case when rn>1 then '' else Item end
              from exp_cte tt
              where t.callID = tt.callID
                    and ltrim(rtrim(Item)) not like 'ID%'
              order by tt.callDateTime, tt.ItemNumber for xml path('')), 1, 1, '') [value1]
from unq_cte t
group by t.callID;
callID  value1
1       Amount: 10, Ref:123,,
2       Amount:12.44, Ref:SIS,Amount:11.22, Ref:Mum,, Ref:Dad
3       Amount:11

【讨论】:

以上是关于如何在按唯一日期时间排序时删除重复项的主要内容,如果未能解决你的问题,请参考以下文章

使用postgresql TEXT类型按字段排序时如何删除重复项?

给定一个未排序的数组,如何删除重复项然后对其进行排序?

如何按日期和 ID 删除 MySQL 表上的重复项? [复制]

从排序数组中删除重复项(不同的最终结果)

SQL Server 删除重复项

如何从 C# 数组中删除重复项?