Определить конкретную последовательность записей в таблице - PullRequest
1 голос
/ 03 декабря 2009

Предположим, таблица с полями TransactionId, ItemId, Code, EffectiveDate и CreateDate.

+---------------+--------+------+------------------+------------------+
| TransactionId | ItemId | Code |   EffectiveDate  |     CreateDate   |
+---------------+--------+------+------------------+------------------+
|              1|       1|     8| 12/2/2009 1:13 PM| 12/2/2009 1:13 PM|
+---------------+--------+------+------------------+------------------+
|              4|       1|    51|12/2/2009 11:08 AM| 12/3/2009 9:01 AM|
+---------------+--------+------+------------------+------------------+
|              2|       1|    14|12/2/2009 11:09 AM|12/2/2009 11:09 AM|
+---------------+--------+------+------------------+------------------+
|              3|       1|    61| 12/3/2009 8:33 AM| 12/3/2009 8:33 AM|
+---------------+--------+------+------------------+------------------+
|              5|       1|    28| 12/3/2009 9:33 AM| 12/3/2009 9:33 AM|
+---------------+--------+------+------------------+------------------+
|              6|       1|     9| 12/3/2009 1:58 PM| 12/3/2009 1:58 PM|
+---------------+--------+------+------------------+------------------+

Мне нужно получить набор записей, где последовательность 51, 61, 9 встречается для данного ItemId, отсортированного по EffectiveDate. Между этими записями могут быть другие записи с другими кодами.

В этом случае я бы вернул TransactionId 4, 3 и 6, как показано ниже.

+---------------+--------+------+------------------+------------------+
| TransactionId | ItemId | Code |   EffectiveDate  |     CreateDate   |
+---------------+--------+------+------------------+------------------+
|              4|       1|    51|12/2/2009 11:08 AM| 12/3/2009 9:01 AM|
+---------------+--------+------+------------------+------------------+
|              3|       1|    61| 12/3/2009 8:33 AM| 12/3/2009 8:33 AM|
+---------------+--------+------+------------------+------------------+
|              6|       1|     9| 12/3/2009 1:58 PM| 12/3/2009 1:58 PM|
+---------------+--------+------+------------------+------------------+

Обратите внимание, что:

  • Это не единственная последовательность, которую мне нужно идентифицировать, но она иллюстрирует проблему.
  • Записи могут быть вставлены в таблицу не по порядку; то есть сначала может быть вставлена ​​запись 61, затем - 51, а затем - 9. Это можно увидеть в примере, где для записи кода 51 CreateDate позже, чем EffectiveDate.
  • Порядок последовательности имеет значение. Таким образом, последовательность 61, 9, 51 не будет возвращать никаких записей, но 51, 61, 9 будет.

Подход к БД идеален, если он прост (то есть без курсоров или чрезмерно сложной хранимой процедуры), но подход с кодом также может работать, хотя это приведет к значительному объему передачи данных из БД.

Средой является SQL Server 2005 и C # / .NET 3.5.

Ответы [ 3 ]

1 голос
/ 03 декабря 2009

Подход к БД идеален, если он прост (т. Е. Нет курсоров или чрезмерно сложной хранимой процедуры)

Я не верю, что чистый подход к БД («чистый» смысл только с использованием SQL SELECT) является практичным, потому что тип SQL, который я представляю, потребует очень замысловатых самостоятельных соединений, конкатенации полей, функций MAX () и т. Д. Тип SQL может быть забавным академическим ответом на загадку в книге Джо Селко «SQL для умных», но я не думаю, что это подходит для производственного кода.

Я думаю, что реалистичный подход - написать какой-то цикл, который отслеживает состояние. Ваша проблема в общем смысле очень похожа на написание кода для проверки состояния пакетов TCPIP для фильтрации спама или сканирования транзакций кредитных карт на предмет мошеннических шаблонов. Все эти проблемы имеют сходные характеристики: действия, выполняемые с текущей строкой (записью), зависят от того, какие записи вы видели ранее (контекст) ... и этот аспект требует хранения переменных состояния.

Если вы хотите избежать циклической обработки данных для анализа, похоже, что Transact-SQL - это лучший способ повышения производительности. Или используйте размещенный CLR, чтобы использовать преимущества синтаксиса C #, сохраняя при этом обработку в ядре базы данных.

1 голос
/ 04 декабря 2009

На самом деле, вы можете получить пару довольно простых решений, использующих функции ранжирования / управления окнами и / или CTE и рекурсивные CTE .

Создайте процедуру, которая принимает символьный список значений кода, разделенных запятыми, которые вы ищете в последовательности, в которой вы хотите их использовать - используйте любой из дюжины возможных способов разбить этот список на таблицу / набор , который состоит из последовательности и значения кода, в результате получается таблица со структурой, подобной этой:

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;
0 голосов
/ 03 декабря 2009

Это только у меня на макушке и не проверено, поэтому может потребоваться некоторая настройка:

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
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...