从具有重试详细信息(id和重试计数)的表中检索失败的作业

时间:2014-11-21 09:52:39

标签: sql sql-server

我对非直观的主题标题道歉。

我有一个表Jobs,其中每一行代表一个由计算机程序执行的维护任务。它有这样的设计:

CREATE TABLE Jobs (
    JobId bigint PRIMARY KEY,
    ...
    Status int NOT NULL,
    OriginalJobId bigint NULL
)

创建/启动作业时,其行将添加到表中,其状态为0。作业完成后,其状态将更新为1,当作业失败时,其状态将更新为2。当作业失败时,作业管理器将通过复制失败作业的详细信息并将Status重置为0并使用原始作业(失败)将新行插入作业表来重试作业)OriginalJobId中的JobId用于跟踪目的。如果此重新尝试失败,则应再次尝试最多3次,每次后续重试都会将原始JobId保留在OriginalJobId列中。

我的问题是尝试制定查询以获取失败的当前作业集并获得重试次数。

这是表格中的示例数据:

JobId | Status | OriginalJobId
    1,       1,           NULL    -- Successful initial job
    2,       0,           NULL    -- Pending initial job
    3,       2,           NULL    -- Failed initial job
    4,       1,              3    -- Successful retry of Job 3
    5,       2,           NULL    -- Failed initial job
    6,       2,              5    -- Failed retry 1 of Job 5
    7,       2,              5    -- Failed retry 2 of Job 5 -- should be tried again for 1 more time
    8,       2,           NULL    -- Failed initial job
    9,       2,              8    -- Failed retry 1 of Job 8
   10,       2,              8    -- Failed retry 2 of Job 8
   11,       2,              8    -- Failed retry 3 of Job 8 -- don't try again
   12,       2,           NULL    -- Failed initial job

我的查询需要返回:

 JobId | RetryCount
     5,           2
    12,           0

注意Job 3是如何被包含的,因为它的上次重试成功(状态1)。类似地,排除了作业8,因为重试次数超过了限制3.包括作业5,因为它仍然失败并且只有2次重试,并且包含了作业12并且没有#39; t还没有重试。

我认为解决方案会是这样的:

SELECT
    J1.JobId
FROM
    Jobs AS J1
    LEFT OUTER JOIN Jobs AS J2 ON J1.JobId = J2.OriginalJobId
WHERE
    J1.Status = 2

...但我无法想到如何获取RetryCount数据。

这是我为此问题创建的SQLFiddle,其中一个解决方案如下:

http://sqlfiddle.com/#!6/8765f

更新

这是一个更新的SQLFiddle,它比较了目前为止提供的5个解决方案(我添加了一个额外的HAVING子句来删除重试次数超过3次的作业)

http://sqlfiddle.com/#!6/8765f/23

在性能方面,我认为GarethD的答案是最好的,因为它有最简单的执行计划,并且倾向于以最快的时间在SqlFiddle中完成。

我的生产表有大约14,000,000行,所以显然结果会有所不同。我会在生产中尝试每一个,看看哪个是最快的,然后相应地选择答案。

谢谢大家的帮助!

5 个答案:

答案 0 :(得分:5)

以下内容返回所需的结果:

SELECT  J1.JobId,
        Retries = COUNT(J2.JobId)
FROM    Jobs AS J1
        INNER JOIN Jobs AS J2 
            ON J1.JobId = J2.OriginalJobId
WHERE   J1.Status = 2
GROUP BY J1.JobId
HAVING COUNT(CASE WHEN J2.Status = 1 THEN 1 END) = 0;

我已将其更改为INNER联接,以便仅包含已重试的作业,但这可以切换回LEFT联接以包含尚未重试的失败作业然而。我还添加了一个HAVING子句来排除重试后没有失败的任何作业。


修改

如上所述,使用INNER JOIN意味着您只返回已重试的作业,以获取您需要使用LEFT JOIN的所有失败作业,这将意味着重试将作为失败返回作业,所以我添加了一个额外的谓词J1.OriginalJobId IS NULL,以确保只返回原始作业:

SELECT  J1.JobId,
        Retries = COUNT(J2.JobId)
FROM    Jobs AS J1
        LEFT JOIN Jobs AS J2 
            ON J1.JobId = J2.OriginalJobId
WHERE   J1.Status = 2
AND     J1.OriginalJobId IS NULL
GROUP BY J1.JobId
HAVING COUNT(CASE WHEN J2.Status = 1 THEN 1 END) = 0;

<强> Example on SQL Fiddle

答案 1 :(得分:3)

这应该可以胜任。它通过COALESCE组合JobIdOriginalJobId,通过将它们分组然后排除状态为1的任何作业来获取重试计数。

SELECT COALESCE(j.OriginalJobId, j.JobId) JobId, 
       COUNT(*)-1 RetryCount
FROM Jobs j
WHERE j.[Status] = 2
AND NOT EXISTS  (SELECT 1
                FROM Jobs 
                WHERE COALESCE(Jobs.OriginalJobId, Jobs.JobId) = COALESCE(j.OriginalJobId, j.JobId)
                AND Jobs.[Status] = 1)
GROUP BY COALESCE(j.OriginalJobId, j.JobId), j.[Status]

答案 2 :(得分:2)

这是我写的稍微冗长的CTE方法,它返回结果,包括原始作业status = 2的作业,并且没有重试:

;WITH  cte AS (
       -- root level jobs that failed and did not have status of 1 after
       SELECT   j.JobId , j.OriginalJobId , 0 AS RetryCount
               FROM     dbo.Jobs j
               WHERE    j.OriginalJobId IS NULL AND j.Status = 2
                        AND NOT EXISTS ( SELECT OriginalJobId
                                         FROM   dbo.Jobs
                                         WHERE  Status = 1
                                                AND OriginalJobId = j.JobId )
       -- unioned with retries
       UNION ALL
       SELECT   j.JobId , j.OriginalJobId , 1 AS RetryCount
       FROM     dbo.Jobs j
       INNER JOIN cte ON cte.JobId = j.OriginalJobId
)
-- Group Jobs & Count retries 
SELECT  JobId , SUM(RetryCount) Retries
FROM    ( SELECT    JobId , cte.RetryCount
          FROM      cte
          WHERE     OriginalJobId IS NULL
          UNION ALL
          SELECT    OriginalJobId AS JobId , cte.RetryCount
          FROM      cte
          WHERE     OriginalJobId IS NOT NULL
        ) t
GROUP BY JobId

答案 3 :(得分:2)

这个“看马”怎么样!没有加入!溶液:

select coalesce(OriginalJobId, JobId) JobId, count(OriginalJobId) RetryCount
from Jobs
group by coalesce(OriginalJobId, JobId)
having count(case status when 1 then 1 end) = 0
and max(status) > 0
order by JobId; 

返回所需的结果:

 JobId | RetryCount
    6,           3
   15,           0

答案 4 :(得分:0)

为什么我们需要执行连接,因为我们唯一需要的是计算OriginalJoibId出现没有'1'?

SELECT OriginalJobId, COUNT(*) As RetryCount
FROM Jobs
WHERE OriginalJobId IS NOT NULL
GROUP BY OriginalJobId
HAVING COUNT(CASE WHEN Status = 1 THEN 1 END) = 0

我认为我们可以简单地忽略OriginalJobId中所有具有NULL的条目,并且只关注重试记录。

编辑:

当我写回答时,我没有注意到第二条记录添加到了所需的输出中。我能做的最好的修补就是以下相当丑陋的构造:=)

SELECT OriginalJobId, COUNT(*) As RetryCount
FROM Jobs
WHERE OriginalJobId IS NOT NULL
GROUP BY OriginalJobId
HAVING COUNT(CASE WHEN Status = 1 THEN 1 END) = 0

UNION ALL

SELECT j.JobId, 0
FROM Jobs j
WHERE (Status = 2) AND (OriginalJobId IS NULL) AND 
      (NOT EXISTS (SELECT 1 FROM Jobs WHERE OriginalJobId = j.JobId))