SQL Server 中带条件的 Where 子句

Posted

技术标签:

【中文标题】SQL Server 中带条件的 Where 子句【英文标题】:Where Clause with Conditions in SQL Server 【发布时间】:2015-10-20 19:08:11 【问题描述】:

我有一个用于网站搜索页面的 SQL 存储过程,用户可以在其中搜索两个日期字段之间的记录:initiatedDateStart 和 InitialDateEnd。 SQL查询的逻辑应该如下:

如果 startDate 有值,但 endDate 为空,或者等于 startDate,返回在 startDate 上创建的记录。 如果 startDate 有值,并且 endDate 值大于 startDate,返回两者之间创建的记录 日期。

由于我不是 SQL 专家,我的存储过程不是很优雅或聪明,但它确实返回了我期望它返回的记录,日期字段除外。我已经对此进行了研究,但语法和逻辑让我很困惑。这是我的存储过程的 sn-p。如果在 initStartDate texbox 中输入了一个值,但 initEndDate 为空,则返回在开始日期之后创建的所有记录。我很感激这方面的任何帮助。

DECLARE @initStartDate datetime = NULL
DECLARE @initEndDate datetime = NULL

SELECT DISTINCT 
        d.[DCRId],      
        d.[Title], 
        d.[FiscalYear] 
        FROM [dbo].[tblDCR] d 
        LEFT OUTER JOIN [dbo].[tblWorkflow] orig ON (d.[DCRId] = orig.[DCRId] AND orig.StepName = 'Draft')
        WHERE 1 = 1
        AND (orig.BadgeDate >= @initStartDate OR @initStartDate IS NULL)
        AND (orig.BadgeDate <= @initEndDate OR @initEndDate IS NULL)

【问题讨论】:

【参考方案1】:

我能想到的最好的处理方法是使用动态 sql

DECLARE @initStartDate datetime = NULL;
DECLARE @initEndDate   datetime = NULL;
Declare @Sql NVARCHAR(MAX);


SET @Sql  = N'SELECT DISTINCT 
                    d.[DCRId],      
                    d.[Title], 
                    d.[FiscalYear] 
            FROM [dbo].[tblDCR] d 
            LEFT OUTER JOIN [dbo].[tblWorkflow] orig 
            ON (d.[DCRId] = orig.[DCRId] AND orig.StepName = ''Draft'')
            WHERE 1 = 1 '
            + CASE WHEN @initStartDate IS NOT NULL THEN 
                N' AND orig.BadgeDate >= @initStartDate ' ELSE N'' END
            + CASE WHEN @initEndDate IS NOT NULL THEN 
                N' AND orig.BadgeDate <= @initEndDate  ' ELSE N'' END

Exec sp_executesql @Sql
                  ,N'@initStartDate datetime, @initEndDate datetime'
                  ,@initStartDate
                  ,@initEndDate

【讨论】:

M.Ali,谢谢。我确实考虑过动态SQL,但我从来没有使用过它,存储过程比这点复杂一点,所以我没有尝试实现动态SQL。如果没有动态 SQL,我将如何包含 CASE?【参考方案2】:

我认为解决您的问题的方法是将当前日期分配给 end 参数,当它为空时。通过这样做,用户可以不输入参数并获取当前日期值,仅输入开始日期并获取开始日期的数据,或者输入结束日期并获取当前日期和结束日期之间的数据。最后,如果由于某种原因用户输入的开始日期高于结束日期,您可以告诉该过程仅返回今天的数据。

CREATE PROCEDURE test_proc
@initStartDate date = null,
@initEndDate date = null
AS
IF @initStartDate IS NULL AND @initEndDate IS NULL
BEGIN 
SET @initStartDate = (SELECT GETDATE())
SET @initEndDate = @initStartDate
END
ELSE IF @initStartDate IS NULL AND @initEndDate IS NOT NULL
BEGIN 
SET @initStartDate = @initEndDate 
END
ELSE IF @initEndDate IS NULL AND @initStartDate IS NOT NULL
BEGIN 
SET @initEndDate = @initStartDate
END

IF @initStartDate > @initEndDate
BEGIN
SET @initStartDate = (SELECT GETDATE())
SET @initEndDate = @initStartDate
END  


SELECT DISTINCT 
    d.[DCRId],      
    d.[Title], 
    d.[FiscalYear] 
    FROM [dbo].[tblDCR] d 
    LEFT OUTER JOIN [dbo].[tblWorkflow] orig ON (d.[DCRId] = orig.[DCRId] AND orig.StepName = 'Draft')
    WHERE orig.BadgeDate BETWEEN @initStartDate AND @initEndDate
GO

我修改的 SQL

DECLARE @initStartDate date = '10/21/15'
DECLARE @initEndDate date = NULL

IF @initStartDate IS NULL AND @initEndDate IS NOT NULL
BEGIN
SET @initStartDate = @initEndDate
END
ELSE IF @initEndDate IS NULL AND @initStartDate IS NOT NULL
BEGIN
SET @initEndDate = @initStartDate
END


SELECT DISTINCT 
        d.[DCRId],      
        d.[Title], 
        d.[FiscalYear] 
        FROM [dbo].[tblDCR] d 
        LEFT OUTER JOIN [dbo].[tblWorkflow] orig ON (d.[DCRId] = orig.[DCRId] AND orig.StepName = 'Draft')
        WHERE 1 = 1
        AND (orig.BadgeDate BETWEEN @initStartDate AND @initEndDate) OR (@initStartDate IS NULL OR @initEndDate IS NULL)

【讨论】:

dfundako,谢谢;我明白你的目标是什么。问题是如果用户不想按日期搜索,日期字段很可能为空。因此,如果任一日期字段中有值,则需要进行测试,然后返回在该特定日期创建的记录。如果两个日期字段都有值,则结果将在日期之间。 如果只为 initStart 或 initEnd 提供了一个日期,我已编辑查询以仅查看一个日期的记录。 dfundako,我在您的帖子中添加了修改后的 SQL,只要开始日期和结束日期不同,它就可以工作。由于某种原因,如果日期相同,BETWEEN 函数不会返回记录。日期也可能都为空;在这种情况下,用户不想按日期搜索,因此该字段不应返回任何内容。 我相信您的 BETWEEN 不起作用的原因是因为您将参数设置为 DATETIME 而不是 DATE。当您运行查找 2015-10-21 00:00:00 和 2015-10-21 00:00:00 之间的记录的 BETWEEN 语句时,它不会返回任何内容。但是,如果您在 2015-10-21 AND 2015-10-21 之间运行它,则无论附加时间戳如何,您都将在 2015-10-21 获得所有结果。 感谢您在这方面的坚持,dfundako,在您的帮助下,我终于能够让整个存储过程正常工作。【参考方案3】:

但是如果@initStartDate 没有值怎么办? 假设如果没有 startDate 条件,您希望它一直返回,那么,将日期条件作为 Nullable 参数传递,就像您一样: 然后,(假设数据库字段只有日期,没有时间),使用以下 where 子句:

 where orig.BadgeDate Between
     Coalesce(@initStartDate, orig.BadgeDate) and
     Coalesce(@initEndDate, @initStartDate, orig.BadgeDate)

【讨论】:

谢谢你,查尔斯。由于此存储过程返回搜索网页的结果,用户可能会或可能不会在搜索字段中输入任何值,因此将为任何未选择的选项传递空值。日期字段很可能为空。但是数据库中的 orig.BadgeDate 值不会为空。所以即使用户不想按日期搜索,Coalesce 也会始终返回一个值,是这样吗? Coalesce 将返回第一个不为空的参数,所以是的,如果最后一个参数是 orig.BadgeDate,那么合并将始终返回一个非空值,但这实际上是,与在那一端没有边界相同,因为orig.BadgeDate 将始终等于orig.BadgeDate

以上是关于SQL Server 中带条件的 Where 子句的主要内容,如果未能解决你的问题,请参考以下文章

SQL Server If 条件在 where 子句?

Hive 中带有 Join 或 Where 子句的条件

SQL HELP - 基于 BIT 变量的条件 where 子句 - SQL Server

为啥以及何时在 WHERE 子句中带有条件的 LEFT JOIN 不等于在 ON 中的相同 LEFT JOIN? [复制]

ms sql server中where子句中的if else条件

我需要从 SQL Server 查询中获取前 5 条记录,但要计算满足 where 子句条件的所有记录