При применении 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 я использовал команду SELECT
with, 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?
источник
Ответы:
Этот вопрос может быть действительно ответственным только перед людьми, которые работали над реализацией
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 как разрешающие разные типы, если они сравнимы с использованием неявного преобразования (для которого тайные правила приоритета типа данных используются и так далее).Обходной путь заключается в явном указании типов данных и добавлении явных преобразований при необходимости. Это похоже на прогресс для меня :)
Один способ написать явно типизированный обходной путь:
Пример рекурсивного CTE:
Наконец, обратите внимание, что использование переписывания
CROSS APPLY
в вопросе не совсем то же самоеUNPIVOT
, что и использованиеNULL
атрибутов.источник
UNPIVOT
Оператор используетIN
оператор. В спецификации для оператора IN (скриншот ниже) показывает , что обаtest_expression
(в данном случае, на слева отIN
) и каждыйexpression
(на правой сторонеIN
) должен быть тем же типом данных. Благодаря транзитивному свойству равенства каждое выражение также должно иметь одинаковый тип данных.источник