WHERE 子句中的 CASE

CASE inside WHERE Clause

我有一个查询,我需要从 table 中获取结果,具体取决于用户在 vb.net 页面上指定的参数。

声明一个变量将是理想的解决方案,但由于它是一个内联 table 值函数,我不能这样做,我读过其他问题,这在 multstatment table-函数,但不推荐使用此类函数。

以下查询将是理想的解决方案或我想要实现的。

 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 年。

只要您想在 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)))

确保首先进行参数评估,因为这会很快,如果为假,查询将无法继续处理条件中处理更密集的部分。

你最好重新组织 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 应该是 DATEDATETIME 而不是 VARCHAR(MAX)



编辑: 包括使用 VARCHAR 时要跳过的箍


使用 VARCHAR 时,所有日期都需要采用 YYYYMMDD 格式。这样一来,stirngs 的自然顺序与日期的自然顺序相同...
- '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 发表评论并更改问题后向 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) 的情况呢?


请你使用我上面的伪代码,并给出一些真实的例子,说明你在每种情况下实际想要做什么?

首先,您是对的,内联 TFV 确实更快。如果不是过于复杂。 我最好在 SQL 服务器端为每个 @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 的努力付诸东流。