6

次のスキーマとクエリを想定します。

intsであると予想される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'

クエリプランは、最終的に子テーブルを調べ、where句の前に実際にキャスト関数を適用します。

子テーブルに新しいインデックスを作成することでこれを修正できました(PKスキャンを実行していました)

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

親テーブルのwhere句を最初にフィルタリングするようになりました。

追加のインデックスなしでこれを修正する方法はありますか?繰り返しになりますが、スキーマの修正に関連する提案はご遠慮ください。この場合、それは間違いなく適切な修正です。

結果セットをフィルタリングする前にキャストを適用した理由を理解することに主に興味があります。

ありがとう

編集-回答:

アーロンとゴードンの両方に感謝します。15回以上の返信ポイントを獲得した場合は、両方の返信に戻ってきます。

ビューでこのクエリを使用したかったので、Gordonの回答が必要になりました。オフィスの何人かの人々は、最初に小さな結果セットを確実に取得することをより細かく制御することを好むため、caseステートメントの使用に慎重でした(アーロンの答え)が、結局はクエリプランを見てあなたの読み取りをチェックすることになりますカウントします。

繰り返しになりますが、すべての回答に感謝します。

4

4 に答える 4

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 に答える
6

SQL Server がクエリを処理する方法を簡単に制御することはできません。実行計画を深く掘り下げることで、その理由のいくつかを理解することができますが、それを理解することは、この特定のケースでのあなたの問題の中で最も少ないと思います. 結合ヒントで少しはできるかもしれませんが、それは私にとってハックであり、動作はまだ保証されていません (特に新しいバージョンに移行する場合など)。あなたが試すことができる2つの回避策は次のとおりです。

;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 に答える