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的重点是尽可能加快搜索速度。
我尝试了什么:
- EntityFrameworkCore LINQ 查询。
- 使用 ADO .NET 即时生成 SQL。
- 下面的存储过程。
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 个查询应该返回所有行。
- 查询 1:预期行数:3,返回行数:0。
- 查询 2:预期行数:3,返回行数:0。
- 查询 3:预期行数:3,返回行数:0。
- 查询 4:预期行数:3,返回行数:0。
- 查询 5:预期行数:1,返回行数:3。
- 查询 6:预期行数:3,返回行数:3。
存储过程:
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
这里有两个问题:
- 我在SP逻辑上做错了什么?
- 这是以
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
我正在尝试编写一个存储过程来搜索 table 的文本字段,如下所示:
TABLE:[用户]
[Id] BIGINT PRIMARY KEY, IDENTITY (1, 1)
[Name] NVARCHAR(100) NOT NULL
[Email] NVARCHAR(100) NOT NULL, UNIQUE
生产数据库有很多列和一个非常大的数据集。这个SP的重点是尽可能加快搜索速度。
我尝试了什么:
- EntityFrameworkCore LINQ 查询。
- 使用 ADO .NET 即时生成 SQL。
- 下面的存储过程。
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 个查询应该返回所有行。
- 查询 1:预期行数:3,返回行数:0。
- 查询 2:预期行数:3,返回行数:0。
- 查询 3:预期行数:3,返回行数:0。
- 查询 4:预期行数:3,返回行数:0。
- 查询 5:预期行数:1,返回行数:3。
- 查询 6:预期行数:3,返回行数:3。
存储过程:
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
这里有两个问题:
- 我在SP逻辑上做错了什么?
- 这是以
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