web-dev-qa-db-ja.com

SQL Server 2008の空の文字列とスペース

私は今朝少し奇妙なことに遭遇し、私はそれを解説のために提出すると思った。

次のSQLクエリがSQL 2008に対して実行されたときに「等しい」と出力される理由を誰かが説明できますか。db互換性レベルは100に設定されています。

if '' = ' '
    print 'equal'
else
    print 'not equal'

そして、これは0を返します:

select (LEN(' '))

スペースを自動トリミングしているようです。これが以前のバージョンのSQL Serverに当てはまるかどうかはわかりません。テストすることもできません。

本番クエリが誤った結果を返していたため、これに遭遇しました。この動作はどこにも文書化されていません。

これに関する情報はありますか?

78
jhale

varcharsと等式は、TSQLでは厄介です。 LEN関数の説明:

指定された文字列式のバイト数ではなく、文字数を返します。末尾の空白を除きます

問題のデータの真のDATALENGTHカウントを取得するには、byteを使用する必要があります。 Unicodeデータがある場合、この状況で得られる値はテキストの長さと同じではないことに注意してください。

print(DATALENGTH(' ')) --1
print(LEN(' '))        --0

式の等価性については、次のように2つの文字列の等価性が比較されます。

  • 短い文字列を取得
  • 長さが長い文字列の長さと等しくなるまで、空白のあるパッド
  • 2つを比較する

予期しない結果を引き起こしているのは中間ステップです-そのステップの後、空白と空白を効果的に比較しているので、それらは等しいように見えます。

LIKEは、=「空白」の状況では、照合しようとしていたパターンで空白埋めを実行しないためです。

if '' = ' '
print 'eq'
else
print 'ne'

eqを与えます:

if '' LIKE ' '
print 'eq'
else
print 'ne'

neを与えます

ただし、LIKEには注意してください。対称ではありません。パターン(RHS)では末尾の空白を有意として扱いますが、一致表現(LHS)では扱いません。以下は here からのものです。

declare @Space nvarchar(10)
declare @Space2 nvarchar(10)

set @Space = ''
set @Space2 = ' '

if @Space like @Space2
print '@Space Like @Space2'
else
print '@Space Not Like @Space2'

if @Space2 like @Space
print '@Space2 Like @Space'
else
print '@Space2 Not Like @Space'

@Space Not Like @Space2
@Space2 Like @Space
82
butterchicken

=演算子は、T-SQLが「同等」ではなく「式のコンテキストの照合によると同じ単語/フレーズ」であり、LENは「単語/フレーズ内の文字数」です。後続の空白を前の単語/フレーズの一部として扱う照合はありません(ただし、先行する空白を前の文字列の一部として扱います)。

「this」と「this」を区別する必要がある場合は、「this」と「this」が同じ単語であるため、「同じ単語またはフレーズです」演算子を使用しないでください。

Way = worksに貢献するのは、文字列の等価演算子が引数の内容と式の照合コンテキストに依存する必要があるという考え方ですが、引数が両方とも文字列型である場合、引数の型に依存するべきではないという考えです。

「これらは同じ単語です」という自然言語の概念は通常、=などの数学演算子でキャプチャできるほど正確ではなく、自然言語には文字列型の概念はありません。コンテキスト(つまり、照合)は重要であり(自然言語で存在します)、ストーリーの一部であり、追加のプロパティ(風変わりなように見えるもの)は、の不自然な世界で明確に定義するために=の定義の一部ですデータ。

型の問題では、異なる文字列型に格納されている単語を変更したくないでしょう。たとえば、タイプVARCHAR(10)、CHAR(10)、およびCHAR(3)はすべて、単語「cat」、および? = 'cat'は、これらのタイプのいずれかの値がWord 'cat'を保持するかどうかを判断できるようにします(大文字と小文字の問題は照合によって決定されます)。

JohnFxのコメントへの応答:

Books Onlineの charおよびvarcharデータの使用 を参照してください。そのページから引用して、私のものを強調します:

各charおよびvarcharデータ値には照合があります。照合順序は、各文字を表すために使用されるビットパターン、比較規則、およびケースまたはアクセント。

見つけるのは簡単かもしれませんが、文書化されています。

注目すべきは、SQLのセマンティクスであり、ここで=は実際のデータと比較のコンテキスト(コンピューターに保存されているビットに関するものではなく)に関係しているため、長い間SQLの一部でした。 RDBMSとSQLの前提は、実世界のデータを忠実に表現することです。そのため、同様のアイデア(CultureInfoなど)がALGOLに似た言語の領域に登場する何年も前に照合をサポートします。これらの言語の前提(少なくともごく最近まで)は、ビジネスデータの管理ではなく、エンジニアリングの問題解決でした。 (最近、検索などの非エンジニアリングアプリケーションでの同様の言語の使用が普及していますが、Java、C#などはまだビジネス以外のルートで苦労しています。)

私の意見では、SQLを「ほとんどのプログラミング言語」とは異なると批判することは公平ではありません。 SQLは、エンジニアリングとは非常に異なるビジネスデータモデリング用のフレームワークをサポートするように設計されているため、言語は異なります(そして、その目標には適しています)。

一体、SQLが最初に指定されたとき、一部の言語には組み込みの文字列型がありませんでした。また、一部の言語では、文字列間の等号演算子は文字データをまったく比較しませんが、参照を比較します!さらに10年か20年後に、==が文化に依存するという考え方が標準になっても驚かないでしょう。

18
Steve Kass

私はこれを見つけました ブログ記事 動作とその理由を説明しています。

SQL標準では、文字列比較では、短い文字列にスペース文字を効果的に埋め込むことが必要です。これにより、N '' = N '' (空の文字列は1つ以上のスペース文字の文字列に相当します)、より一般的には、末尾のスペースだけが異なる場合、任意の文字列は別の文字列に相当します。これは状況によっては問題になる場合があります。

詳細情報もMSKB316626で入手できます

9
JohnFx

少し前に似たような問題がありました here

LEN( '')の代わりに、DATALENGTH( '')を使用します-これにより正しい値が得られます。

解決策は、そこでの回答で説明したようにLIKE句を使用すること、および/またはWHERE句に2番目の条件を含めてDATALENGTHもチェックすることでした。

その質問とリンクを読んでください。

5
AdaTheDev

値をリテラルスペースと比較するには、LIKEステートメントの代わりにこの手法を使用することもできます。

IF ASCII('') = 32 PRINT 'equal' ELSE PRINT 'not equal'
3
David G

別の方法は、スペースに価値がある状態に戻すことです。例:スペースを_のような既知の文字に置き換えます

if REPLACE('hello',' ','_') = REPLACE('hello ',' ','_')
    print 'equal'
else
    print 'not equal'

戻り値:等しくない

理想的ではなく、おそらく遅いかもしれませんが、すぐに必要な場合は、もう1つの手っ取り早い方法です。

0
CooPzZ

SQLサーバーのchar/varcharフィールドでselectのレコードを区別する方法:例:

declare @mayvar as varchar(10)

set @mayvar = 'data '

select mykey, myfield from mytable where myfield = @mayvar

予想

mykey(int)| myfield(varchar10)

1 | 'データ'

取得

mykey | myfield

1 | 'data'2 | 'データ'

select mykey, myfield from mytable where myfield = 'data'(最後の空白なし)と書いても同じ結果が得られます。

どうやって解決したの?このモードでは:

select mykey, myfield
from mytable
where myfield = @mayvar 
and DATALENGTH(isnull(myfield,'')) = DATALENGTH(@mayvar)

myfieldにインデックスがある場合は、それぞれの場合に使用されます。

役立つことを願っています。

0
Orix

Nullを使用するというアイデアは優れていますが、常に使用できるとは限りませんが、他の文字の有無にかかわらず、データ内のスペースを処理する必要がある場合があります。私は説明された状況に遭遇し、このように解決しました:

... where( '>' + @space + '<')<>( '>' + @ space2 + '<')

もちろん、大量のデータを処理することはありませんが、数百行ですばやく簡単に動作します...

ハーバート

0
Herbert