为什么在使用UNPIVOT时SQL Server要求数据类型长度相同?


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

如果我尝试取消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

在SQL Server 2005中引入UNPIVOT之前,我将使用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的两个部分的列类型时,将应用相同的严格性。
Andriy M

Answers:


25

使用UNPIVOT时要求相同长度的背后逻辑是什么?

这个问题可能只有实施实施计划的人员才能真正回答UNPIVOT。您可以通过与他们联系以获得支持来获取此信息。以下是我对推理的理解,可能并非100%准确:


T-SQL包含任意数量的怪异语义和其他违反直觉的行为实例。其中一些最终将在弃用周期中消失,但其他一些可能永远不会“改进”或“固定”。除了其他方面,还存在依赖于这些行为的应用程序,因此必须保留向后兼容性。

隐式转换和表达式类型派生的规则占上述怪异的很大一部分。我并不羡慕测试人员,他们必须确保SET为新版本保留奇怪的(通常是未记录的)行为(在会话值的所有组合下)。

也就是说,在引入新的语言功能时(没有明显的向后兼容性),没有充分的理由不做改进,避免过去的错误。新功能,例如递归公用表表达式(如Andriy M在评论中提到的),UNPIVOT可以自由使用相对理智的语义和明确定义的规则。

关于在类型中是否包含长度是否使显式键入过分有多种观点,但我个人欢迎这样做。在我看来,类型varchar(25)varchar(50)相同的,多于decimal(8)decimal(10)是。我认为,特殊的大小写字符串类型转换会使事情变得不必要,并且没有任何实际价值。

有人可能会争辩说,只应明确声明可能会丢失数据的隐式转换,但是那里也有极端情况。最终,将需要进行转换,因此我们不妨将其明确化。

如果允许从varchar(25)到的隐式转换varchar(50),那将是另一个(最有可能是隐藏的)隐式转换,并具有所有常见的怪异边缘情况并SET设置了敏感度。为什么不使实现最简单,最明确呢?(但是,没有什么是完美的,而允许隐藏varchar(25)和隐藏varchar(50)在其中sql_variant是可耻的。)

重写UNPIVOTwith APPLYUNION ALL避免(更好的)类型行为,因为for的规则UNION要向后兼容,并且在联机丛书中记录为允许不同的类型,只要它们与隐式转换是可比的(数据类型优先的奥秘规则)等等)。

解决方法是明确显示数据类型,并在必要时添加明确的转换。这对我来说似乎是进步:)

编写显式类型的解决方法的一种方法:

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)均必须是相同的数据类型。由于相等性的传递属性,每个表达式也必须具有相同的数据类型。

在此处输入图片说明


是的,我了解数据类型要求,但问题是为什么长度必须相同。
塔林

我忽略了这一点,是的,IN运算符通常不在乎长度。
dev_etter

允许您忽略指定长度的一种替代方法是将每个变量都强制转换
#!
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.