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的答案,因为我们想在视图中使用此查询。办公室的一些人对使用案例陈述持谨慎态度,因为他们更愿意控制以确保我们首先获得较小的结果集(Aaron的答案),但这一切都归结为查看查询计划并检查您的阅读计数。
再次感谢所有回复!
答案 0 :(得分: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;
(我还谈到this answer中的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并不完美。几年前我写了this article来帮助解决这个问题。
答案 1 :(得分:6)
首先,这不是一个“明显的设计问题”。 SQL是输出的描述性语言,而不是指定如何完成prcoessing的过程语言。一般而言,不能保证处理顺序,这是一个优点。我可能会说存在设计问题,但它是围绕SQL语句中异常的一般处理。
根据SQL Server文档(http://msdn.microsoft.com/en-us/library/ms181765.aspx),您可以依赖于标量表达式的CASE语句的evauation顺序。因此,以下内容应该有效:
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。当演员表是隐含的(并且有数百列)时,这种情况会更加糟糕。
答案 2 :(得分: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 (我是 Isnumeric )以证明我没有得到任何字母数字。
但是由于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中实现大小写的任何字母数字数字
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
以上将解决这两个问题。任何人对此表示欢迎。 :)
答案 3 :(得分:-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