Perché SQL Server richiede che la lunghezza del tipo di dati sia la stessa quando si utilizza UNPIVOT?


28

Quando si applica la UNPIVOTfunzione a dati non normalizzati, SQL Server richiede che il tipo di dati e la lunghezza siano gli stessi. Capisco perché il tipo di dati deve essere lo stesso, ma perché UNPIVOT richiede che la lunghezza sia la stessa?

Diciamo che ho i seguenti dati di esempio che devo annullare la rotazione:

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');

Se provo a UNPIVOT le colonne Firstnamee Lastnamesimili a:

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

SQL Server genera l'errore:

Messaggio 8167, livello 16, stato 1, riga 6

Il tipo di colonna "Cognome" è in conflitto con il tipo di altre colonne specificato nell'elenco UNPIVOT.

Per risolvere l'errore, è necessario utilizzare una sottoquery per eseguire innanzitutto il cast della Lastnamecolonna per avere la stessa lunghezza di 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;

Vedi SQL Fiddle with Demo

Prima che UNPIVOT fosse introdotto in SQL Server 2005, avrei usato un SELECTcon UNION ALLper annullare la rotazione delle colonne firstname/ lastnamee la query sarebbe stata eseguita senza la necessità di convertire le colonne alla stessa lunghezza:

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

Vedi SQL Fiddle with Demo .

Siamo anche in grado di annullare la rotazione dei dati utilizzando CROSS APPLYsenza avere la stessa lunghezza sul tipo di dati:

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

Vedi SQL Fiddle with Demo .

Ho letto tramite MSDN ma non ho trovato nulla che spiegasse il ragionamento per forzare la lunghezza sul tipo di dati a essere la stessa.

Qual è la logica che richiede la stessa lunghezza quando si utilizza UNPIVOT?


4
(Forse non correlato ma ...) La stessa rigidità viene applicata quando si confrontano i tipi di colonna delle due parti di un CTE ricorsivo.
Andriy M,

Risposte:


25

Qual è la logica che richiede la stessa lunghezza quando si utilizza UNPIVOT?

Questa domanda può essere veramente rispondente solo alle persone che hanno lavorato all'implementazione di UNPIVOT. Potresti riuscire a ottenerlo contattandoli per il supporto . Quanto segue è la mia comprensione del ragionamento, che potrebbe non essere preciso al 100%:


T-SQL contiene un numero qualsiasi di istanze di semantica bizzarra e altri comportamenti controintuitivi. Alcuni di questi alla fine andranno via come parte dei cicli di deprecazione, ma altri potrebbero non essere mai "migliorati" o "riparati". A parte qualsiasi altra cosa, esistono applicazioni che dipendono da questi comportamenti, quindi è necessario preservare la retrocompatibilità.

Le regole per le conversioni implicite e la derivazione del tipo di espressione rappresentano una parte significativa della stranezza sopra menzionata. Non invidio i tester che devono garantire che i comportamenti strani (e spesso privi di documenti) vengano preservati (sotto tutte le combinazioni di SETvalori di sessione e così via) per le nuove versioni.

Detto questo, non vi è alcuna buona ragione per non apportare miglioramenti ed evitare errori passati quando si introducono nuove funzionalità linguistiche (senza ovviamente un bagaglio di compatibilità con le versioni precedenti). Nuove funzionalità come espressioni di tabelle comuni ricorsive (come menzionato da Andriy M in un commento) ed UNPIVOTerano libere di avere semantiche relativamente sane e regole chiaramente definite.

Ci sarà una serie di opinioni sul fatto che includere la lunghezza nel tipo stia spingendo troppo oltre la digitazione esplicita, ma personalmente lo accolgo con favore. A mio avviso, i tipi varchar(25)e nonvarchar(50) sono gli stessi, non più di e lo sono. La conversione del tipo di stringa di involucro speciale complica inutilmente le cose e non aggiunge alcun valore reale, secondo me.decimal(8)decimal(10)

Si potrebbe sostenere che solo le conversioni implicite che potrebbero perdere dati dovrebbero essere dichiarate esplicitamente, ma ci sono anche casi limite. Alla fine, sarà necessaria una conversione, quindi potremmo anche renderla esplicita.

Se fosse consentita la conversione implicita da varchar(25)in varchar(50), sarebbe solo un'altra conversione implicita (molto probabilmente nascosta), con tutti i soliti casi bizzarri e la SETsensibilità di impostazione. Perché non rendere l'implementazione il più semplice ed esplicita possibile? (Niente è perfetto, tuttavia, ed è un peccato che nascondersi varchar(25)e varchar(50)dentro a sql_variantsia permesso.)

Riscrivere il UNPIVOTcon APPLYed UNION ALLevita il comportamento (migliore) del tipo perché le regole per UNIONsono soggette a compatibilità con le versioni precedenti e sono documentate nella documentazione online come autorizzare tipi diversi purché siano comparabili utilizzando la conversione implicita (per la quale le regole arcane di tipo di dati hanno la precedenza vengono utilizzati e così via).

La soluzione alternativa consiste nell'essere espliciti sui tipi di dati e nell'aggiungere conversioni esplicite ove necessario. Questo mi sembra un progresso :)

Un modo per scrivere la soluzione alternativa digitata in modo esplicito:

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;

Esempio CTE ricorsivo:

-- 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;

Infine, nota che la riscrittura usando CROSS APPLYnella domanda non è esattamente la stessa di UNPIVOT, perché non rifiuta gli NULLattributi.


1

L' UNPIVOToperatore utilizza l' INoperatore. Le specifiche per l'operatore IN (screenshot seguente) indicano che sia test_expression(in questo caso, a sinistra di IN) sia ciascuno expression(a destra IN) devono essere dello stesso tipo di dati. Grazie alla proprietà transitiva dell'uguaglianza, anche ogni espressione deve essere dello stesso tipo di dati.

inserisci qui la descrizione dell'immagine


Bene, capisco il requisito del tipo di dati, ma la domanda è: perché la lunghezza deve essere la stessa.
Taryn

L'ho trascurato e sì, l'operatore IN di solito non si preoccupa della lunghezza.
dev_etter,

Un'alternativa che ti consente di trascurare la necessità di specificare la lunghezza è di lanciare ciascuno come SQL_Variant: sqlfiddle.com/#!3/13b9a/2/0
dev_etter
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.