【问题标题】:Identify a specific sequence of records in a table标识表中的特定记录序列
【发布时间】:2009-12-03 20:21:21
【问题描述】:

假设一个包含 TransactionId、ItemId、Code、EffectiveDate 和 CreateDate 字段的表。

+---------------+--------+------+------ -+------------------+ |交易ID |项目 ID |代码 |生效日期 |创建日期 | +---------------+--------+------+------ -+------------------+ | 1| 1| 8| 2009 年 12 月 2 日下午 1:13| 2009 年 12 月 2 日下午 1:13| +---------------+--------+------+------ -+------------------+ | 4| 1| 51|2009 年 12 月 2 日上午 11:08| 2009 年 12 月 3 日上午 9:01| +---------------+--------+------+------ -+------------------+ | 2| 1| 14|2009 年 12 月 2 日上午 11:09|2009 年 12 月 2 日上午 11:09| +---------------+--------+------+------ -+------------------+ | 3| 1| 61| 2009 年 12 月 3 日上午 8:33| 2009 年 12 月 3 日上午 8:33| +---------------+--------+------+------ -+------------------+ | 5| 1| 28| 2009 年 12 月 3 日上午 9:33| 2009 年 12 月 3 日上午 9:33| +---------------+--------+------+------ -+------------------+ | 6| 1| 9| 2009 年 12 月 3 日下午 1:58| 2009 年 12 月 3 日下午 1:58| +---------------+--------+------+------ -+------------------+

我需要获取给定 ItemId 出现序列 51、61、9 的记录集,按 EffectiveDate 排序。这些记录之间可能有其他记录,其中包含其他代码。

在这种情况下,我将返回 TransactionId 的 4、3 和 6,如下所示。

+---------------+--------+------+------ -+------------------+ |交易ID |项目 ID |代码 |生效日期 |创建日期 | +---------------+--------+------+------ -+------------------+ | 4| 1| 51|2009 年 12 月 2 日上午 11:08| 2009 年 12 月 3 日上午 9:01| +---------------+--------+------+------ -+------------------+ | 3| 1| 61| 2009 年 12 月 3 日上午 8:33| 2009 年 12 月 3 日上午 8:33| +---------------+--------+------+------ -+------------------+ | 6| 1| 9| 2009 年 12 月 3 日下午 1:58| 2009 年 12 月 3 日下午 1:58| +---------------+--------+------+------ -+------------------+

注意:

  • 这不是我需要确定的唯一序列,但它说明了问题。
  • 记录可以乱序插入到表中;也就是说,可以先插入 61 记录,然后是 51,然后是 9。您可以在示例中看到这一点,其中代码 51 记录的 CreateDate 晚于 EffectiveDate。
  • 序列的顺序很重要。因此,序列 61、9、51 不会返回任何记录,但 51、61、9 会。

如果 DB 方法简单(即没有游标或过于复杂的存储过程),则它是理想的选择,但代码方法也可以工作,尽管它会导致大量数据从 DB 传输出来。

环境为 SQL Server 2005 和 C#/.NET 3.5。

【问题讨论】:

    标签: c# sql sql-server sql-server-2005


    【解决方案1】:

    实际上,您可以利用ranking/windowing functions 和/或CTEsrecursive CTEs 获得一些相当简单的解决方案。

    创建一个过程,该过程接受一个基于字符的逗号分隔的 Code 值列表,该列表以您希望它们的顺序查找 - 使用由序列和 Code 值组成的 dozen possible ways to split this list into a table/set 中的任何一个,结果在具有如下结构的表中:

    declare @sequence table (sequence int not null, Code int not null);
    

    一旦您有了这个,只需将已排序的表与给定 ItemId 的相同代码值上的源表连接起来,对源集进行排序即可 - 一旦您对源集进行了过滤和排序,您就可以简单地根据匹配的序列值再次加入 - 这听起来很复杂,但实际上它会是这样的单个查询:

    with srcData as (
        select  row_number() over(order by t.EffectiveDate) as rn,
                t.TransactionId, t.ItemId, t.Code, t.EffectiveDate, t.CreateDate
        from    #TableName t
        join    @sequence s
        on      t.Code = s.Code
        where   t.ItemId = @item_id
    )
    select  d.TransactionId, d.ItemId, d.Code, d.EffectiveDate, d.CreateDate
    from    srcData d
    join    @sequence s
    on      d.rn = s.sequence
    and     d.Code = s.Code
    order by d.rn;
    

    仅此一项并不能保证您获得的结果集与您正在寻找的结果集相同,但是将数据暂存到临时表中并在代码周围添加一些简单的检查就可以解决问题(例如,添加校验和验证和代码值的总和)

    declare @tempData table (rn int, TransactionId smallint, ItemId smallint, Code smallint, EffectiveDate datetime, CreateDate datetime);
    
    with srcData as (
        select  row_number() over(order by t.EffectiveDate) as rn,
                t.TransactionId, t.ItemId, t.Code, t.EffectiveDate, t.CreateDate
        from    #TableName t
        join    @sequence s
        on      t.Code = s.Code
        where   t.ItemId = @item_id
    )
    insert  @tempData
            (rn, TransactionId, ItemId, Code, EffectiveDate, CreateDate)
    select  d.rn, d.TransactionId, d.ItemId, d.Code, d.EffectiveDate, d.CreateDate
    from    srcData d
    join    @sequence s
    on      d.rn = s.sequence
    and     d.Code = s.Code;
    
    -- Verify we have matching hash/sums    
    if
    (
        ( (select sum(Code) from @sequence) = (select sum(Code) from @tempData) )
        and
        ( (select checksum_agg(checksum(sequence, Code)) from @sequence) = (select checksum_agg(checksum(rn, Code)) from @tempData) )
    )
    begin;
        -- Match - return the resultset
        select  d.TransactionId, d.ItemId, d.Code, d.EffectiveDate, d.CreateDate
        from    @tempData d
        order by d.rn;
    
    end;
    

    如果您想全部内联,您可以使用不同的方法利用 CTE 和递归来执行运行总和/总计和类似 OrdPath 的比较(尽管您仍然需要解析序列字符数据进入数据集)

    -- Sequence data with running total
    with sequenceWithRunningTotal as
    (
        -- Anchor
        select  s.sequence, s.Code, s.Code as runningTotal, cast(s.Code as varchar(8000)) as pth,
                sum(s.Code) over(partition by 1) as sumCode
        from    @sequence s
        where   s.sequence = 1
        -- Recurse
        union all
        select  s.sequence, s.Code, b.runningTotal + s.Code as runningTotal,
                b.pth + '.' + cast(s.Code as varchar(8000)) as pth,
                b.sumCode as sumCode
        from    @sequence s
        join    sequenceWithRunningTotal b
        on      s.sequence = b.sequence + 1
    ),
    -- Source data with sequence value
    srcData as 
    (
        select  row_number() over(order by t.EffectiveDate) as rn,
                t.TransactionId, t.ItemId, t.Code, t.EffectiveDate, t.CreateDate,
                sum(t.Code) over(partition by 1) as sumCode
        from    #TableName t
        join    @sequence s
        on      t.Code = s.Code
        where   t.ItemId = @item_id
    ),
    -- Source data with running sum
    sourceWithRunningSum as
    (
        -- Anchor
        select  t.rn, t.TransactionId, t.ItemId, t.Code, t.EffectiveDate, t.CreateDate,
                t.Code as runningTotal, cast(t.Code as varchar(8000)) as pth,
                t.sumCode
        from    srcData t
        where   t.rn = 1
        -- Recurse
        union all
        select  t.rn, t.TransactionId, t.ItemId, t.Code, t.EffectiveDate, t.CreateDate,
                s.runningTotal + t.Code as runningTotal,
                s.pth + '.' + cast(t.Code as varchar(8000)) as pth,
                t.sumCode
        from    srcData t
        join    sourceWithRunningSum s
        on      t.rn  = s.rn + 1
    )
    select  d.TransactionId, d.ItemId, d.Code, d.EffectiveDate, d.CreateDate
    from    sourceWithRunningSum d
    join    sequenceWithRunningTotal s
    on      d.rn = s.sequence
    and     d.Code = s.Code
    and     d.runningTotal = s.runningTotal
    and     d.pth = s.pth
    and     d.sumCode = s.sumCode
    order by d.rn;
    

    【讨论】:

      【解决方案2】:

      如果简单(即没有游标或过于复杂的存储过程),DB 方法是理想的选择

      我不相信纯 DB 方法(“纯”意味着仅使用 SQL SELECT)是实用的,因为我设想的 SQL 类型需要非常复杂的自连接、字段连接、MAX() 函数等。这对于 Joe Celko 的“聪明人的 SQL”一书中的一个谜题,SQL 类型可能是一个有趣的学术答案,但我认为这不适合生产代码。

      我认为现实的方法是编写某种循环来跟踪状态。一般来说,您的问题与编写代码以对 TCPIP 数据包进行状态检查以进行垃圾邮件过滤或扫描信用卡交易以查找欺诈模式非常相似。所有这些问题都具有相似的特征:您对当前行(记录)执行的操作取决于您之前看到的记录(上下文)......而这方面需要保存状态变量。

      如果您想避免往返数据以进行分析,看起来 Transact-SQL 是提高性能的最佳方式。或者使用托管 CLR 来利用 C# 语法,同时仍将处理保留在数据库引擎中。

      【讨论】:

        【解决方案3】:

        这只是我的想法,未经测试,因此可能需要一些调整:

        SELECT DISTINCT
             T.TransactionID,
             T.ItemID,
             T.Code,
             T.EffectiveDate,
             T.CreateDate
        FROM
             My_Table T
        INNER JOIN (
             SELECT
                  T1.TransactionID,
                  T2.TransactionID,
                  T3.TransactionID
             FROM
                  My_Table T1
             INNER JOIN My_Table T2 ON
                  T2.ItemID = T1.ItemID AND
                  T2.Code = 61 AND
                  T2.EffectiveDate > T1.EffectiveDate
             INNER JOIN My_Table T3 ON
                  T3.ItemID = T1.ItemID AND
                  T3.Code = 9 AND
                  T3.EffectiveDate > T2.EffectiveDate
             WHERE
                  T1.Code = 51
             ) SQ ON
             SQ.TransactionID = T1.TransactionID OR
             SQ.TransactionID = T2.TransactionID OR
             SQ.TransactionID = T3.TransactionID
        

        【讨论】:

        • 显然,这会受到限制,因为您正在硬编码您正在寻找的序列数量,并且需要您在扩展所需序列数量时继续附加子查询/连接拉出来。
        猜你喜欢
        • 2019-11-28
        • 1970-01-01
        • 2022-01-17
        • 2020-11-15
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多