Чому 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 генерує помилку:

Msg 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 with Demo

До UNPIVOT вводиться в SQL Server 2005, я б використовувати SELECTз , UNION ALLщоб UNPIVOT в firstname/ lastnameстовпців і запит буде виконуватися без необхідності перетворення стовпців в одній і тій же довжини:

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

Див. SQL Fiddle with Demo .

Ми також можемо успішно розкручувати дані, використовуючи CROSS APPLYбез однакової довжини на тип даних:

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

Див. SQL Fiddle with Demo .

Я прочитав 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атрибути.


1

UNPIVOTОператор використовує INоператор. У специфікації для оператора IN (скріншот нижче) показує , що обидва test_expression(в даному випадку, на зліва від IN) і кожен expression(на правій стороні IN) повинен бути тим же типом даних. Завдяки перехідній властивості рівності, кожен вираз повинен бути і одного типу даних.

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


Правильно, я розумію вимогу типу даних, але питання полягає в тому, чому довжина повинна бути однаковою.
Taryn

Я не помітив цього, і так, оператор IN, як правило, не хвилює довжину.
dev_etter

Альтернативою, яка дозволяє не помітити необхідність вказувати довжину, є передавання кожного як SQL_Variant: sqlfiddle.com/#!3/13b9a/2/0
dev_etter
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.