Почему SQL Server требует, чтобы длина типа данных была одинаковой при использовании UNPIVOT?

28

При применении UNPIVOTфункции к ненормализованным данным SQL Server требует, чтобы тип данных и длина были одинаковыми. Я понимаю, почему тип данных должен быть одинаковым, но почему UNPIVOT требует одинаковую длину?

Допустим, у меня есть следующие примеры данных, которые мне нужно отключить:

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

Если я попытаюсь UNPIVOT Firstnameи Lastnameстолбцы, похожие на:

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

SQL Server генерирует ошибку:

Сообщение 8167, уровень 16, состояние 1, строка 6

Тип столбца «Фамилия» конфликтует с типом других столбцов, указанных в списке UNPIVOT.

Чтобы устранить ошибку, мы должны использовать подзапрос, чтобы сначала привести Lastnameстолбец такой же длины, как 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;

Смотрите SQL Fiddle с демо

До введения UNPIVOT в SQL Server 2005 я использовал команду SELECTwith, UNION ALLчтобы отключить столбцы firstname/, lastnameи запрос выполнялся без необходимости преобразования столбцов одинаковой длины:

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

Смотрите SQL Fiddle с демонстрацией .

Мы также можем успешно отключить данные, используя CROSS APPLYодинаковую длину для типа данных:

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

Смотрите SQL Fiddle с демонстрацией .

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

Какая логика лежит в основе требования одинаковой длины при использовании UNPIVOT?

Тарын
источник
4
(Возможно, не связанный, но ...) Строгая строгость применяется при сравнении типов столбцов двух частей рекурсивного CTE.
Андрей М

Ответы:

25

Какая логика лежит в основе требования одинаковой длины при использовании UNPIVOT?

Этот вопрос может быть действительно ответственным только перед людьми, которые работали над реализацией UNPIVOT. Вы можете получить это, связавшись с ними для поддержки . Следующее - мое понимание рассуждений, которые могут быть не точными на 100%:


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

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

Тем не менее, нет веских оснований для того, чтобы не вносить улучшения и избегать прошлых ошибок при внедрении новых языковых функций (без явного багажа обратной совместимости). Новые функции, такие как рекурсивные общие табличные выражения (как упомянуто Андреем М в комментарии), UNPIVOTбыли свободны иметь относительно нормальную семантику и четко определенные правила.

Будут высказаны разные мнения о том, что включение длины в тип слишком далеко заходит при явной типизации, но лично я приветствую это. На мой взгляд, типы varchar(25)и varchar(50)являются не то же самое, больше чем decimal(8)и decimal(10)есть. Специальное преобразование типа строки в корпусе усложняет вещи без необходимости и, на мой взгляд, не добавляет никакой реальной ценности.

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

Если бы неявное преобразование из varchar(25)в varchar(50)было разрешено, это было бы просто другое (скорее всего скрытое) неявное преобразование со всеми обычными странными крайними случаями и SETустановкой чувствительности. Почему бы не сделать реализацию максимально простой и понятной? (Тем не менее, нет ничего идеального, и это позор, что прятаться varchar(25)и varchar(50)внутри sql_variantможно.)

Переписывая UNPIVOTс APPLYи UNION ALLизбегая (лучшего) поведения типов, потому что правила для UNIONних подлежат обратной совместимости и документированы в Books Online как разрешающие разные типы, если они сравнимы с использованием неявного преобразования (для которого тайные правила приоритета типа данных используются и так далее).

Обходной путь заключается в явном указании типов данных и добавлении явных преобразований при необходимости. Это похоже на прогресс для меня :)

Один способ написать явно типизированный обходной путь:

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;

Пример рекурсивного 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;

Наконец, обратите внимание, что использование переписывания CROSS APPLYв вопросе не совсем то же самое UNPIVOT, что и использование NULLатрибутов.

Пол Уайт говорит, что GoFundMonica
источник
1

UNPIVOTОператор использует INоператор. В спецификации для оператора IN (скриншот ниже) показывает , что оба test_expression(в данном случае, на слева от IN) и каждый expression(на правой стороне IN) должен быть тем же типом данных. Благодаря транзитивному свойству равенства каждое выражение также должно иметь одинаковый тип данных.

введите описание изображения здесь

dev_etter
источник
Правильно, я понимаю требование к типу данных, но вопрос в том, почему длина должна быть одинаковой.
Тарын
Я упустил это из виду, и да, оператор IN обычно не заботится о длине.
dev_etter
Альтернативой, позволяющей упустить из виду необходимость указания длины, является приведение каждого к SQL_Variant: sqlfiddle.com/#!3/13b9a/2/0
dev_etter