我对非直观的主题标题道歉。
我有一个表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行,所以显然结果会有所不同。我会在生产中尝试每一个,看看哪个是最快的,然后相应地选择答案。
谢谢大家的帮助!
答案 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组合JobId
和OriginalJobId
,通过将它们分组然后排除状态为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))