Pourquoi SQL Server requiert-il que la longueur du type de données soit la même lors de l'utilisation d'UNPIVOT?

28

Lors de l'application de la UNPIVOTfonction à des données qui ne sont pas normalisées, SQL Server requiert que le type de données et la longueur soient identiques. Je comprends pourquoi le type de données doit être le même, mais pourquoi UNPIVOT exige-t-il que la longueur soit la même?

Disons que j'ai les exemples de données suivants que je dois débloquer:

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

Si j'essaye de UNPIVOT les colonnes Firstnameet Lastnamesimilaires à:

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

SQL Server génère l'erreur:

Msg 8167, niveau 16, état 1, ligne 6

Le type de colonne "Nom" est en conflit avec le type des autres colonnes spécifiées dans la liste UNPIVOT.

Afin de résoudre l'erreur, nous devons utiliser une sous-requête pour lancer d'abord la Lastnamecolonne pour avoir la même longueur que 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;

Voir SQL Fiddle avec démo

Avant d'introduire UNPIVOT dans SQL Server 2005, j'utilisais un SELECTavec UNION ALLpour annuler le pivotement des colonnes firstname/ lastnameet la requête s'exécuterait sans avoir besoin de convertir les colonnes à la même longueur:

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

Voir SQL Fiddle avec démo .

Nous sommes également en mesure de débloquer les données avec succès CROSS APPLYsans avoir la même longueur sur le type de données:

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

Voir SQL Fiddle avec démo .

J'ai lu MSDN mais je n'ai rien trouvé qui explique le raisonnement pour forcer la longueur du type de données à être la même.

Quelle est la logique derrière l'exigence de la même longueur lors de l'utilisation d'UNPIVOT?

Taryn
la source
4
(Peut-être sans rapport mais ...) La même rigueur est appliquée lors de la comparaison des types de colonnes des deux parties d'un CTE récursif.
Andriy M

Réponses:

25

Quelle est la logique derrière l'exigence de la même longueur lors de l'utilisation d'UNPIVOT?

Cette question ne peut être véritablement répondue que par les personnes qui ont travaillé à la mise en œuvre de UNPIVOT. Vous pourrez peut-être l'obtenir en les contactant pour obtenir de l'aide . Ce qui suit est ma compréhension du raisonnement, qui peut ne pas être exact à 100%:


T-SQL contient un nombre illimité d'instances de sémantique étrange et d'autres comportements contre-intuitifs. Certains d'entre eux finiront par disparaître dans le cadre des cycles de dépréciation, mais d'autres pourraient ne jamais être «améliorés» ou «fixés». En dehors de toute autre chose, il existe des applications qui dépendent de ces comportements, donc la compatibilité descendante doit être préservée.

Les règles relatives aux conversions implicites et à la dérivation du type d'expression représentent une proportion importante de l'étrangeté mentionnée ci-dessus. Je n'envie pas les testeurs qui doivent s'assurer que les comportements étranges (et souvent non documentés) sont préservés (sous toutes les combinaisons de SETvaleurs de session et ainsi de suite) pour les nouvelles versions.

Cela dit, il n'y a aucune bonne raison de ne pas apporter d'améliorations et d'éviter les erreurs passées lors de l'introduction de nouvelles fonctionnalités de langage (sans évidemment aucun bagage de compatibilité descendante). De nouvelles fonctionnalités comme les expressions de table communes récursives (comme mentionné par Andriy M dans un commentaire) et UNPIVOTétaient libres d'avoir une sémantique relativement saine et des règles clairement définies.

Il y aura un large éventail de vues pour savoir si l'inclusion de la longueur dans le type va trop loin dans la saisie explicite, mais personnellement je m'en réjouis. À mon avis, les types varchar(25)et nevarchar(50) sont pas les mêmes, pas plus que decimal(8)et ne le decimal(10)sont. La conversion spéciale de type chaîne de boîtier complique les choses inutilement et n'ajoute aucune valeur réelle, à mon avis.

On pourrait faire valoir que seules les conversions implicites susceptibles de perdre des données devraient être explicitement énoncées, mais il existe également des cas limites. En fin de compte, une conversion va être nécessaire, alors nous pourrions aussi bien la rendre explicite.

Si la conversion implicite de varchar(25)à varchar(50)était autorisée, ce ne serait qu'une autre conversion implicite (très probablement cachée), avec tous les cas de bord étranges habituels et les SETsensibilités définies. Pourquoi ne pas rendre l'implémentation la plus simple et la plus explicite possible? (Cependant, rien n'est parfait et c'est dommage que se cacher varchar(25)et à l' varchar(50)intérieur d'un sql_variantsoit autorisé.)

La réécriture de UNPIVOTwith APPLYet UNION ALLévite le comportement de type (meilleur) car les règles de UNIONsont sujettes à une compatibilité descendante et sont documentées dans la documentation en ligne comme autorisant différents types tant qu'elles sont comparables à l'aide de la conversion implicite (pour lesquelles les règles obscures de priorité des types de données sont utilisés, etc.).

La solution de contournement consiste à être explicite sur les types de données et à ajouter des conversions explicites si nécessaire. Cela ressemble à un progrès pour moi :)

Une façon d'écrire la solution de contournement explicitement typée:

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;

Exemple CTE récursif:

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

Enfin, notez que la réécriture à l'aide CROSS APPLYde la question n'est pas tout à fait la même que la UNPIVOT, car elle ne rejette pas les NULLattributs.

Paul White dit GoFundMonica
la source
1

L' UNPIVOTopérateur utilise l' INopérateur. Les spécifications de l'opérateur IN (capture d'écran ci-dessous) indiquent que le test_expression(dans ce cas, à gauche de IN) et chacun expression(à droite de IN) doivent être du même type de données. Grâce à la propriété transitive d'égalité, chaque expression doit également être du même type de données.

entrez la description de l'image ici

dev_etter
la source
À droite, je comprends l'exigence de type de données, mais la question est de savoir pourquoi la longueur doit être la même.
Taryn
J'ai oublié cela, et oui, l'opérateur IN ne se soucie généralement pas de la longueur.
dev_etter
Une alternative qui vous permet de passer outre la nécessité de spécifier la longueur est de convertir chacun en SQL_Variant: sqlfiddle.com/#!3/13b9a/2/0
dev_etter