6

我正在为来自客户端的自定义数据创建适配器。我不能更改他们的架构或修改他们表中的值,但我可以建议新的索引。方法是使用 CTE 连接自定义数据并重新格式化以使用我们的列名、枚举值等。一旦重新格式化数据,我们的标准 CTE 就可以附加,并从中伪造一个查询来执行我们的标准分析。

由于 LEFT JOIN 没有匹配,或者由于它们的数据中的值实际上是 NULL,重新格式化产生的一些值是 NULL。

我的任务是在许多字段中用默认值替换 NULL,并且还允许将 WHERE 子句插入到查询中。目前,ISNULL 调用或 CASE 语句用于处理默认值。目前,当 WHERE 条件被命中时,这种替换已经被执行,因此可以访问我们的查询构建器的最终用户可以过滤一个可能是默认值的值。如果过滤器值是默认值,则应选择用默认值替换的具有 NULL 值的记录。

问题是,如果我有 myField = ISNULL(myField, 'MyDefault') 作为我的重新格式化公式,然后在洋葱的外层(后来的 CTE)中有 WHERE myField = 'MyDefault',那么这个 where 子句不是sargable:查询优化器不会在 myField 上选择我的索引。

我想到的一个部分解决方案是不在我的内部 CTE 中进行任何 NULL 替换,然后有一个插入 WHERE 子句的 CTE,然后有一个执行所有 NULL 替换的外部 CTE。这样的查询可以使用索引。(我已经验证了这一点。)但是,where 子句不能再期望对默认值的值的测试也会选择具有 NULL 值的记录,因为这种替换还没有发生。

有没有办法执行空替换,允许 SARGABLE where 过滤器,并过滤 NULL 值,就好像它们保持默认值一样?

关于问题大小的注意事项:一个典型的例子涉及将一个 600 万条记录表连接到一个 700 万条记录表,并使用多对多关系创建 1200 万条记录。当过滤器为 SARGABLE 时,查询大约需要 10 秒。当它不是 SARGABLE 时,在一台机器上需要 10 多分钟,在更快的机器上需要 3 分钟以上。

对所选解决方案的评论:

巧妙地使用交集来允许将字段与 NULL 或没有 ISNULL 或其他非 sargable 函数的非 NULL 进行比较,可以在对遗留查询进行最少更改的情况下插入到我们的代码中。

评论 2:缺少案例

有以下六种情况:

  1. 所选值不为空且不等于默认值且与过滤器值不匹配。应排除。
  2. 所选值不为空且不等于默认值并且与过滤器值匹配。应包括。
  3. 选定的值不为空,并且等于默认值并且与过滤器值不匹配。应排除。
  4. 所选值不为空,并且 DOES 等于默认值,并且与过滤器值匹配。应包括。
  5. 所选值为 null 且过滤器值不是默认值。应排除。
  6. 所选值为空,过滤器值为默认值。应包括。

案例 4 无法使用提供的解决方案。所选字段不为空,因此交叉点的前半部分有一条非空值的记录。但是在交集的后半部分,NULLIF 语句创建了一条具有空值的记录。交集产生零记录。记录被拒绝。我仍在寻找处理这种情况的解决方案。很近...

更新解决方案:

我有一个修复。假设我正在使用 [County Name],而我的默认值是“未知”...

where EXISTS (
    select [County Name] 
    intersect 
    (select NULLIF('User selected county name', 'Unknown') union select 'User selected county name')
)
4

4 回答 4

3

看起来您已经在动态构建查询,因此当您从工具中获取需要过滤的值时,您可以使用看起来像这样的 where 子句构建查询。

SQL小提琴

MS SQL Server 2008 架构设置

create table YourTable
(
  ID int identity primary key,
  Name varchar(20)
)

create index IX_YourTable_Name on YourTable(Name)

insert into YourTable values
('Name1'),
('Name2'),
(null)

查询 1

declare @Param varchar(20)
set @Param = 'DefaultName'

select ID,
       coalesce(Name, 'DefaultName') as Name
from YourTable
where exists(select Name intersect select nullif(@Param, 'DefaultName'))

结果

| ID |        NAME |
--------------------
|  3 | DefaultName |

查询 2

declare @Param varchar(20)
set @Param = 'Name1'

select ID,
       coalesce(Name, 'DefaultName') as Name
from YourTable
where exists(select Name intersect select nullif(@Param, 'DefaultName'))

结果

| ID |  NAME |
--------------
|  1 | Name1 |

上述查询的查询计划将使用 IX_YourTable_Name 进行搜索。

在此处输入图像描述

参考:未记录的查询计划:平等比较

于 2013-06-21T18:29:12.797 回答
1

你说你不能改变架构,但我在这里跳出框框思考。您可以添加一个具有查看现有数据库的视图的新数据库。例如:

use NewViewDb
GO

CREATE VIEW dbo.[T1T2View]
AS
SELECT field1, field2, COALESCE(field3, 'default value'), ...
FROM RealDb.dbo.Table1 t1 LEFT JOIN RealDb.dbo.Table2 t2 
ON t1.Id = t2.Id

GO
于 2013-06-21T18:09:39.550 回答
0

因此,最终问题不在于发送数据时使用ISNULL(或COALESCE),而在于过滤。那里的问题是,如果在谓词中使用子句,通常不能使用索引OR,而这些语句几乎就是这样。

最终的解决方案是,如果您必须在 sql server 内部执行此操作,则使用动态 sql,或者如果您在 sql server 之外,则只需构建最终查询。如果您在存储过程中执行此操作,您最终将只进行 sql 查询,然后调用sp_executesql生成的查询。

您将在此处执行的操作的关键点是您将检查要过滤的值(即“我的默认值”),如果是该值,那么您将添加谓词以过滤该值,或者您将添加将值限制为的谓词IS NULL

如果您需要有关如何完成此操作的更多信息,我可以提供一个示例查询。

于 2013-06-21T18:17:38.333 回答
0

如果您使用临时表而不是 CTE,则可以填充数据然后对其进行索引。

于 2013-06-21T17:54:03.023 回答