Пустая строка в 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
Майо,
1
То
1
Это интересный вопрос. Кажется, что возвращается равное, независимо от того, сколько пробелов вы вставили в любую строку, совпадают они или нет. После дополнительных экспериментов я заметил, что он эффективно выполняет RTRIM с обеих сторон оператора равенства перед сравнением. Похоже, вы получили ответ на функцию LEN, но меня действительно интересует более подробный ответ, чем «varchars и равенство в TSQ» для части вашего вопроса о равенстве.
JohnFx,
Я считаю, что Oracle тоже так поступает.
quillbreaker
В общем, я считаю, что хранить пустую строку - плохая идея, и это одна из причин. Я предпочитаю использовать Null и нахожу много проблем, когда люди пытаются преобразовать нулевую информацию в такое значение, как пустая строка или выход данных за пределы нормального диапазона.
HLGEM

Ответы:

90

varchars и равенство тернисты в TSQL. LENФункция говорит:

Возвращает количество символов, а не количество байтов заданного строкового выражения, за исключением конечных пробелов .

Вам нужно использовать, DATALENGTHчтобы получить истинное byteколичество рассматриваемых данных. Если у вас есть данные в Юникоде, обратите внимание, что значение, которое вы получите в этой ситуации, не будет совпадать с длиной текста.

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. Функции ВПРАВО и ВЛЕВО демонстрируют аналогичное поведение, но не задокументированы. Кажется, что это буквальный символ с пробелом, который вызывает проблему. Я заметил, что это также возвращает равное: if '' = SPACE (1) print 'equal' else print 'not equal' Мне не очень интересно получать истинную длину, я просто был сбит с толку, когда я искал пространство в столбец, все столбцы, которые были пустыми строками, были возвращены.
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 - это «количество символов в слове / фразе». Никакие параметры сортировки не рассматривают завершающие пробелы как часть предшествующего им слова / фразы (хотя они рассматривают начальные пробелы как часть предшествующей им строки).

Если вам нужно отличить «это» от «это», не следует использовать оператор «это одно и то же слово или фраза», потому что «это» и «это» - одно и то же слово.

Способствовать = works является идея о том, что оператор равенства строк должен зависеть от содержимого своих аргументов и от контекста сопоставления выражения, но он не должен зависеть от типов аргументов, если они оба являются строковыми типами. .

Концепция естественного языка «это одно и то же слово» обычно недостаточно точна, чтобы ее можно было уловить математическим оператором, таким как =, и в естественном языке нет концепции строкового типа. Контекст (то есть сопоставление) имеет значение (и существует на естественном языке) и является частью истории, а дополнительные свойства (некоторые, которые кажутся причудливыми) являются частью определения =, чтобы сделать его четко определенным в неестественном мире данные.

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

Ответ на комментарий JohnFx:

См. Раздел Использование данных char и varchar в электронной документации. Цитата с этой страницы, акцент мой:

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

Я согласен, что это было бы проще найти, но это задокументировано.

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

На мой взгляд, несправедливо критиковать SQL за то, что он отличается от «большинства языков программирования». SQL был разработан для поддержки структуры моделирования бизнес-данных, которая сильно отличается от инженерной, поэтому язык другой (и лучше для его цели).

Черт возьми, когда SQL был впервые указан, некоторые языки не имели встроенного строкового типа. И в некоторых языках по-прежнему оператор равенства между строками вообще не сравнивает символьные данные, а сравнивает ссылки! Меня не удивит, если через десять-два десятилетия идея, что == зависит от культуры, станет нормой.

Стив Касс
источник
BOL описывает оператор = следующим образом: «Сравнивает равенство двух выражений (оператор сравнения)». Независимо от того, является ли поведение правильным или нет, вы должны признать, что оно чрезвычайно сбивает с толку и нестандартно с точки зрения использования этого оператора в большинстве языков программирования. MS должна хотя бы добавить в документацию предупреждение об этом поведении.
JohnFx
@JohnFx: См. Мой ответ "слишком длинный для комментария".
Стив Касс,
9

Я нашел эту статью в блоге, в которой описывается поведение и объясняется, почему.

Стандарт SQL требует, чтобы сравнения строк эффективно дополняли более короткую строку пробелами. Это приводит к удивительному результату: N '' = N '' (пустая строка равна строке из одного или нескольких пробелов) и вообще любая строка равна другой строке, если они отличаются только конечными пробелами. В некоторых случаях это может быть проблемой.

Дополнительная информация также доступна в MSKB316626

JohnFx
источник
Благодарю. Я удивлен, что это в стандарте. Я уверен, что у кого-то намного умнее меня была на то веская причина.
jhale
@John: вы хотели написать ≠ (не равно) в своем комментарии?
Стив Касс,
В исходной цитате была ошибка, которую я скопировал напрямую. Я обновил цитату, чтобы отразить то, что имел в виду первоначальный автор.
JohnFx
5

Некоторое время назад был похожий вопрос, где я рассмотрел аналогичную проблему здесь

Вместо LEN(' ')использования DATALENGTH(' ')- это дает вам правильное значение.

Решения заключались в том, чтобы использовать LIKEпредложение, как объяснено в моем ответе, и / или включить второе условие в WHEREпредложение для проверки DATALENGTH.

Прочтите этот вопрос и ссылки там.

AdaTheDev
источник
3

Чтобы сравнить значение с буквальным пространством, вы также можете использовать этот метод в качестве альтернативы оператору LIKE:

IF ASCII('') = 32 PRINT 'equal' ELSE PRINT 'not equal'
Дэвид Дж.
источник
0

Как различать записи при выборе с полями 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'

доходность: не равно

Не идеально и, вероятно, медленно, но это еще один быстрый путь вперед, когда нужно быстро.

CooPzZ
источник
0

Иногда приходится иметь дело с пробелами в данных, с любыми другими символами или без них, даже если идея использования Null лучше, но не всегда применима. Я столкнулся с описанной ситуацией и решил ее следующим образом:

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

Конечно, вы бы не сделали этого для большого количества данных, но это работает быстро и легко для нескольких сотен строк ...

Герберт
источник
1
Вопрос заключался в том, почему SQL-сервер ведет себя именно так, а не в том, как справиться с таким поведением в целом. jhale, вероятно, предпочел бы не изменять свой программный код, а только конфигурацию своего сервера.
Lutz Prechelt