复杂SQL查询(至少对我而言)
我正在尝试开发一个sql查询,它将返回序列号列表。该表设置为每当序列号到达一个步骤时,都会输入日期和时间。完成该步骤后,将输入另一个日期和时间。我想开发一个查询,该查询将为我提供已进入该步骤但未退出该步骤的序列号列表。他们可能会多次输入,所以我只查找在输入之后没有出口的序列号 为便于使用,请参阅表1复杂SQL查询(至少对我而言),sql,Sql,我正在尝试开发一个sql查询,它将返回序列号列表。该表设置为每当序列号到达一个步骤时,都会输入日期和时间。完成该步骤后,将输入另一个日期和时间。我想开发一个查询,该查询将为我提供已进入该步骤但未退出该步骤的序列号列表。他们可能会多次输入,所以我只查找在输入之后没有出口的序列号 为便于使用,请参阅表1 1. Serial | Step | Date 2. 1 | enter | 10/1 3. 1 | exit | 10/2 4. 1 | enter |
1. Serial | Step | Date
2. 1 | enter | 10/1
3. 1 | exit | 10/2
4. 1 | enter | 10/4
5. 2 | enter | 10/4
6. 3 | enter | 10/5
7. 3 | exit | 10/6
对于上表,应检索序列号1和2,但不应检索序列号3
这可以在带有子查询的单查询中完成吗?这将为您提供所有没有结束“退出”的“输入”记录。如果只需要序列号列表,则还应按序列号分组并仅选择该列
SELECT DISTINCT Serial
FROM Table t
WHERE (SELECT COUNT(*) FROM Table t2 WHERE t.Serial = t2.Serial AND Step = 'exit') <
(SELECT COUNT(*) FROM Table t2 WHERE t.Serial = t2.Serial AND Step = 'enter')
SELECT t1.*
FROM Table1 t1
LEFT JOIN Table1 t2 ON t2.Serial=t1.Serial
AND t2.Step='Exit' AND t2.[Date] >= t1.[Date]
WHERE t1.Step='Enter' AND t2.Serial IS NULL
如果您确定您已经为您不想要的输入和退出值找到了匹配的输入和退出值,那么您可以查找输入计数不等于退出计数的所有序列值
select * from Table1
group by Step
having count(*) % 2 = 1
这是指不能有两个“enter”,但每个enter后面都有一个“exit”,如所提供的示例所示如果您使用的是MS SQL 2005或2008,则可以使用CTE来获取您要查找的结果
SELECT * FROM Table1 T1
WHERE NOT EXISTS (
SELECT * FROM Table1 T2
WHERE T2.Serial = T1.Serial
AND T2.Step = 'exit'
AND T2.Date > T1.Date
)
WITH ExitCTE
AS
(SELECT Serial, StepDate
FROM #Table1
WHERE Step = 'exit')
SELECT A.*
FROM #Table1 A LEFT JOIN ExitCTE B ON A.Serial = B.Serial AND B.StepDate > A.StepDate
WHERE A.Step = 'enter'
AND B.Serial IS NULL
如果您不使用这些,我会尝试使用子查询来代替
SELECT A.*
FROM #Table1 A LEFT JOIN (SELECT Serial, StepDate
FROM #Table1
WHERE Step = 'exit') B
ON A.Serial = B.Serial AND B.StepDate > A.StepDate
WHERE A.Step = 'enter'
AND B.Serial IS NULL
我个人认为,最好通过改变数据存储方式来实现这一点。当前方法不能是高效的或有效的。是的,你可以到处乱搞,想办法把数据拿出来。但是,如果您输入了多个步骤,但同一序列号没有退出,会发生什么情况?是的,这不应该发生,但迟早会发生,除非你写了代码来阻止它,否则代码编写起来会很复杂。如果有一个在同一条记录中同时存储enter和exit的表,那就更干净了。然后,查询变得很简单,查找已输入但未退出的内容的速度也会更快 在Oracle中:
SELECT *
FROM (
SELECT serial,
CASE
WHEN so < 0 THEN "Stack overflow"
WHEN depth > 0 THEN "In"
ELSE "Out"
END AS stack
FROM (
SELECT serial, MIN(SUM(DECODE(step, "enter", 1, "exit", -1) OVER (PARTITION BY serial ORDER BY date)) AS so, SUM(DECODE(step, "enter", 1, "exit", -1)) AS depth
FROM Table 1
GROUP BY serial
)
)
WHERE stack = "Out"
这将选择您想要的,并过滤掉没有进入的退出。有人建议重新排列您的数据,但我没有看到任何示例,因此我将尝试一下。这是您描述的同一个表的部分非规范化变体。这个例子只考虑了enter和exit,但是它可以很容易地扩展,但是它最大的缺点是在填充表之后添加额外的步骤,比如说,enter/process/exit是很昂贵的-您必须修改表才能这样做
serial enter_date exit_date
------ ---------- ---------
1 10/1 10/2
1 10/4 NULL
2 10/4 NULL
3 10/5 10/6
然后,您的查询变得非常简单:
SELECT serial,enter_date FROM table1 WHERE exit_date IS NULL;
serial enter_date
------ ----------
1 10/4
2 10/4
这里有一个简单的查询,可以用于您的场景
SELECT Serial FROM Table1 t1
WHERE Step='enter'
AND (SELECT Max(Date) FROM Table1 t2 WHERE t2.Serial = t1.Serial) = t1.Date
我已经测试了这一行,这将为您提供序列号为1和2的行。我在MySQL中测试了这一行
SELECT Serial,
COUNT(NULLIF(Step,'enter')) AS exits,
COUNT(NULLIF(Step,'exit')) AS enters
FROM Table1
WHERE Step IN ('enter','exit')
GROUP BY Serial
HAVING enters <> exits
我不确定日期在这里的重要性,但可以很容易地修改上述内容,以纳入日内或跨日的要求。他特别指出可能有多个输入,但从未说过没有退出的多个输入。实际上,你不能通过分组选择*。最初的海报只是将其作为从表1中选择[Step]的简写,即[Step]的计数为*%2=1。同样值得注意的是,模运算符%不是ANSI,但广泛使用。让count*%2=1的诀窍是什么?有人能解释吗?我的第一个想法是,最好有一条记录,输入日期和退出日期允许空字段。如果每个序列号都可以多次输入和退出,则可以使用一个表将多个输入和退出记录映射到一个序列号。