【问题标题】:SQL Server pivot values with different data typesSQL Server 具有不同数据类型的数据透视值
【发布时间】:2020-09-28 11:21:59
【问题描述】:

我正在尝试在 MSSQL 2016 中旋转不同类型的所有值。我找不到如何旋转不同数据类型的方法..

第一张表是初始形式/结构。 第二个表格是所需的形状。

我正在尝试使用以下 SQL 代码来转换我的值

SELECT
    [id] AS [id],
    FIRSTNAME,
    LASTNAME,
    BIRTHDATE,
    ADDRESS,
    FLAG,
    NUMBER
FROM (
    SELECT
        [cm].[key] AS [id],
        [cm].[column] AS [column],
        [cm].[string] AS [string],
        [cm].[bit] AS [bit],
        [cm].[xml] AS [xml],
        [cm].[number] AS [number],
        [cm].[date] AS [date]
    FROM [cmaster] AS [cm]
) AS [t]
PIVOT (
    MAX([string]) --!?!?
    FOR [column] IN (
        FIRSTNAME,
        LASTNAME,
        BIRTHDATE,
        ADDRESS,
        FLAG,
        NUMBER
    )
) AS [p]

【问题讨论】:

  • 只需用CONVERT/CAST 包裹列名。例如CONVERT(datetime,BIRTHDATE).

标签: sql sql-server pivot-table


【解决方案1】:

我认为你最好的选择是使用条件聚合,例如

SELECT  cm.id,
        FIRSTNAME = MAX(CASE WHEN cm.[property] = 'firstname' THEN cm.[string] END),
        LASTNAME = MAX(CASE WHEN cm.[property] = 'lastname' THEN cm.[string] END),
        BIRTHDATE = MAX(CASE WHEN cm.[property] = 'birthddate' THEN cm.[date] END),
        FLAG = CONVERT(BIT, MAX(CASE WHEN cm.[bit] = 'flag' THEN CONVERT(TINYINT, cm.[boolean]) END)),
        NUMBER = MAX(CASE WHEN cm.[property] = 'number' THEN cm.[integer] END)
FROM    cmaster AS cm
GROUP BY cm.id;

尽管如您所见,您的查询与您的 EAV 模型非常紧密地耦合,以及为什么将 EAV 视为 SQL 反模式。您的替代方法是在子查询中创建一个列并以此为中心,但您必须转换为单一数据类型,并失去一点类型安全性:

SELECT  id, FIRSTNAME, LASTNAME, BIRTHDATE, ADDRESS, FLAG, NUMBER
FROM    (
            SELECT  id = cm.[key],
                    [column] = cm.[column],
                    Value = CASE cm.type
                                WHEN 'NVARCHAR' THEN cm.string
                                WHEN 'DATETIME' THEN CONVERT(NVARCHAR(MAX), cm.date, 112)
                                WHEN 'XML' THEN CONVERT(NVARCHAR(MAX), cm.xml)
                                WHEN 'BIT' THEN CONVERT(NVARCHAR(MAX), cm.boolean)
                                WHEN 'INT' THEN CONVERT(NVARCHAR(MAX), cm.integer)
                            END
            FROM    cmaster AS cm
        ) AS t
        PIVOT
        (
            MAX(Value)
            FOR [column] IN (FIRSTNAME, LASTNAME, BIRTHDATE, ADDRESS, FLAG, NUMBER)
        ) AS p;

【讨论】:

    【解决方案2】:

    为了根据您的要求生成结果,首先我们需要将数据转换为一种与所有数据类型兼容的格式。 VARCHAR 是理想的选择。然后使用简单的选择查询准备基表,然后 PIVOT 结果。 在最后一个投影中,如果需要,您可以将数据转换回原始格式。

    此查询也可以动态编写,以便在添加记录时获得结果。在这里,我根据您的数据提供静态答案。如果您需要更通用的动态答案,请告诉我。所以我可以在这里发帖。

    --data insert scripts I used: 
     CREATE TABLE  First_Table
     (
     [id] int,
     [column] VARCHAR(10),
     [string] VARCHAR(20),
     [bit] BIT,
     [xml] [xml],
     [number] INT,
     [date] DATE
    
        )
    
     SELECT     GETDATE()
    
    
    INSERT INTO  First_Table VALUES(1, 'FIRST NAME', 'JOHN' , NULL, NULL, NULL, NULL)
    INSERT INTO  First_Table VALUES(1, 'LAST NAME', 'DOE' , NULL, NULL, NULL, NULL)
    INSERT INTO  First_Table VALUES(1, 'BIRTH DATE', NULL , NULL, NULL, NULL, '1985-02-25')
    INSERT INTO  First_Table VALUES(1, 'ADDRESS', NULL , NULL, 'SDFJDGJOKGDGKPDGKPDKGPDKGGKGKG', NULL, NULL)
    INSERT INTO  First_Table VALUES(1, 'FLAG', NULL , 1, NULL, NULL, NULL)
    INSERT INTO  First_Table VALUES(1, 'NUMBER', NULL , NULL, NULL, 20, NULL)
    
    
    
    SELECT 
    PIVOTED.* FROM
    
    (
     --MAKING THE BASE TABLE FOR PIVOT
    SELECT 
       [id]
       ,[column] AS [COLUMN]
       , CASE WHEN  [column]  = 'FIRST NAME' then [string] 
       WHEN [column]  = 'LAST NAME' then [string] 
       WHEN [column]  = 'BIRTH DATE' then CAST([date] AS VARCHAR(100))
       WHEN [column]  = 'ADDRESS' then CAst([xml] as VARCHAR(100)) 
       WHEN [column]  = 'FLAG' then CAST([bit] AS VARCHAR(100))
       else CAST([number] AS VARCHAR(100)) END AS  [VALUE]
    FROM   First_Table
     ) AS [P]
    
     PIVOT
    (
    
     MIN ([P].[VALUE])
     FOR [column]  in  ([FIRST NAME],[LAST NAME],[BIRTH DATE],[ADDRESS],[FLAG],[NUMBER])
    
      ) AS PIVOTED
    

    结果:

    【讨论】:

      【解决方案3】:

      SQL:

      SELECT
                  ID,
                  FIRSTNAME,
                  ...,
                  FLAG = CAST (FLAG AS INT),
                  ...
      FROM
                  (
                  SELECT
                              *
                  FROM
                              (
                              SELECT
                                          f.ID,
                                          f.PROPERTY,
                                          f.STRING + f."INTEGER" + f.DATETIME + f.BOLLEAN + f.XML AS COLS
                              FROM
                                          FIRSTTBL f)
                  PIVOT(
                              min(COLS) FOR PROPERTY IN
                                          (
                                          'firstname' AS firstname,
                                          'lastname' AS lastname,
                                          'birthdate' AS birthdate,
                                          'address' AS address,
                                          'flag' AS flag,
                                          'number' AS "NUMBER"
                                          )
                              )
                  )
      

      根据原表,STRING、INTEGER、DATETIME、BOLLEAN和XML列中的任意行只有一个非空值,所以我们只需要获取第一个非空值并赋值给对应的新列。使用 PIVOT 函数进行转置并不难,只是我们需要根据 SQL 规则处理不同的数据类型,这要求每一列都有一致的类型。对于这个任务,首先我们需要将组合的列值转换为字符串,执行行到列的转置,然后将字符串转换回正确的类型。当列数很多时,SQL 语句会很棘手,动态需求甚至难以实现。

      但使用开源集算器 SPL 编写代码很容易:

        A
      1 =connect("MSSQL")
      2 =A1.query@x("SELECT * FROM FIRSTTBL")
      3 =A2.pivot(ID;PROPERTY,~.array().m(4:).ifn();"firstname":"FIRSTNAME",
      "lastname":"LASTANME","birthdate":"BIRTHDAY",
      "address":"ADDRESS","flag":"FLAG","number":"NUMBER")

      SPL 不要求同一列中的数据具有一致的类型。它在执行转置时很容易保持原始数据类型。

      【讨论】:

        猜你喜欢
        • 2016-12-21
        • 1970-01-01
        • 2015-02-28
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多