6

假设以下模式和查询:

请看一下在我们期望为整数的 varchar 列中具有值的明显设计问题。

create table dbo.Parent (
    Id bigint NOT NULL,
    TypeId int NOT NULL
)

create table dbo.Child (
    Id bigint NOT NULL,
    ParentId bigint NOT NULL,
    TypeId int NOT NULL,
    varcharColumn varchar(300) NULL
)

select cast(c.varcharColumn as int)
from dbo.Parent p (nolock)
    inner join dbo.Child c (nolock)
        on p.Id = c.ParentId
            and c.TypeId = 2
where p.TypeId = 13

休息:

由于无法转换为 int 的值,我们得到了强制转换中断。在这种情况下:“123-1”。奇怪的是,被转换的值被从最终结果集中过滤掉了。

例如,这将返回零个结果

select c.varcharColumn
from dbo.Parent p (nolock)
    inner join dbo.Child c (nolock)
        on p.Id = c.ParentId
            and c.TypeId = 2
where p.TypeId = 13
    and c.varcharColumn = '123-1'

查询计划最终会查看 Child 表并在 where 子句之前实际应用 cast 函数。

我们能够通过在子表上创建一个新索引来解决这个问题(它正在执行 PK扫描

create index [NCIDX_dbo_Child__TypeId] on dbo.Child (
    TypeId
)
include (
    ParentId,
    varcharColumn
)

它现在首先过滤父表的 where 子句。

有没有办法在没有额外索引的情况下解决这个问题?同样,请不要提出任何与修复我们的架构相关的建议。在这种情况下,这绝对是正确的解决方法。

我最感兴趣的是了解为什么它在过滤结果集之前应用了演员表。

谢谢

编辑 - 答案:

非常感谢 Aaron 和 Gordon。如果我得到超过 15 个代表点,我会回来并回复您的两个回复。

我们最终需要 Gordon 的答案,因为我们想在视图中使用此查询。办公室里的一些人对使用 case 语句持谨慎态度,因为他们更喜欢有更多的控制权来确保我们首先有一个较小的结果集(Aaron 的回答),但这一切都归结为查看查询计划并检查您的阅读计数。

再次感谢所有回复!

4

4 回答 4

6

您无法轻松控制 SQL Server 处理查询的方式。您可以通过深入研究执行计划来找出一些原因,但在我认为的这种特定情况下,理解这是您遇到的最少问题。也许你可以对加入提示做一些事情,但这对我来说很棘手,并且行为仍然不能保证(特别是当你移动到新版本等时)。您可以尝试的两种解决方法是:

;WITH c AS 
(
  SELECT varcharColumn, ParentID, TypeId
   FROM dbo.Child AS c
   WHERE c.TypeId = 2
   AND ISNUMERIC(varcharColumn) = 1 --*
)
SELECT CONVERT(INT, c.varcharColumn)
FROM dbo.Parent AS p
INNER JOIN c
ON c.ParentId = p.Id
WHERE p.TypeId = 13;

但我听说过这样的情况,即使将其分离到 CTE 中也可能导致导致转换首先发生的糟糕计划。因此,您可能需要进一步分解它:

SELECT varcharColumn, ParentID, TypeId
INTO #c
   FROM dbo.Child AS c
   WHERE c.TypeId = 2
   AND ISNUMERIC(varcharColumn) = 1; --*

SELECT CONVERT(INT, c.varcharColumn)
  FROM dbo.Parent AS p
  INNER JOIN #c AS c
  ON c.ParentId = p.Id
  WHERE p.TypeId = 13;

(我也在这个答案中谈到了CASE表达解决方案。)

如果您使用的是 SQL Server 2012,则可以简单地执行此操作 - 现在,是否在过滤器之前尝试转换并不重要,并且您不必依赖不稳定的ISNUMERIC()功能。*

SELECT TRY_CONVERT(INT, c.varcharColumn)
  FROM dbo.Parent AS p
  INNER JOIN dbo.Child AS c
  ON c.ParentId = p.Id
  WHERE c.TypeId = 2
  AND p.TypeId = 13;

*请注意,IsNumeric 并不完美。几年前我写了这篇文章来帮助解决这个问题。

于 2012-09-06T18:09:28.627 回答
6

首先,这不是一个“明显的设计问题”。SQL 是输出的描述性语言,而不是指定如何进行处理的过程语言。一般来说,不能保证处理的顺序,这是一个优势。我可能会说存在设计问题,但它与 SQL 语句中异常的一般处理有关。

根据 SQL Server 文档 (http://msdn.microsoft.com/en-us/library/ms181765.aspx),对于标量表达式的 CASE 语句,您可以依赖评估顺序。因此,以下应该有效:

select (case when isnumeric(c.varcharColumn) = 1 then cast(c.varcharColumn as int) end)

或者,为了更接近“int”表达式:

select (case when isnumeric(c.varcharColumn) = 1 and c.varcharColumn not like '%.%' and c.varcharColumn not like '%e%'
             then cast(c.varcharColumn as int)
        end)

至少您的代码正在执行显式 CAST。当强制转换是隐式的(并且有数百列)时,这种情况会更加糟糕。

于 2012-09-06T18:18:49.277 回答
0

根据我的技术观点

 create table dbo.Parent (
    Id bigint NOT NULL,
    TypeId int NOT NULL
)
    create table dbo.Child (
        Id bigint NOT NULL,
        ParentId bigint NOT NULL,
        TypeId int NOT NULL,
        varcharColumn varchar(300) NULL
    )

    select cast(c.varcharColumn as int)
    from dbo.Parent p (nolock)
        inner join dbo.Child c (nolock)
            on p.Id = c.ParentId
                and c.TypeId = 2
    where p.TypeId = 13

在选择时,您正在进行内部联接,这会导致查找表并过滤掉数据现在据我所知,SQL Server 正在应用“所谓的优化”并随之进行强制转换操作。显然它是在过滤之前应用这种转换导致转换问题。

另一个问题,它的好奇心把我带到这里,

SELECT    MAX( Cast(dealer_number AS INT) + 1)
                    FROM   dealer_number dn
                    INNER JOIN dealer d
                    ON dn.dealer_seq = d.dealer_seq 
                    INNER JOIN dealer_type(nolock) dt
                    ON dt.dealer_number_seq = dn.dealer_number_seq
                    INNER JOIN program_dealer_type(nolock) pdt
                     ON pdt.program_dealer_type_seq = dt.program_dealer_type_seq
                    WHERE 
                    Isnumeric(dealer_number) = 1 AND
                    pdt.dealer_type = 'Dealer'
                    AND d.program_seq = 57

有以下问题

varchar 值“10054239051”的转换溢出了一个 int 列。

我添加了Isnumeric以 100% 证明我没有得到任何字母数字。

但是由于 Isnumeric 在表查找性能时会导致相应的错误

现在只需删除Isnumeric就可以了

SELECT    MAX( Cast(dealer_number AS INT) + 1)
                    FROM   dealer_number dn
                    INNER JOIN dealer d
                    ON dn.dealer_seq = d.dealer_seq 
                    INNER JOIN dealer_type(nolock) dt
                    ON dt.dealer_number_seq = dn.dealer_number_seq
                    INNER JOIN program_dealer_type(nolock) pdt
                    ON pdt.program_dealer_type_seq = dt.program_dealer_type_seq
                    WHERE 
                    pdt.dealer_type = 'Dealer'
                    AND d.program_seq = 57

现在我刚刚删除了那个预处理过滤器,它只是解决了问题,但是为了 100% 证明我没有得到任何字母数字我必须在选择中实现案例

SELECT    MAX(case WHEN 
                    ISNUMERIC(DEALER_NUMBER )=1 
                        THEN  Cast(dealer_number AS INT) 
                        ELSE 0 END 
                    + 1) 
                    FROM   dealer_number dn
                    INNER JOIN dealer d
                    ON dn.dealer_seq = d.dealer_seq 
                    INNER JOIN dealer_type(nolock) dt
                    ON dt.dealer_number_seq = dn.dealer_number_seq
                    INNER JOIN program_dealer_type(nolock) pdt
                    ON pdt.program_dealer_type_seq = dt.program_dealer_type_seq
                    WHERE 
                    pdt.dealer_type = 'Dealer'
                    AND d.program_seq = 57

以上将解决这两个问题。任何人都对此表示欢迎。:)

于 2019-10-17T06:58:24.627 回答
-1

您可以将过滤器移动到子查询中,这将首先过滤掉坏值,然后强制转换所有内容。这没有回答为什么,但它确实以我认为最简单的方式为您提供了您想要的东西。

select cast(varcharColumn as int)
FROM(
select c.varcharColumn

from dbo.Parent p (nolock)
    inner join dbo.Child c (nolock)
        on p.Id = c.ParentId
            and c.TypeId = 2
where p.TypeId = 13
) table1
于 2012-09-06T18:12:53.683 回答