Порожній рядок SQL Server 2008 проти простору


83

Сьогодні вранці я зіткнувся з чимось трохи дивним і подумав подати це для коментарів.

Хтось може пояснити, чому наступний запит SQL друкує "дорівнює" при запуску проти SQL 2008. Рівень сумісності db встановлено на 100.

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

І це повертає 0:

select (LEN(' '))

Здається, це автоматичне обрізання простору. Я не маю уявлення, чи так було в попередніх версіях SQL Server, і я більше не маю жодного, щоб навіть перевірити його.

Я зіткнувся з цим, оскільки виробничий запит повертав неправильні результати. Я ніде не можу задокументувати цю поведінку.

Хтось має якусь інформацію з цього приводу?


2
SQL 2005: select len ​​('') повертає 0
Mayo

1
Те саме робить на Sql Server 2000.
Pierre-Alain Vigeant

1
Це захоплююче питання. Здається, повертається рівним, незалежно від того, скільки пробілів ви ввели в будь-який рядок, збігаються вони чи ні. Після подальших експериментів я помітив, що він ефективно робить RTRIM з обох сторін оператора рівності перед порівнянням. Схоже, ви отримали відповідь на функцію LEN, але насправді мене цікавить більш ґрунтовна відповідь, ніж "вархари та рівність терплячі в TSQ" на частину вашого питання щодо рівності.
JohnFx,

Я вважаю, Oracle теж це робить.
quillbreaker

Взагалі, я вважаю, що зберігати порожній рядок - погана ідея, і це одна з причин. Я віддаю перевагу використанню Null і виявляю багато проблем, коли люди намагаються перетворити нульову інформацію у таке значення, як порожній рядок або вихід даних із нормального діапазону.
HLGEM

Відповіді:


90

varchars і рівність є непростими в TSQL. LENФункція каже:

Повертає кількість символів, а не кількість байт, даного рядкового виразу, за винятком порожніх пробілів .

Вам потрібно використовувати, DATALENGTHщоб отримати справжній byteпідрахунок даних, про які йдеться. Якщо у вас є дані Unicode, зверніть увагу, що значення, яке ви отримаєте в цій ситуації, не буде однаковим із довжиною тексту.

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

Коли справа стосується рівності виразів, два рядки порівнюються для рівності таким чином:

  • Отримати коротший рядок
  • Прокладка із заготовками, доки довжина не дорівнює довжині довжини
  • Порівняйте два

Це середній крок, який призводить до несподіваних результатів - після цього ви ефективно порівнюєте пробіли з пробілами - отже, вони вважаються рівними.

LIKEповодиться краще, ніж =у ситуації "пробілів", оскільки він не виконує заповнення пробілів на шаблоні, який ви намагалися зрівняти:

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

Дасть, eqпоки:

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

Дасть ne

Обережно, LIKEхоча: він не симетричний: він розглядає кінцеві пробіли як значущі у шаблоні (RHS), але не вираз відповідності (LHS). Звідси взято наступне :

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

1
Приємна відповідь. Я не помітив цього в документації LEN. Однак це не обмежується LEN. Функція ВПРАВО та ВЛІВО виявляє подібну поведінку, але там це не задокументовано. Здається, це буквал із пробілом, який спричиняє проблему. Я помітив, що це також повертається рівним: якщо '' = ПРОБІЛ (1) друк 'рівний' інакше друк 'не рівний', я насправді не зацікавлений отримати справжню довжину, я просто був збентежений, чому коли я шукав простір у стовпець, повернуто всі стовпці, які були порожніми рядками.
jhale

Крім того, приємна інформація про заяву LIKE. Я думаю, мораль історії полягає в тому, щоб намагатись не потрапляти в положення, коли потрібно порівнювати пробіл і порожній рядок.
jhale

2
Проблема більша, ніж порівняння простору з порожнім рядком. Порівняння будь-яких двох рядків, які закінчуються різною кількістю пробілів, демонструє однакову поведінку.
JohnFx,

3
@butterchicken: Вибачте за таку пізню пост, я тільки що бачив це питання, але коли я запустив цю (останню) на моєму sql-server-2008 r2я, @Space Not Like @Space2 @Space2 Not Like @Space . Будь-яка ідея чому?
Razort4x

1
Підтверджено на SQL Server 2012 та SQL Server 2014, результат@Space Not Like @Space2 @Space2 Not Like @Space
Просто учень

19

Оператор = T-SQL - це не стільки "дорівнює", скільки "це одне і те ж слово / фраза, відповідно до порівняння контексту виразу", а LEN - "кількість символів у слові / фразі". Жодне зіставлення не розглядає кінцеві пробіли як частину слова / фрази, що передує їм (хоча вони розглядають провідні пробіли як частину рядка, якому вони передують).

Якщо вам потрібно відрізнити "це" від "це", ви не повинні використовувати оператор "це те саме слово або фраза", оскільки "це" і "це" - це одне і те ж слово.

Сприяння шляху = працює - це ідея того, що оператор рівності рядків повинен залежати від вмісту своїх аргументів та від контексту порівняння виразу, але це не повинно залежати від типів аргументів, якщо вони обидва є рядковими типами .

Поняття природної мови "це одне й те саме слово", як правило, недостатньо чітке, щоб його можна було схопити математичним оператором, як =, і в природній мові немає поняття типу рядка. Контекст (тобто співставлення) має значення (і існує в природній мові) і є частиною історії, а додаткові властивості (деякі, які здаються химерними) є частиною визначення = для того, щоб зробити його чітко визначеним у неприродному світі даних.

Що стосується питання типу, ви не хотіли б, щоб слова змінювалися, коли вони зберігаються в різних типах рядків. Наприклад, типи VARCHAR (10), CHAR (10) та CHAR (3) можуть містити зображення слова «кішка», а? = 'cat' повинен дозволити нам вирішити, чи значення будь-якого з цих типів містить слово 'cat' (з питаннями регістру та наголосу, що визначаються порівнянням).

Відповідь на коментар JohnFx:

Див. Розділ Використання даних char та varchar у Books Online. Цитуючи цю сторінку, наголошу на моєму:

Кожне значення даних char та varchar має порівняння. Сортування визначає такі атрибути, як бітові шаблони, що використовуються для представлення кожного символу, правила порівняння та чутливість до регістру чи наголосу.

Я згоден, що це може бути простіше знайти, але це задокументовано.

Варто також зазначити, що семантика SQL, де = має відношення до реальних даних та контексту порівняння (на відміну від чогось про біти, що зберігаються на комп'ютері), є частиною SQL давно. Передумовою СУБД та SQL є вірне представлення даних із реального світу, отже, підтримка збігів за багато років до того, як подібні ідеї (наприклад, CultureInfo) увійшли в сферу мов, подібних до Алголу. Передумовою цих мов (принаймні до недавнього часу) було вирішення проблем в інженерії, а не управління діловими даними. (Останнім часом використання подібних мов у неінженерних програмах, таких як пошук, робить деякі прориви, але Java, C # і так далі все ще борються зі своїми комерційними коріннями.)

На мій погляд, нечесно критикувати SQL за відмінність від "більшості мов програмування". SQL був розроблений для підтримки основи для моделювання бізнес-даних, яка сильно відрізняється від інженерної, тому мова інша (і краще для своєї мети).

Чорт візьми, коли SQL було вперше вказано, деякі мови не мали жодного вбудованого типу рядка. І в деяких мовах досі оператор рівності між рядками взагалі не порівнює дані символів, а порівнює посилання! Мене не здивувало б, якщо через наступні десять років чи два ідея, що == залежить від культури, стає нормою.


BOL описує оператор = таким чином: "Порівнює рівність двох виразів (оператор порівняння)." Незалежно від того, правильна чи ні поведінка, слід визнати, що це надзвичайно заплутано та нестандартно з точки зору використання цього оператора в більшості мов програмування. Держава-член повинна принаймні додати попередження до документації щодо такої поведінки.
JohnFx,

@JohnFx: Дивіться мою занадто довгу відповідь на коментарі.
Steve Kass

9

Я знайшов цю статтю в блозі, яка описує поведінку та пояснює, чому.

Стандарт SQL вимагає, щоб порівняння рядків фактично заповнювало коротший рядок пробілами. Це призводить до дивовижного результату, що N '' = N '' (порожній рядок дорівнює рядку з одного або декількох пробільних символів) і, загальніше, будь-який рядок дорівнює іншому рядку, якщо вони відрізняються лише кінцевими пробілами. У деяких ситуаціях це може бути проблемою.

Додаткова інформація також доступна в MSKB316626


Дякую. Мене дивує, що це в стандарті. Я впевнений, що хтось набагато розумніший за мене мав для цього вагому причину.
jhale

@ Джон: ти мав на увазі написати ≠ (не рівне) у своєму коментарі?
Steve Kass

В оригінальній цитаті була помилка, яку я скопіював безпосередньо. Я оновив цитату, щоб відобразити, що мав на увазі оригінальний автор.
JohnFx,

5

Нещодавно було подібне запитання, де я розглядав подібну проблему тут

Замість LEN(' '), використовуйте DATALENGTH(' ')- це дає вам правильне значення.

Рішення полягали у використанні LIKEречення, як пояснено в моїй відповіді, та / або включенні другої умови в WHEREпункт для перевірки DATALENGTH.

Прочитайте це питання та посилання там.


3

Щоб порівняти значення з буквальним пробілом, ви можете також використовувати цей прийом як альтернативу оператору LIKE:

IF ASCII('') = 32 PRINT 'equal' ELSE PRINT 'not equal'

0

Як відрізнити записи на select за допомогою полів char / varchar на сервері sql: приклад:

declare @mayvar as varchar(10)

set @mayvar = 'data '

select mykey, myfield from mytable where myfield = @mayvar

очікуваний

mykey (int) | myfield (varchar10)

1 | 'дані'

отримані

mykey | myfield

1 | 'дані' 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

Інший спосіб - повернути його у стан, який має простір. наприклад: замінити пробіл символом, відомим як _

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

повертає: не дорівнює

Не ідеально, і, мабуть, повільно, але це ще один швидкий шлях вперед, коли потрібно швидко.


0

Іноді доводиться мати справу з пробілами в даних, з будь-якими іншими символами або без них, хоча ідея використання Null є кращою - але не завжди придатною для використання. Я натрапив на описану ситуацію і вирішив її таким чином:

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

Звичайно, ви не зробите цього для великого обсягу даних, але це працює швидко і просто для кількох сотень рядків ...


1
Питання полягало в тому, чому SQL-сервер поводився так, як поводився, а не в тому, як поводитися з такою поведінкою загалом. jhale, мабуть, волів би не змінювати свій програмний код, а лише конфігурацію сервера.
Lutz Prechelt
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.