WHERE 子句中的 CASE
Posted
技术标签:
【中文标题】WHERE 子句中的 CASE【英文标题】:CASE inside WHERE Clause 【发布时间】:2016-04-20 15:29:16 【问题描述】:我有一个查询,我需要从表中获取结果,具体取决于用户在 vb.net 页面上指定的参数。
声明一个变量将是理想的解决方案,但由于它是一个内联表值函数,我不能这样做,我读过其他问题,这在多语句表函数中是可能的,但使用这样的不推荐使用函数。
以下查询将是理想的解决方案或我想要实现的目标。
USE [AUDIT]
GO
ALTER FUNCTION [dbo].[GetCreated_LPA_Audits]
(
@fechaInicio nvarchar (max),
@fechaFin nvarchar (max)
@Period int,
@Fecha int
)
RETURNS TABLE
AS
RETURN
(
SELECT T1.Plant,T1.Area,T1.Location,T1.IDAudit,T1.Auditor,T1.AuditDate,T1.DueDate,T1.CreationDate
FROM Header AS T1 inner join Audits AS T2 ON T1.IDChecklist = T2.IDChecklist
inner join AuditGroups AS T3 ON T2.IDGroup = T3.IDGroup
WHERE T3.IDGroup = '2' and CreationDate is not null AND
CASE WHEN @Periodo = '0' THEN CreationDate>= @fechaInicio
WHEN @Periodo = '1' THEN DATEPART(MONTH,CreationDate)>= @Fecha
WHEN @Periodo = '2' THEN
CASE WHEN @Fecha = 13 THEN
DATEPART(MONTH,CreationDate)>= 1
END
WHEN @Periodo = '2' THEN
CASE WHEN @Fecha = 14 THEN
DATEPART(MONTH,CreationDate)>= 7
END
WHEN @Periodo = '3' THEN
CASE WHEN @Fecha = 15 THEN
DATEPART(Year,CreationDate)>= 2015
END
END
AND
CASE WHEN @Periodo = '0' THEN @fechaInicio<=CreationDate
WHEN @Periodo = '1' THEN @Fecha<=DATEPART(MONTH,CreationDate)
WHEN @Periodo = '2' THEN
CASE WHEN @Fecha = 13 THEN
6<=DATEPART(MONTH,CreationDate)
END
WHEN @Periodo = '2' THEN
CASE WHEN @Fecha = 14 THEN
12<=DATEPART(MONTH,CreationDate)
END
WHEN @Periodo = '3' THEN
CASE WHEN @Fecha = 15 THEN
DATEPART(Year,CreationDate)>= 2015
END
END
与 在前面的查询中@fechaInicio、@fechaFin、@Periodo 和@Fecha 是用户提供的参数。
基本上如果@Periodo = 0
我需要在哪里得到结果
CreationDate>= @fechaInicio and CreationDate<=@fechaFin
如果@Periodo = 1
我需要得到结果
DATEPART(MONTH,CreationDate)>= @Fecha and DATEPART(MONTH,CreationDate)<= @Fecha
等等。 希望我说清楚了,提前谢谢!
编辑使用来自@MatBailie 的伪代码,稍作改动并回答他的问题
IF @periodo = '0' THEN
WHERE CreationDate >= @fechaInicio -- Copied from 1st CASE
AND @fechaFin <= CreationDate -- Copied from 2nd CASE
-- gets results from @fechaInicio to @fechaFin
-- i.e. results from 04/05/2016 to 04/16/2016
IF @periodo = '1' THEN
WHERE DATEPART(MONTH,CreationDate) >= @Fecha -- Copied from 1st CASE
AND @Fecha <= DATEPART(MONTH,CreationDate) -- Copied from 2nd CASE
-- In these case both conditions are the same 'cause
-- @Fecha is the number of a month (1 - 12)
-- i.e. @Fecha = 3 will get all the results of March
-- regardless of what it is on @fechaInicio and @fechaFin
IF @periodo = '2' THEN
IF @fetcha = 13 THEN
WHERE DATEPART(MONTH,CreationDate)>= 1 -- Copied from 1st CASE
AND 6<=DATEPART(MONTH,CreationDate) -- Copied from 2nd CASE
IF @fetcha = 14 THEN
WHERE DATEPART(MONTH,CreationDate)>= 7 -- Copied from 1st CASE
AND 12<=DATEPART(MONTH,CreationDate) -- Copied from 2nd CASE
-- You never use @fetchaInicio?
-- You want everything in the first 6 months or last 6 months
-- For every year
-- Regardless of what is in @fetchaInicio?
-- Exactly!!--
IF @periodo = '3' THEN
IF @fetcha = 15 THEN
WHERE DATEPART(Year,CreationDate)>= 2015 -- Copied from 1st CASE
AND 2015 <= DATEPART(Year,CreationDate) -- Copied from 2nd CASE
--
@periodo = '2' AND @fetcha NOT IN (13,14)
的情况如何?
那么@periodo = '3' AND @fetcha NOT IN (15)
的案子呢?
这种情况不存在,它在客户端受到限制。
如果他们选择 @Periodo = '2'
,那么 @Fecha
的值将是 1 - 12,仅此而已。
与@Periodo = '3'
相同,然后@Fecha
的值为 15 或 16,均指 2015 年或 2016 年。
【问题讨论】:
Conditional where clause in Sql Server?的可能重复AND @VPeriodo >= @VFecha AND @VPeriodo <= @VFecha
huh??.. 你能更具体地说明你的问题是什么吗?
@JamieD77 我在一个时间范围内(@fechaInicio
和@fechaFin
)得到结果但是根据用户请求(使用@Periodo
参数),我需要从那个时间范围内得到结果但它可能是按月、学期或年,在@VPeriodo
我将要比较的时间间隔(DATEPART
)存储在@VFecha
它是我需要的时间范围(即如果它的月份是 1-12 )
@abichango - 如果你能在你的问题中给出真实的例子,我今天可以提供更多帮助。不要尝试使用看起来像 SQL 但语法无效的伪代码,这只会引起混乱。给出实际样本数据和实际输入值,然后给出实际输出来演示每种情况下应该发生什么。 ***.com/help/mcve
@abichango - 仔细检查一下,因为这是一个不寻常的用例,当@periodo
是1
或2
,那么你想要数据库中每年的结果吗?你从不想要March-2015
,你只想要March
?即使/当你有 10 年的数据?
【参考方案1】:
你最好重新组织 WHERE 子句,这样过滤的字段就在左边,而不是在任何函数内。
例如...
WHERE
CreationDate >= @VPeriodo
AND CreationDate < CASE
WHEN @Periodo = '0' THEN DATEADD(DAY, 1, @VPeriodo)
WHEN @Periodo = '1' THEN DATEADD(MONTH, 1, @VPeriodo)
WHEN @Periodo = '2' THEN DATEADD(MONTH, 1, @VPeriodo)
WHEN @Periodo = '3' THEN DATEADD(YEAR, 1, @VPeriodo)
END
在这个例子中,右手边都是标量常数。这意味着您可以对 CreationDate 字段进行范围扫描。
另外,@VPeriodo 应该是 DATE
或 DATETIME
而不是 VARCHAR(MAX)
。
编辑:包括使用 VARCHAR 跳过的箍
使用 VARCHAR 时,所有日期都需要采用 YYYYMMDD
格式。这样一来,搅拌的自然顺序与日期的自然顺序相同...
- '20161101'
> '20161002'
当使用其他格式时,例如YYYYDDMM
,它会失败...
- '20160111'
'20160210' 问题,在这种格式下,10 月 2 日在 11 月 1 日之后
WHERE
CreationDate >= @VPeriodo
AND CreationDate < CONVERT(
NVARCHAR(8),
CASE
WHEN @Periodo = '0' THEN DATEADD(DAY, 1, CAST(@VPeriodo AS DATE))
WHEN @Periodo = '1' THEN DATEADD(MONTH, 1, CAST(@VPeriodo AS DATE))
WHEN @Periodo = '2' THEN DATEADD(MONTH, 1, CAST(@VPeriodo AS DATE))
WHEN @Periodo = '3' THEN DATEADD(YEAR, 1, CAST(@VPeriodo AS DATE))
END,
112 -- Format code for ISO dates, YYYYMMDD
)
编辑:在 OP 提出 cmets 并更改问题后向 OP 提出的问题
我在这里所做的只是重新安排你的代码,为你写的东西制作伪代码......
IF @periodo = '0' THEN
WHERE CreationDate >= @fetchaInicio -- Copied from 1st CASE
AND @fetchaInicio <= CreationDate -- Copied from 2nd CASE
-- These two conditions are direct from your code
-- But they're the same as each other
-- What do you REALLY want to happen when @Periodo = '0'?
IF @periodo = '1' THEN
WHERE DATEPART(MONTH,CreationDate) >= @Fecha -- Copied from 1st CASE
AND @Fecha <= DATEPART(MONTH,CreationDate) -- Copied from 2nd CASE
-- These two conditions are direct from your code
-- But they're the same as each other
-- What do you REALLY want to happen when @Periodo = '1'?
IF @periodo = '2' THEN
IF @fetcha = 13 THEN
WHERE DATEPART(MONTH,CreationDate)>= 1 -- Copied from 1st CASE
AND 6<=DATEPART(MONTH,CreationDate) -- Copied from 2nd CASE
IF @fetcha = 14 THEN
WHERE DATEPART(MONTH,CreationDate)>= 7 -- Copied from 1st CASE
AND 12<=DATEPART(MONTH,CreationDate) -- Copied from 2nd CASE
-- You never use @fetchaInicio?
-- You want everything in the first 6 months or last 6 months
-- For every year
-- Regardless of what is in @fetchaInicio?
IF @periodo = '3' THEN
IF @fetcha = 15 THEN
WHERE DATEPART(Year,CreationDate)>= 2015 -- Copied from 1st CASE
AND DATEPART(Year,CreationDate)>= 2015 -- Copied from 2nd CASE
-- Both conditions are the same again, why?
-- You want everything from 2015 onwards, forever?
-- You never use @fetchaInicio?
-- It's always 2015?
那么@periodo = '2' AND @fetcha NOT IN (13,14)
的案例呢?
那么@periodo = '3' AND @fetcha NOT IN (15)
的案子呢?
请您使用我上面的伪代码并给出一些真实示例,说明您在每种情况下实际想要做什么?
【讨论】:
事情是@VPeriodo
不是一个实际参数,我用它来“模拟”一个变量以将其均衡到某个值(显然不是那样工作的)。它也是VARCHAR
,因为CreationDate
它的套索VARCHAR
(这就是有人设计桌子的方式)
@abichango - 我不明白,你的函数定义有一个名为 @vperiodo
的变量。但不管它是一个变量,还是另一个表中的字段(在连接谓词中使用),这个结构仍然成立。您是否有无法应用此结构的实际示例?
我想我不够清楚(抱歉),我确实有 @VPeriodo
变量,但它不在任何其他表中,它本身也没有值,我试图将它用作一个局部变量,我可以使其值等于CreationDate
或DATEPART(month,CreationDate
),而不是将其(如SQL 那样)与这些变量进行比较。原始查询没有@VPeriodo,也没有@VFecha 和@VFecha 2,就像我说我试图将它们用作变量来保存数据,而不是与它们比较数据,这有意义吗?这也是正在使用的实际查询。
@abichango - 不,抱歉,这对我来说没有意义。在您的问题中,您可以举例说明您实际要做什么?
@abichango - 为什么你认为内联表值函数不能有参数?和/或为什么您认为您不能使用用户从 VBA 表单中选择的参数调用此 iTVF?【参考方案2】:
首先,您是对的,内联 TFV 确实更快。如果不是过于复杂。 我最好在 SQL Server 端为每个 @Periodo 参数值设置多个 iTFV,并在客户端的代码中选择正确的一个。
或者,您可以在单个 iTVF 中执行此操作
WHERE
@Periodo = '0' AND CreationDate>= @fechaInicio and CreationDate<=@fechaFin
OR @Periodo = '1' and DATEPART(MONTH,CreationDate)>= @Fecha and DATEPART(MONTH,CreationDate)<= @Fecha
...
但是众所周知,MS SQL 偶尔会为 OR 运算符构建糟糕的计划,这可能会使您坚持使用 iTVF 的努力毫无用处。
【讨论】:
你的最后一句话是问题,它是一个不断增长的表,有多少 OR 会是一个问题?或者更多的是关于OR
声明而不是有多少OR
??【参考方案3】:
当您想在 where 子句中使用参数作为条件时,您可以遵循以下格式:
WHERE
((@Periodo = '0' AND CreationDate
OR (@Periodo = '1' AND @VPeriodo = CAST(DATEPART(MONTH,CreationDate) as Nvarchar(10))
OR (@Periodo = '2' AND @VPeriodo = CAST(DATEPART(MONTH,CreationDate) as Nvarchar(10))
OR (@Periodo = '3' AND @VPeriodo = CAST(DATEPART(Year,CreationDate) as Nvarchar(10)))
确保首先进行参数评估,因为这会很快,并且它为 false 将阻止查询继续处理条件中处理更密集的部分。
【讨论】:
这仍然是出了名的慢。多个 OR 条件本身可能会导致优化器失效。并且在函数中嵌入搜索字段通常是无法恢复的。两者都产生全表扫描而不是索引范围搜索。 @MatBailie 有人将我的问题标记为可能与此 question 重复!答案和这个一样,我认为它解决了我的问题,但是由于速度问题,还有其他方法可以实现吗?? @abichango - 这是一个详细链接,说明为什么这种类型的查询表现不佳。 sommarskog.se/dyn-search-2008.html至于具体的替代方案,我不确定您对我的回答的评论到底是什么意思。以上是关于WHERE 子句中的 CASE的主要内容,如果未能解决你的问题,请参考以下文章
用于返回结果集的 where 子句中的 case 语句包含空值
where 子句中的 case 语句 - SQL Server