SQL WHERE 子句中的逻辑如何构建?

How to construct the logic in this SQL WHERE clause?

我正在尝试编写一个存储过程来搜索 table 的文本字段,如下所示:

TABLE:[用户]

[Id] BIGINT PRIMARY KEY, IDENTITY (1, 1)
[Name] NVARCHAR(100) NOT NULL
[Email] NVARCHAR(100) NOT NULL, UNIQUE

生产数据库有很多列和一个非常大的数据集。这个SP的重点是尽可能加快搜索速度。

我尝试了什么:

  1. EntityFrameworkCore LINQ 查询。
  2. 使用 ADO .NET 即时生成 SQL。
  3. 下面的存储过程。

SP 取得了迄今为止最好的结果,但结果并不准确。

测试脚本

USE [TestDatabase]

--DELETE FROM [User] -- Commented for your safety.
DBCC CHECKIDENT ('[User]', RESEED, 0)
INSERT INTO [User] ([Name], [Email]) VALUES ('Name 01', 'Email01@Email.com')
INSERT INTO [User] ([Name], [Email]) VALUES ('Name 02', 'Email02@Email.com')
INSERT INTO [User] ([Name], [Email]) VALUES ('Name 03', 'Email03@Email.com')

EXECUTE SpUserSearch 0
EXECUTE SpUserSearch 1
EXECUTE SpUserSearch 0, NULL, NULL
EXECUTE SpUserSearch 1, NULL, NULL
EXECUTE SpUserSearch 0, 'Name 01', '@'
EXECUTE SpUserSearch 1, 'Name 01', '@'

结果:

前 4 个查询应该返回所有行。

存储过程:

CREATE OR ALTER PROCEDURE SpUserSearch
    @Condition BIT = 0, -- AND=0, OR=1.
    @Name NVARCHAR(100) = NULL,
    @Email NVARCHAR(100) = NULL
AS
BEGIN

    SET NOCOUNT ON;

    DECLARE @UseName BIT
    DECLARE @UseEmail BIT

    IF ((@Name IS NULL) OR (LEN(@Name) = 0)) SET @UseName = 0 ELSE SET @UseName = 1
    IF ((@Email IS NULL) OR (LEN(@Email) = 0)) SET @UseEmail = 0 ELSE SET @UseEmail = 1

    IF (@Condition = 0)
        SELECT [Id], [Name], [Email]
        FROM [User]
        WHERE
            ((@UseName = 1) OR ([Name] LIKE '%' + @Name + '%'))
            AND
            ((@UseEmail = 1) OR ([Email] LIKE '%' + @Email + '%'))
    ELSE
        SELECT [Id], [Name], [Email]
        FROM [User]
        WHERE
            ((@UseName = 1) OR ([Name] LIKE '%' + @Name + '%'))
            OR
            ((@UseEmail = 1) OR ([Email] LIKE '%' + @Email + '%'))

    RETURN (@@ROWCOUNT)

END

这里有两个问题:

  1. 我在SP逻辑上做错了什么?
  2. 这是以 WHERE 子句为条件的最高效的方法吗?我不确定 CURSOR 是否适用于这种情况。

如有任何建议,我们将不胜感激。

问题 1

我认为问题出在每个条件中括号内的 OR 谓词,我认为它应该是如下所示的 AND 谓词:

CREATE OR ALTER PROCEDURE SpUserSearch
    @Condition BIT = 0, -- AND=0, OR=1.
    @Name NVARCHAR(100) = NULL,
    @Email NVARCHAR(100) = NULL
AS
BEGIN

    SET NOCOUNT ON;

    DECLARE @UseName BIT
    DECLARE @UseEmail BIT

    IF ((@Name IS NULL) OR (LEN(@Name) = 0)) SET @UseName = 0 ELSE SET @UseName = 1
    IF ((@Email IS NULL) OR (LEN(@Email) = 0)) SET @UseEmail = 0 ELSE SET @UseEmail = 1

    IF (@Condition = 0)
        SELECT [Id], [Name], [Email]
        FROM [User]
        WHERE
            ((@UseName = 1) AND ([Name] LIKE '%' + @Name + '%'))
            AND
            ((@UseEmail = 1) AND ([Email] LIKE '%' + @Email + '%'))
    ELSE
        SELECT [Id], [Name], [Email]
        FROM [User]
        WHERE
            ((@UseName = 1) AND ([Name] LIKE '%' + @Name + '%'))
            OR
            ((@UseEmail = 1) AND ([Email] LIKE '%' + @Email + '%'))

    RETURN (@@ROWCOUNT)

END

问题二

我同意 Larnu 的评论,因为您正在使用这些通配符,所以您可能无法在性能上做太多工作。

你的逻辑是错误的:你需要 @UseName = 0@UseEmail = 0 在程序的 AND 一半。您还需要在 OR 一半中将 (@UseName = 1) OR 交换为 (@UseName = 1) AND

尽管很难说如果只提供一个搜索值,@Condition 的意图是什么:如果行中的 [Name][Email] 为空怎么办?

CREATE OR ALTER PROCEDURE SpUserSearch
    @Condition BIT = 0, -- AND=0, OR=1.
    @Name NVARCHAR(100) = NULL,
    @Email NVARCHAR(100) = NULL
AS
BEGIN

    SET NOCOUNT ON;

    DECLARE @UseName BIT
    DECLARE @UseEmail BIT

    IF ((@Name IS NULL) OR (LEN(@Name) = 0)) SET @UseName = 0 ELSE SET @UseName = 1
    IF ((@Email IS NULL) OR (LEN(@Email) = 0)) SET @UseEmail = 0 ELSE SET @UseEmail = 1

    IF (@Condition = 0)
        SELECT [Id], [Name], [Email]
        FROM [User]
        WHERE
            ((@UseName = 0) OR ([Name] LIKE '%' + @Name + '%'))
            AND
            ((@UseEmail = 0) OR ([Email] LIKE '%' + @Email + '%'))
    ELSE
        SELECT [Id], [Name], [Email]
        FROM [User]
        WHERE
            ((@UseName = 1) AND ([Name] LIKE '%' + @Name + '%'))
            OR
            ((@UseEmail = 1) AND ([Email] LIKE '%' + @Email + '%'))

    RETURN (@@ROWCOUNT)

END

性能方面:这永远不会很好,because of the leading wildcard

您可能还会遇到参数嗅探问题,解决方案通常是动态构建查询,如我在 .

中所示

@UseName@UseEmail 不是必需的。如果它是一个空格,您可以简单地将变量设为 null,然后在 WHERE 下使用 ISNULL 以及 @Condition。 (不需要 IF/ELSE

这是戴泳镜的步骤:) :

CREATE OR ALTER PROCEDURE SpUserSearch
    @Condition BIT = 0, -- AND=0, OR=1.
    @Name NVARCHAR(100) = NULL,
    @Email NVARCHAR(100) = NULL
AS
BEGIN

    SET NOCOUNT ON;
   -- this would covers NULL and Whitespace
    SET @Name = CASE WHEN @Name IS NOT NULL AND LEN(@Name) = 0 THEN NULL ELSE '%' + @Name + '%' END

    SET @Email = CASE WHEN @Email IS NOT NULL AND LEN(@Email) = 0 THEN NULL ELSE '%' + @Email + '%' END
    
    SELECT [Id], [Name], [Email]
    FROM 
        [User]
    WHERE
    (
            @Condition = 0 
        AND 
        (
                [Name] LIKE ISNULL(@Name, [Name])
            AND [Email] LIKE ISNULL(@Email, [Email])
        )
    )
    OR 
    (
            @Condition = 1 
        AND 
        (
                [Name] LIKE ISNULL(@Name, [Name])
            OR  [Email] LIKE ISNULL(@Email, [Email])
        )
    )

    RETURN (@@ROWCOUNT)

END