SQL Server, найти произвольную последовательность значений - PullRequest
2 голосов
/ 30 ноября 2009

Предположим, у нас есть таблица обслуживания

Customer LastLogin ActionType
1        12/1/2007 2
1        12/2/2007 2
etc.

Нам нужен список всех клиентов, которые в любой момент в течение данного года имели одну или несколько непрерывных последовательностей длиной 14 дней для входа с типом действия 2.

Конечно, я могу легко сделать это с помощью кода, и даже сделать это довольно быстро для небольших наборов. Есть ли не-курсор способ сделать это в SQL?

Ответы [ 5 ]

5 голосов
/ 30 ноября 2009

Это выберет всех клиентов по крайней мере с двумя последовательными действиями одного и того же типа.

WITH    rows AS 
        (
        SELECT  customer, action,
                ROW_NUMBER() OVER (PARTITION BY customer ORDER BY lastlogin) AS rn
        FROM    mytable
        )
SELECT  DISTINCT customer
FROM    rows rp
WHERE   EXISTS
        (
        SELECT  NULL
        FROM    rows rl
        WHERE   rl.customer = rp.customer
                AND rl.rn = rp.rn + 1
                AND rl.action = rp.action
        )

Вот более эффективный запрос для простого действия 2:

WITH    rows AS 
        (
        SELECT  customer, ROW_NUMBER() OVER (PARTITION BY customer ORDER BY lastlogin) AS rn
        FROM    mytable
        WHERE   action = 2
        )
SELECT  DISTINCT customer
FROM    rows rp
WHERE   EXISTS
        (
        SELECT  NULL
        FROM    rows rl
        WHERE   rl.customer = rp.customer
                AND rl.rn = rp.rn + 1
        )

Обновление 2:

Чтобы выбрать непрерывные диапазоны:

WITH    rows AS 
        (
        SELECT  customer, action, lastlogin
                ROW_NUMBER() OVER (PARTITION BY customer ORDER BY lastlogin) AS rn
                ROW_NUMBER() OVER (PARTITION BY customer, action ORDER BY lastlogin) AS series
        FROM    mytable
        )
SELECT  DISTINCT customer
FROM    (
        SELECT  customer
        FROM    rows rp
        WHERE   action
        GROUP BY
                customer, actioncode, series - rn
        HAVING
                DETEDIFF(day, MIN(lastlogin), MAX(lastlogin)) >= 14
        ) q

Этот запрос вычисляет две серии: одна возвращает смежные ORDER BY lastlogin, вторая секционирует на action дополнительно:

action  logindate rn  series diff = rn - series
1       Jan 01    1   1      0
1       Jan 02    2   2      0
2       Jan 03    3   1      2
2       Jan 04    4   2      2
1       Jan 05    5   3      2
1       Jan 06    6   4      2

Пока разница между двумя схемами одинакова, серии не прерываются. Каждое прерывание прерывает серию.

Это означает, что комбинация (action, diff) определяет непрерывные группы.

Мы можем группировать по action, diff, находить MAX и MIN в группах и фильтровать их.

Если вам нужно выбрать 14 строк вместо 14 последовательных дней, просто выберите COUNT(*) вместо DATEDIFF.

1 голос
/ 30 ноября 2009

Использование:

WITH dates AS (
  SELECT CAST('2007-01-01' AS DATETIME) 'date'
  UNION ALL
   SELECT DATEADD(dd, 1, t.date) 
     FROM dates t
    WHERE DATEADD(dd, 1, t.date) <= GETDATE())
   SELECT m.customer, 
          m.actiontype
     FROM dates d
LEFT JOIN MAINTENANCE m ON m.last_login = d.date
    WHERE m.last_login IS NULL
1 голос
/ 30 ноября 2009

РЕДАКТИРОВАТЬ: Это сработало бы для исходного вопроса о двух подряд. 14 в ряд другой ответ

Сначала вам нужна последовательность, чтобы использовать ROWNUMBER

Вы можете выполнить самообслуживание, используя ROWNUMBER = ROWNUMBER + 1

Любые две последовательные строки с одинаковым идентификатором клиента, и обе строки с «2» ActionType будут давать вам список CUSTOMER в качестве ответа.

Попробуйте это

WITH Maintenance AS
(
SELECT 1 as Customer, CONVERT (DateTime, '1/1/2008') DateTimeStamp, 1 ActionType
UNION
SELECT 1, '3/1/2009', 1
UNION
SELECT 1, '3/1/2006', 2
UNION
SELECT 2, '3/1/2009', 1
UNION
SELECT 2, '3/1/2006', 2
)
,RowNumberMaintenance AS
(SELECT  ROW_NUMBER () OVER (ORDER BY Customer, DateTimeStamp)  AS RowNumber, *
FROM Maintenance)
SELECT m1.Customer
From RowNumberMaintenance M1
    INNER JOIN RowNumberMaintenance M2
        ON M1.Customer = M2.Customer
        AND M1.RowNumber = M2.RowNumber + 1
WHERE 1=1
        AND M1.ActionType <> 2
        AND M2.ActionType <> 2
0 голосов
/ 30 ноября 2009

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

SELECT DISTINCT
     T1.customer
FROM
     Maintenance T1
INNER JOIN Maintenance T2 ON
     T2.customer = T1.customer AND
     T2.action_type = 2 AND
     T2.last_login > T1.last_login
LEFT OUTER JOIN Maintenance T3 ON
     T3.customer = T1.customer AND
     T3.last_login > T1.last_login AND
     T3.last_login < T2.last_login AND
     T3.action_type <> 2
WHERE
     T1.actiontype = 2 AND
     T3.customer IS NULL

SQL делает то, что я сказал выше - находит строку (T1) с другой строкой после нее (T2) и с action_type = 2, где между ними нет строки (T3) с другим типом действия. T3.customer IS NULL проверяет NULL, потому что если столбец равен NULL (я предполагаю, что это столбец NOT NULL), то это означает, что LEFT OUTER JOIN не должен найти строку, соответствующую критериям.

0 голосов
/ 30 ноября 2009
select customerID, count(customerID)
from maintenance
where actiontype = 2
group by customerID
having count(customerID) >= 1
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...