SQL Server 2008 pusty ciąg a spacja


83

Dziś rano natknąłem się na coś dziwnego i pomyślałem, że przedstawię to do komentarza.

Czy ktoś może wyjaśnić, dlaczego następujące zapytanie SQL wyświetla „równe”, gdy jest uruchamiane względem SQL 2008. Poziom zgodności db jest ustawiony na 100.

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

A to zwraca 0:

select (LEN(' '))

Wygląda na to, że automatycznie przycina przestrzeń. Nie mam pojęcia, czy tak było w poprzednich wersjach programu SQL Server i nie mam już w pobliżu miejsca, aby to przetestować.

Wpadłem na to, ponieważ zapytanie produkcyjne zwracało nieprawidłowe wyniki. Nigdzie nie mogę znaleźć udokumentowanego tego zachowania.

Czy ktoś ma jakieś informacje na ten temat?


2
SQL 2005: select len ​​('') zwraca 0
Mayo

1
Robi to samo na Sql Server 2000.
Pierre-Alain Vigeant

1
To fascynujące pytanie. Wygląda na to, że zwraca równe bez względu na to, ile spacji umieścisz w dowolnym ciągu, niezależnie od tego, czy pasują, czy nie. Po dalszych eksperymentach zauważyłem, że przed porównaniem skutecznie wykonuje RTRIM po obu stronach operatora równości. Wygląda na to, że masz odpowiedź na temat funkcji LEN, ale naprawdę interesuje mnie dokładniejsza odpowiedź niż „varchary i równość są cierniste w TSQ” w części pytania dotyczącej równości.
JohnFx

Wierzę, że Oracle też to robi.
quillbreaker

Ogólnie uważam, że przechowywanie pustego ciągu to zły pomysł i to jest jeden z powodów. Wolę używać wartości Null i napotykam wiele problemów, gdy ludzie próbują przekształcić informacje o wartości Null w wartość, taką jak pusty ciąg lub dane wykraczające poza normalny zakres.
HLGEM

Odpowiedzi:


89

varchars i równość są drażliwe w TSQL. LENFunkcja mówi:

Zwraca liczbę znaków, a nie liczbę bajtów, danego wyrażenia łańcuchowego, z wyłączeniem końcowych spacji .

Musisz użyć, DATALENGTHaby uzyskać prawdziwą byteliczbę danych, o których mowa. Jeśli masz dane Unicode, zwróć uwagę, że wartość uzyskana w tej sytuacji nie będzie taka sama jak długość tekstu.

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

Jeśli chodzi o równość wyrażeń, dwa ciągi są porównywane pod kątem równości w następujący sposób:

  • Uzyskaj krótszy ciąg
  • Pad ze spacjami aż długość będzie równa długości dłuższego sznurka
  • Porównaj oba

To środkowy krok powoduje nieoczekiwane wyniki - po tym kroku skutecznie porównujesz białe spacje z białymi znakami - stąd są one postrzegane jako równe.

LIKEzachowuje się lepiej niż =w sytuacji "spacji", ponieważ nie wykonuje wypełniania pustymi znakami na wzorcu, który próbujesz dopasować:

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

Daje eqpodczas:

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

Da ne

Uważaj LIKEjednak: nie jest symetryczny: traktuje końcowe białe znaki jako znaczące we wzorcu (RHS), ale nie w wyrażeniu dopasowującym (LHS). Poniższa pochodzi z tutaj :

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
Niezła odpowiedź. Nie zauważyłem tego w dokumentacji LEN. Nie ogranicza się to jednak do LEN. Funkcje PRAWO i LEWO wykazują podobne zachowanie, ale nie jest to udokumentowane. Wydaje się, że jest to dosłowne ze spacją, która powoduje problem. Zauważyłem, że to również zwraca równe: if '' = SPACJA (1) print 'equal' else print 'not equal' Nie jestem zainteresowany uzyskaniem prawdziwej długości, byłem po prostu zdezorientowany, dlaczego, kiedy szukałem spacji w kolumna, zwrócone zostały wszystkie kolumny, które były pustymi ciągami.
jhale

Również miłe informacje o oświadczeniu LIKE. Wydaje mi się, że morał tej historii polega na tym, że staraj się nie stawiać się w sytuacji, w której musisz porównać spację i pusty ciąg.
jhale

2
Problem jest większy niż porównanie spacji z pustym ciągiem. Porównanie dowolnych dwóch ciągów zakończonych różną liczbą spacji wykazuje to samo zachowanie.
JohnFx

3
@butterchicken: Przepraszam za taki koniec postu, po prostu zobaczyłem to pytanie, ale kiedy wpadłem to (ostatni) na moim sql-server-2008 r2mam, @Space Not Like @Space2 @Space2 Not Like @Space . Każdy pomysł, dlaczego?
Razort4x

1
Potwierdzony na SQL Server 2012 i SQL Server 2014, wynik to@Space Not Like @Space2 @Space2 Not Like @Space
Just a learner

19

Operator = to T-SQL to nie tyle „równa się”, ile „to to samo słowo / fraza, zgodnie z zestawieniem kontekstu wyrażenia”, a LEN to „liczba znaków w słowie / frazie”. Żadne sortowania nie traktują końcowych spacji jako części poprzedzającego je słowa / frazy (chociaż traktują początkowe spacje jako część ciągu, który poprzedzają).

Jeśli chcesz odróżnić „to” od „tego”, nie powinieneś używać operatora „to to samo słowo lub fraza”, ponieważ „to” i „to” to to samo słowo.

Przyczyną do tego, jak działa = jest idea, że ​​operator równości ciągów powinien zależeć od zawartości swoich argumentów i kontekstu sortowania wyrażenia, ale nie powinien zależeć od typów argumentów, jeśli oba są typami ciągów .

Pojęcie języka naturalnego „to są to samo słowo” zazwyczaj nie jest wystarczająco precyzyjne, aby można je było przechwycić przez operator matematyczny, taki jak =, aw języku naturalnym nie ma pojęcia typu łańcucha. Kontekst (tj. Zestawienie) ma znaczenie (i istnieje w języku naturalnym) i jest częścią opowieści, a dodatkowe właściwości (niektóre wydają się dziwaczne) są częścią definicji =, aby było dobrze zdefiniowane w nienaturalnym świecie dane.

Jeśli chodzi o typ, nie chciałbyś, aby słowa zmieniały się, gdy są przechowywane w różnych typach ciągów. Na przykład wszystkie typy VARCHAR (10), CHAR (10) i CHAR (3) mogą zawierać reprezentacje słowa „kot” i? = „kot” powinno pozwolić nam zdecydować, czy wartość któregokolwiek z tych typów zawiera słowo „kot” (z uwzględnieniem wielkości liter i akcentu określonych przez porównanie).

Odpowiedź na komentarz JohnFx:

Zobacz Używanie danych char i varchar w Books Online. Cytując z tej strony, podkreśl moje:

Każda wartość danych char i varchar ma sortowanie. Sortowania definiują atrybuty, takie jak wzorce bitowe używane do reprezentowania każdego znaku, reguły porównania i wrażliwość na wielkość liter lub akcentowanie.

Zgadzam się, że mogłoby to być łatwiejsze do znalezienia, ale jest to udokumentowane.

Warto również zauważyć, że semantyka SQL, gdzie = ma związek z rzeczywistymi danymi i kontekstem porównania (w przeciwieństwie do czegoś o bitach przechowywanych na komputerze), była częścią SQL przez długi czas. Założeniem RDBMS i SQL jest wierna reprezentacja rzeczywistych danych, stąd wsparcie dla zestawień na wiele lat przed pojawieniem się podobnych pomysłów (takich jak CultureInfo) do królestwa języków podobnych do Algola. Założeniem tych języków (przynajmniej do niedawna) było rozwiązywanie problemów w inżynierii, a nie zarządzanie danymi biznesowymi. (Ostatnio użycie podobnych języków w aplikacjach niezwiązanych z inżynierią, takich jak wyszukiwanie, ma pewne znaczenie, ale Java, C # itd. Wciąż borykają się z nie-biznesowymi korzeniami).

Moim zdaniem krytykowanie SQL za to, że różni się od „większości języków programowania”, jest niesprawiedliwe. SQL został zaprojektowany do obsługi struktury modelowania danych biznesowych, która jest bardzo różna od inżynierii, więc język jest inny (i lepszy dla jego celu).

Heck, kiedy po raz pierwszy określono SQL, niektóre języki nie miały żadnego wbudowanego typu łańcucha. W niektórych językach operator równości między łańcuchami w ogóle nie porównuje danych znakowych, ale porównuje referencje! Nie zdziwiłbym się, gdyby za następną dekadę lub dwie idea, że ​​== jest zależna od kultury, stanie się normą.


BOL tak opisuje operator =: „Porównuje równość dwóch wyrażeń (operator porównania)”. Niezależnie od tego, czy zachowanie jest poprawne, czy nie, musisz przyznać, że jest bardzo zagmatwane i niestandardowe pod względem użycia tego operatora w większości języków programowania. Firma MS powinna przynajmniej dodać ostrzeżenie do dokumentacji dotyczące tego zachowania.
JohnFx

@JohnFx: Zobacz moją odpowiedź, która jest zbyt długa na komentarz.
Steve Kass

9

Znalazłem ten artykuł na blogu, który opisuje zachowanie i wyjaśnia dlaczego.

Standard SQL wymaga, aby porównania ciągów skutecznie uzupełniały krótszy ciąg znakami spacji. Prowadzi to do zaskakującego wyniku, że N '' = N '' (pusty ciąg równa się ciągowi jednego lub więcej znaków spacji), a bardziej ogólnie każdy ciąg równa się innemu ciągowi, jeśli różnią się tylko spacjami końcowymi. W niektórych sytuacjach może to stanowić problem.

Więcej informacji można również znaleźć w MSKB316626


Dzięki. Dziwię się, że jest w standardzie. Jestem pewien, że ktoś znacznie mądrzejszy ode mnie miał ku temu dobry powód.
jhale

@John: czy chciałeś napisać ≠ (nie równa się) w swoim komentarzu?
Steve Kass

Oryginalny cytat zawierał błąd, który skopiowałem bezpośrednio. Zaktualizowałem cytat, aby odzwierciedlić to, co miał na myśli pierwotny autor.
JohnFx

5

Było podobne pytanie jakiś czas temu, gdzie spojrzałem na podobnym problemem tutaj

Zamiast LEN(' ')używać DATALENGTH(' ')- to daje poprawną wartość.

Rozwiązania miały wykorzystywać LIKEklauzulę, jak wyjaśniono w mojej odpowiedzi, i / lub zawierać również drugi warunek w WHEREklauzuli do sprawdzenia DATALENGTH.

Przeczytaj to pytanie i linki tam.


3

Aby porównać wartość z dosłowną spacją, możesz również użyć tej techniki jako alternatywy dla instrukcji LIKE:

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

0

Jak rozróżniać rekordy przy zaznaczaniu za pomocą pól char / varchar na serwerze sql: przykład:

declare @mayvar as varchar(10)

set @mayvar = 'data '

select mykey, myfield from mytable where myfield = @mayvar

spodziewany

mykey (int) | myfield (varchar10)

1 | „dane”

uzyskane

mykey | moja dziedzina

1 | „dane” 2 | „dane”

nawet jeśli napiszę select mykey, myfield from mytable where myfield = 'data' (bez ostatniej spacji), otrzymuję te same wyniki.

jak rozwiązałem? W tym trybie:

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

a jeśli na myfield istnieje indeks, będzie on używany w każdym przypadku.

Mam nadzieję, że będzie to pomocne.


0

Innym sposobem jest przywrócenie go do stanu, w którym przestrzeń ma wartość. np .: zamień spację na znak znany jak _

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

zwraca: nie równe

Nie jest idealny i prawdopodobnie powolny, ale jest kolejnym szybkim krokiem naprzód, gdy jest potrzebny szybko.


0

Czasami mamy do czynienia ze spacjami w danych, z innymi znakami lub bez nich, nawet jeśli pomysł użycia wartości Null jest lepszy - ale nie zawsze nadaje się do użytku. Napotkałem opisaną sytuację i rozwiązałem ją w ten sposób:

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

Oczywiście nie zrobiłbyś tego dla dużej ilości danych, ale działa to szybko i łatwo dla kilkuset wierszy ...


1
Pytanie brzmiało, dlaczego serwer SQL zachowywał się tak, jak się zachowywał, a nie jak ogólnie radzić sobie z takim zachowaniem. jhale prawdopodobnie wolałby raczej nie modyfikować kodu programu, a jedynie konfigurację serwera.
Lutz Prechelt
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.