简体   繁体   中英

How to construct the logic in this SQL WHERE clause?

I am trying to write a stored procedure to search through text fields of a table as follows:

TABLE: [User]

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

The production database has many columns and a very large data set. The point of this SP is to speed up searching as much as practically possible.

What I tried:

  1. EntityFrameworkCore LINQ queries.
  2. Generating SQL on-the-fly using ADO .NET.
  3. The stored procedure below.

The SP has yeilded the best results so far but the results are not accurate.

TEST SCRIPT

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', '@'

RESULTS:

测试脚本结果

The first 4 queries should have returned ALL rows.

  • Query 1: Expected Rows: 3, Returned Rows: 0.
  • Query 2: Expected Rows: 3, Returned Rows: 0.
  • Query 3: Expected Rows: 3, Returned Rows: 0.
  • Query 4: Expected Rows: 3, Returned Rows: 0.
  • Query 5: Expected Rows: 1, Returned Rows: 3.
  • Query 6: Expected Rows: 3, Returned Rows: 3.

STORED PROCEDURE:

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

There are two questions here:

  1. What am I doing wrong in the SP logic?
  2. Is this the most performant way to condition on the WHERE clause? I am not sure if CURSOR s apply in this context.

Any advice would be appreciated.

Question 1

I think the problem is around the OR predicates inside the parenthesis in each condition, I believe it should be an AND predicate like shown below:

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

Question 2

I agree with Larnu's comment that, because you're using those wildcards, you probably can't work much on performance.

Your logic is wrong: you need @UseName = 0 and @UseEmail = 0 in the AND half of the procedure. You also need to swap (@UseName = 1) OR to (@UseName = 1) AND in the OR half.

Although it's hard to say what the intention would be for @Condition if only one search value is supplied: what if [Name] or [Email] in the row is null?

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

Performance-wise: this is never going to be great, because of the leading wildcard .

You may also get parameter-sniffing problems, for which the solution is usually to build the query dynamically, as I show in an answer to your other question .

@UseName and @UseEmail are not necessary. you can simply null the variable if it's a whitespace, and then make use of ISNULL under the WHERE along with the @Condition as well. (no need for the IF/ELSE

Here is the procedure wearing a swimming goggles:):

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM