3

我有一个包含一列位值的表。我想编写一个函数,如果关联项的所有记录都为真,则返回真。

我发现这样做的一种方法是:

Select @Ret = CAST(MIN(CAST(IsCapped as tinyInt)) As Bit) 
    from ContractCover cc
    Inner join  ContractRiskVersion crv on cc.ContractRiskId = crv.ContractRiskId
    WHERE crv.ContractVersionId = @ContractVersionId
    AND cc.IsActive = 1
    return @ret

但是转换为 int 是为了获得最低的成本吗?我是否应该只是根据说查询:

(count(Id) where IsCapped = 0 > 0) 返回 false 而不是进行多次强制转换?

在执行计划中,调用这个函数似乎并不重执行(但我对分析查询计划不太熟悉 - 它似乎与存储过程的另一部分的成本相同,例如 2 %)。

编辑 - 当我执行调用函数的存储过程并查看执行计划时 - 它调用函数的部分具有查询成本(相对于批处理):1%,与存储过程的其他部分相当。除非我看错了:)

谢谢!!

4

3 回答 3

4

我会使用 exists 语句来执行此操作,因为它会从找到 1 条记录的那一刻起跳出查询IsCapped = 0,因为您的查询将始终读取所有数据。

CREATE FUNCTION dbo.fn_are_contracts_capped(@ContractVersionId int)
RETURNS bit
WITH SCHEMABINDING
AS
BEGIN
    DECLARE @return_value bit

    IF EXISTS(
      SELECT 1 
        FROM dbo.ContractCover cc
        JOIN dbo.ContractRiskVersion crv 
          ON cc.ContractRiskId = crv.ContractRiskId
       WHERE crv.ContractVersionId = @ContractVersionId
         AND cc.IsActive = 1
         AND IsCapped = 0)
      BEGIN
        SET @return_value = 0
      END
    ELSE
      BEGIN
        SET @return_value = 1
      END

    RETURN @return_value
  END

与读取数据所需的 IO 相比,强制转换不会增加很多开销。

编辑:将代码包装在标量函数中。

于 2012-06-01T07:39:17.210 回答
2

铸造SELECT将受到 CPU 和内存的限制。在这种情况下不确定多少——通常情况下,我们通常会先尝试优化 IO,然后再担心 CPU 和内存。所以我在那里没有给你一个明确的答案。

也就是说,这个特殊解决方案的问题在于它不会短路。SQL Server 将读出 ContractVersionId = @ContractVersionId 和 IsActive = 1 的所有行,将 IsCapped 转换为 INT,并取最小值,实际上,一旦找到 IsCapped = 0 的单行,您就可以退出。它不会如果 ContactVersionId 具有高度选择性,并且只返回表的一小部分,或者大多数行都有上限,这并不重要。但是,如果 ContactVersionId 的选择性不高,或者如果很大比例的行没有上限,那么您要求 SQL Server 做太多的工作。

第二个考虑是标量值函数在 SQL Server 中是一个臭名昭著的性能拖累。如果可能,最好创建为内联表函数,例如:

create function AreAllCapped(@ContractVersionId int)
returns table as return (
select 
  ContractVersionId = @ContractVersionId 
, AreAllCapped = case when exists (
      select * 
      from ContractCover cc 
      join ContractRiskVersion crv on cc.ContractRiskId = crv.ContractRiskId
      where crv.ContractVersionId = @ContractVersionId
        and cc.IsActive = 1
        and IsCapped = 0
      ) 
    then 0 else 1 end
)

然后您可以CROSS APPLY在 FROM 子句中调用它(假设 SQL 2005 或更高版本)。

最后一点:计算 IsCapped = 0 的地方有类似的问题。如果您熟悉的话,这就像 LINQ 中 Any() 和 Count() 之间的区别。Any() 将短路, Count() 必须实际计算所有元素。SELECT COUNT(*) ... WHERE IsCapped = 0仍然必须计算所有行,即使您只需要一行就可以继续前进。

于 2012-06-01T13:19:41.540 回答
1

当然,众所周知,bit列不能作为参数传递给聚合函数(因此,如果需要传递,则必须先将其转换为整数),但是bit可以对列进行排序经过。因此,您的查询可以这样重写:

SELECT TOP 1 @Ret = IsCapped
FROM ContractCover cc
INNER JOIN ContractRiskVersion crv on cc.ContractRiskId = crv.ContractRiskId
WHERE crv.ContractVersionId = @ContractVersionId
  AND cc.IsActive = 1
ORDER BY IsCapped;

请注意,在此特定查询中,假定IsCapped不能为 NULL。如果可以,您需要在 WHERE 子句中添加一个额外的过滤器:

AND IsCapped IS NOT NULL

当然,除非您实际上更愿意返回 NULL 而不是 0(如果有的话)。

至于选角的成本,对于菲利普和彼得已经说过的话,我真的没有什么要补充的。我确实发现bit数据需要在聚合之前进行转换是一件令人讨厌的事情,但这从来都不是主要问题。

于 2012-06-01T18:27:51.497 回答