有效的 WHERE 子句检查最小值和 NULL 之间的值

Posted

技术标签:

【中文标题】有效的 WHERE 子句检查最小值和 NULL 之间的值【英文标题】:Efficient WHERE clause to check value BETWEEN minumim and NULL 【发布时间】:2018-02-01 17:32:54 【问题描述】:

我有一个 SQL 查询,它根据租期返回价格水平。该表有列:

ProductID (UNIQUEIDENTIFIER), MinDuration(INT), MaxDuration(INT), Price (DECIMAL(10, 4)

我目前使用的查询的简化版本是:

SELECT Price
FROM RentalPrices
WHERE ProductID = @ProductID
  AND @Duration BETWEEN MinDuration AND MaxDuration;

表格示例

+--------------------------------------+-------------+-------------+-------+
|              ProductID               | MinDuration | MaxDuration | Price |
+--------------------------------------+-------------+-------------+-------+
| e93d19e1-142f-41cc-8f39-9cf7607717ac |           0 | 3           |    25 |
| e93d19e1-142f-41cc-8f39-9cf7607717ac |           4 | 7           |    50 |
| e93d19e1-142f-41cc-8f39-9cf7607717ac |           8 | 14          |   100 |
| e93d19e1-142f-41cc-8f39-9cf7607717ac |          15 | 30          |   200 |
| e93d19e1-142f-41cc-8f39-9cf7607717ac |          31 | NULL        |   500 |
+--------------------------------------+-------------+-------------+-------+

我的问题是大于或等于 31 的值不会找到最后一行,即开放式最大值,有没有一种好的、简单的方法来处理这个问题?我知道一些可能可行的选项,但不确定什么是最好的。

BETWEEN MinDuration AND ISNULL(MaxDuration,2147483647) @Duration >= MinDuration AND (@Duration <= MaxDuration OR MaxDuration IS NULL)

特别是目标是在最大数量的情况下提高效率,我们最终会达到最后一个值的情况很少见(在实际表格中,它不是一个月,而是可能是六个月甚至更长,具体取决于什么产品正在租用),因此对于持续时间在两个值之间的最有效的解决方案,同时仍然处理上限为空的情况,并且效率还可以。

【问题讨论】:

您的ISNULL 和您的`AND (OR)` 似乎是一个合理的解决方案。你有什么顾虑? 第二个选项有什么问题? 第一个选项使用了一个技巧。第二个选项是明确而直接的。 @Md.SumanKabir,但这与 = MinDuration 相同,其中 MaxDuration 为 NULL,因此在示例表 32 中不返回任何价格。 @Md.SumanKabir 那行不通。 BETWEEN MinDuration AND ISNULL(MaxDuration,@duration) 会。 【参考方案1】:

最有效的方法大概就是忽略maxduration

SELECT TOP (1) Price
FROM ?
WHERE ProductID = @ProductID AND MinDuration <= @Duration
ORDER BY MinDuration DESC;

这可以利用(ProductId, MinDuration)上的索引。

至于你的问题本身。我会投票赞成将最大值存储在表中,而不是为此目的使用NULL。不是最美观的解决方案,但它简化了编码。您提出的两种方法之间的选择实际上与美观有关(尽管我会使用 ANSI 标准 COALESCE())。

【讨论】:

排序需要DESC? 我不明白这是如何回答这个问题的。如果传递的持续时间为 6,则不应返回第一行,因为它的最大持续时间为 3,因此这不适合寻求 6 天租金的人。 @andrew 在您的示例中,如果您有 20 个 @duration,那么您想要 15->30 的行吗?由于还返回了所有持续时间较短的记录,您希望最高 MinDuration 的记录低于您的搜索变量吗? @andrew 在此答案中,where 子句仅按最短持续时间过滤。 where 子句获取 MinDuration 小于或等于变量的所有行。然后对这些行进行排序,并实际返回第一行(前 1 行)。该行应该具有 where 子句找到的行中最高的 MinDuration。因此它们应该按降序排列,以确保最高匹配值在前。 (按升序排序将始终返回 0-3 行...) 这并不能真正回答一般问题,它仅适用于有人对特定产品的价格感兴趣的特定情况。例如,当没有WHERE Product = @ProductID 时,它将不起作用。 (我认为)问题是关于查询 x 和无穷大之间值的通用方法。【参考方案2】:

不是最有效的,但我认为是干净的

SELECT Price
FROM RentalPrices
WHERE ProductID = @ProductID
AND @Duration BETWEEN MinDuration AND isnull(MaxDuration, @Duration);

考虑 ProductID、Price 上的集群 PK

declare @rental table (id int not null, minDuration int not null, maxDuration int, price int not null);
insert into @rental (id, minDuration, maxDuration, price) values 
         (1, 0, 3, 25)
       , (1, 4, 7, 50)  
       , (1, 8, 14, 100)
       , (1, 15, 30, 200)
       , (1, 31, null, 500);

declare @id int = 1;
declare @duration int = 15;

select id, minDuration, maxDuration, price 
  from @rental
 order by id, MinDuration;

SELECT Price, @duration, minDuration, maxDuration
  FROM @rental
 WHERE id = @id
   AND @duration BETWEEN minDuration AND isnull(maxDuration, @Duration)
 order by id, minDuration;

【讨论】:

这与MinDuration &lt;= @Duration ... 相同,与其他答案相同,但不需要TOP/ORDER BY。 不会ISNULL(MaxDuration, @Duration) 总是匹配,当@Duration 落入小括号时会导致额外的结果?当然可以忽略较大的行,或者我们可以 SELECT TOP 1 无论如何都会返回顺序中的第一个匹配项,但它与写入的响应有关,需要代码在服务器上解决它。跨度>

以上是关于有效的 WHERE 子句检查最小值和 NULL 之间的值的主要内容,如果未能解决你的问题,请参考以下文章

NULL的陷阱:Merge

merge into on条件能有Null判断嘛

SQL 基础之where过滤和比较运算符

向 where 子句添加条件

在带有 LINQ to XML 的 VB.NET 中,where 子句在属性值和字符串之间设置不区分大小写的比较

如果不是 Null,则检查输入参数并在 SQL Server 的 where 中使用它