SQL Server,查找任意值序列

SQL Server,查找任意值序列,sql,sql-server-2008,Sql,Sql Server 2008,让我们假设我们有一个表维护 Customer LastLogin ActionType 1 12/1/2007 2 1 12/2/2007 2 etc. 我们想要一份列表,列出在给定的一年中的任何时候有一个或多个连续14天的操作类型为2的登录序列的所有客户 当然,我可以很容易地用代码来实现这一点,甚至可以让它在小集合上运行得相当快。在SQL中是否有一种非光标方式来执行此操作?编辑:这将适用于原始问题,即一行两个。连续14个是不同的答案 首先,您需要一个序列,以便使用

让我们假设我们有一个表维护

Customer LastLogin ActionType
1        12/1/2007 2
1        12/2/2007 2
etc.
我们想要一份列表,列出在给定的一年中的任何时候有一个或多个连续14天的操作类型为2的登录序列的所有客户

当然,我可以很容易地用代码来实现这一点,甚至可以让它在小集合上运行得相当快。在SQL中是否有一种非光标方式来执行此操作?

编辑:这将适用于原始问题,即一行两个。连续14个是不同的答案

首先,您需要一个序列,以便使用ROWNUMBER

您可以使用ROWNUMBER=ROWNUMBER+1对自身进行自连接维护

具有相同客户id的任意两个conrequisive行以及具有2个ActionType are的两个行都将为您提供客户列表作为您的答案

试试这个

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

这将选择至少有两个相同类型的连续操作的所有客户

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
        )
以下是针对just action 2的更高效查询:

更新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
此查询计算两个系列:一个按lastlogin返回连续顺序,另一个按操作另外分区:

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
只要两种方案之间的差异相同,序列就不会中断。每次中断都会中断序列

这意味着动作diff的组合定义了不间断组

我们可以按动作、差异分组,在组中找到最大值和最小值,并对它们进行过滤


如果您需要选择14行而不是连续14天,只需根据计数*而不是日期差异进行筛选。

我将假设,对于具有不同操作类型的同一用户,序列是指两行或多行具有连续的日期时间值,中间没有其他行。在这种情况下,这将为您提供您想要得到的:

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之间没有具有不同action类型的行。T3.customer IS NULL检查NULL,因为如果该列为NULL,我假设它是一个非NULL列,那么这意味着左侧外部联接一定没有找到符合条件的行。

使用:

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

为清晰起见编辑。我们正在寻找更长的运行序列,而不仅仅是两个连续的序列。在我发布代码后你会这样做吗?你知道我必须坐在椅子上写代码,但是面朝上坡?我应该更具体一点!你是怎么做的?特别是14个,没有14个不同的子选择。好的,我很快会测试它。我绝对需要顺序,而不仅仅是计数*。我们在寻找行为模式。
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
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