Warum erfordert SQL Server, dass die Datentyplänge bei Verwendung von UNPIVOT gleich ist?

28

Wenn Sie die UNPIVOTFunktion auf Daten anwenden, die nicht normalisiert sind, muss der Datentyp und die Länge in SQL Server identisch sein. Ich verstehe, warum der Datentyp gleich sein muss, aber warum erfordert UNPIVOT, dass die Länge gleich ist?

Angenommen, ich habe die folgenden Beispieldaten, die ich zum Deaktivieren benötige:

CREATE TABLE People
(
    PersonId int, 
    Firstname varchar(50), 
    Lastname varchar(25)
)

INSERT INTO People VALUES (1, 'Jim', 'Smith');
INSERT INTO People VALUES (2, 'Jane', 'Jones');
INSERT INTO People VALUES (3, 'Bob', 'Unicorn');

Wenn ich versuche, die Spalten Firstnameund zu UNPIVOTEN Lastname, ähnlich wie in:

select PersonId, ColumnName, Value  
from People
unpivot
(
  Value 
  FOR ColumnName in (FirstName, LastName)
) unpiv;

SQL Server generiert den Fehler:

Nachricht 8167, Ebene 16, Status 1, Zeile 6

Der Spaltentyp "Nachname" steht in Konflikt mit dem Typ anderer Spalten, die in der UNPIVOT-Liste angegeben sind.

Um den Fehler zu beheben, müssen wir zuerst eine Unterabfrage verwenden, um die LastnameSpalte mit der folgenden Länge zu konvertieren Firstname:

select PersonId, ColumnName, Value  
from
(
  select personid, 
    firstname, 
    cast(lastname as varchar(50)) lastname
  from People
) d
unpivot
(
  Value FOR 
  ColumnName in (FirstName, LastName)
) unpiv;

Siehe SQL Fiddle with Demo

Bevor UNPIVOT in SQL Server 2005 eingeführt wurde, würde ich ein SELECTmit verwenden, UNION ALLum das Pivot der firstname/ lastname-Spalten aufzuheben , und die Abfrage würde ausgeführt, ohne dass die Spalten in dieselbe Länge konvertiert werden müssen:

select personid, 'firstname' ColumnName, firstname value
from People
union all
select personid, 'LastName', LastName
from People;

Siehe SQL Fiddle with Demo .

Wir sind auch in der Lage, die Pivot-Funktion der Daten erfolgreich zu deaktivieren, CROSS APPLYohne die gleiche Länge für den Datentyp zu haben:

select PersonId, columnname, value
from People
cross apply
(
    select 'firstname', firstname union all
    select 'lastname', lastname
) c (columnname, value);

Siehe SQL Fiddle with Demo .

Ich habe MSDN durchgelesen, aber ich habe keine Erklärung gefunden, warum die Länge des Datentyps gleich ist.

Welche Logik steckt dahinter, wenn bei UNPIVOT dieselbe Länge erforderlich ist?

Taryn
quelle
4
(Möglicherweise unabhängig, aber ...) Dieselbe Strenge wird beim Vergleich der Spaltentypen der beiden Teile eines rekursiven CTE angewendet.
Andriy M

Antworten:

25

Welche Logik steckt dahinter, wenn bei UNPIVOT dieselbe Länge erforderlich ist?

Diese Frage kann nur von den Personen wirklich beantwortet werden, die an der Umsetzung von gearbeitet haben UNPIVOT. Sie können dies möglicherweise erhalten, indem Sie sich an sie wenden, um Unterstützung zu erhalten . Das Folgende ist mein Verständnis der Argumentation, die möglicherweise nicht 100% genau ist:


T-SQL enthält eine beliebige Anzahl von Instanzen seltsamer Semantik und anderer kontraintuitiver Verhaltensweisen. Einige von ihnen werden irgendwann als Teil der Abschreibungszyklen verschwinden, andere werden jedoch möglicherweise nie "verbessert" oder "behoben". Abgesehen von allem anderen gibt es Anwendungen, die von diesem Verhalten abhängen. Daher muss die Abwärtskompatibilität erhalten bleiben.

Die Regeln für implizite Konvertierungen und die Ableitung von Ausdruckstypen machen einen erheblichen Teil der oben genannten Verrücktheit aus. Ich beneide die Tester nicht, die sicherstellen müssen, dass das seltsame (und oft nicht dokumentierte) Verhalten (unter allen Kombinationen von SETSitzungswerten usw.) für neue Versionen beibehalten wird .

Trotzdem gibt es keinen guten Grund, bei der Einführung neuer Sprachfunktionen (mit offensichtlich keinem Gepäck für die Abwärtskompatibilität) keine Verbesserungen vorzunehmen und frühere Fehler zu vermeiden. Neue Funktionen wie rekursive allgemeine Tabellenausdrücke (wie von Andriy M in einem Kommentar erwähnt) und UNPIVOTrelativ gesunde Semantik und klar definierte Regeln.

Es wird eine Reihe von Ansichten darüber geben, ob die Einbeziehung der Länge in den Typ eine zu weit gehende explizite Eingabe erfordert, aber ich persönlich begrüße dies. Aus meiner Sicht sind die Typen varchar(25)und varchar(50)sind nicht die gleichen, sowenig decimal(8)und decimal(10)sind. Eine spezielle Konvertierung von Zeichenfolgen in Groß- und Kleinschreibung erschwert die Arbeit unnötig und bietet meiner Meinung nach keinen wirklichen Mehrwert.

Man könnte argumentieren, dass nur implizite Konvertierungen, die Daten verlieren könnten, explizit angegeben werden müssen, aber es gibt auch Randfälle. Letztendlich wird eine Konvertierung erforderlich sein, die wir genauso gut explizit machen können.

Wenn die implizite Konvertierung von varchar(25)nach varchar(50)erlaubt wäre, wäre es nur eine andere (höchstwahrscheinlich verborgene) implizite Konvertierung mit all den üblichen seltsamen Randfällen und dem SETSetzen von Empfindlichkeiten. Warum nicht die Implementierung so einfach und explizit wie möglich gestalten? (Nichts ist perfekt, aber, und es ist eine Schande , dass versteckt varchar(25)und im varchar(50)Innern eines sql_varianterlaubt ist.)

Das Umschreiben von UNPIVOTwith APPLYund UNION ALLvermeidet das (bessere) Typverhalten, da die Regeln für UNIONdie Abwärtskompatibilität unterliegen und in der Onlinedokumentation so dokumentiert sind, dass sie verschiedene Typen zulassen, sofern sie mit impliziter Konvertierung vergleichbar sind (für die die arkanen Regeln des Datentyps Vorrang haben) verwendet werden, und so weiter).

Die Problemumgehung besteht darin, die Datentypen explizit anzugeben und bei Bedarf explizite Konvertierungen hinzuzufügen. Das sieht für mich nach Fortschritt aus :)

Eine Möglichkeit, die explizit eingegebene Problemumgehung zu schreiben:

SELECT
    U.PersonId,
    U.ColumnName,
    U.Value
FROM dbo.People AS P
CROSS APPLY
(
    VALUES (CONVERT(varchar(50), Lastname))
) AS CA (Lastname)
UNPIVOT
(
    Value FOR
    ColumnName IN (P.Firstname, CA.Lastname)
) AS U;

Beispiel für einen rekursiven CTE:

-- Fails
WITH R AS
(
    SELECT Dummy = 'A row'
    UNION ALL
    SELECT 'Another row'
    FROM R
    WHERE Dummy = 'A row'
)
SELECT Dummy
FROM R;

-- Succeeds
WITH R AS
(
    SELECT Dummy = CONVERT(varchar(11), 'A row')
    UNION ALL
    SELECT CONVERT(varchar(11), 'Another row')
    FROM R
    WHERE Dummy = 'A row'
)
SELECT Dummy
FROM R;

Beachten Sie schließlich, dass das Umschreiben CROSS APPLYin der Frage nicht ganz mit dem in der Frage übereinstimmt UNPIVOT, da NULLAttribute nicht abgelehnt werden.

Paul White sagt GoFundMonica
quelle
1

Der UNPIVOTBediener nutzt den INBediener. Die Angaben für den IN-Operator (Abbildung unten) geben an, dass sowohl der test_expression(in diesem Fall der linke IN) als auch der expression(der rechte IN) Datentyp identisch sein müssen. Aufgrund der transitiven Eigenschaft der Gleichheit muss jeder Ausdruck ebenfalls vom gleichen Datentyp sein.

Bildbeschreibung hier eingeben

dev_etter
quelle
Richtig, ich verstehe die Datentypanforderung, aber die Frage ist, warum die Länge gleich sein muss.
Taryn
Ich habe das übersehen und ja, der IN-Operator kümmert sich normalerweise nicht um die Länge.
dev_etter
Eine Alternative, die es Ihnen ermöglicht, die Notwendigkeit der Angabe der Länge zu übersehen, besteht darin, sie als SQL_Variant umzuwandeln: sqlfiddle.com/#!3/13b9a/2/0
dev_etter