SQL 服务器:将行显示为列/数据透视表

SQL Server : show rows as columns / Pivot

我有一个 SQL Server 2008 Express table 这样的:

rno   gid   uid   dat          origamt   disamt
-----------------------------------------------
1     AA    a     12-05-2016    200       210
2     AA    b     12-05-2016    300       305
3     AA    c     12-05-2016    150       116
4     BB    a     12-05-2016    120       125
5     BB    c     12-05-2016    130       136
6     CC    a     12-05-2016    112       115
7     CC    b     12-05-2016    135       136

等等不同的日期

我想这样显示:

sno     dat       gid   a_orig   a_dis   b_orig  b_dis   c_orig   c_dis .....
1    12-05-2016   AA    200      210     300     305     150      116
2    12-05-2016   BB    120      125     0       0       130      136
3    12-05-2016   CC    112      115     135     136     0        0

注意:uid的值不是固定的,可能会动态变化,所以,a_orig,a_dis,b_orig, b_dis, 等不能硬编码到 SQL.

注意:由于 gid 和 uid 的笛卡尔积,预计每个日期大约有 300 行。我将通过实施 LIKE 子句按日期进行搜索,因为 dat 列的数据类型是 varchar(50).

注意:我希望 origamtdisamt 的数据类型是 varchar(50) 而不是 Decimal(18, 0) 但它是不是强迫。

我尝试通过参考在 Whosebug 和其他网站上发布的几篇文章来使用 PIVOT,但无法完全完成工作。

这是我尝试过的方法,使用固定的 uid 得到了几乎不错的结果,并且只获取了 origamt:

select *
from 
(
  select gid, uid, dat, origamt
  from vamounts
) as src
pivot
(
  sum(origamt)
  for uid IN ( a, b )
) as piv;

请帮助我解决这个问题的最简单的解决方案。我更喜欢最少的代码行和最少的复杂性。

加入可能会有帮助

declare @t table (rno int,  gid varchar(2),  uid varchar(1),  dat varchar(10), origamt int,  disamt int)
insert into @t
values 
(1,     'AA',    'a',     '12-05-2016',    200,       210),
(2 ,    'AA',    'b',     '12-05-2016',    300,       305),
(3  ,   'AA',    'c',     '12-05-2016',    150,       116),
(4 ,    'BB',    'a',     '12-05-2016',    120,       125),
(5 ,    'BB',    'c',     '12-05-2016',    130,       136),
(6 ,    'CC',    'a',     '12-05-2016',    112,       115),
(7 ,    'CC',    'b',     '12-05-2016',    135,       136)

    select   -- piv.*,piv2.*
             piv.gid,piv.dat
            ,piv.a as a_org
            ,piv2.a as a_dis
            ,piv.b as b_org
            ,piv2.b as b_dis
            ,piv.c as c_org
            ,piv2.c as c_dis
    from 
    (
      select gid, uid, dat, origamt
      from  @t
    ) as src
    pivot
    (
      sum(origamt)
      for uid IN ([a],[b],[c] )
    ) as piv
    join 
    (select piv2.*
    from 
    (
      select gid, uid, dat, disamt
      from  @t
    ) as src
    pivot
    (
      sum(disamt)
      for uid IN ([a],[b],[c] )
    ) as piv2
    ) piv2
    on piv2.gid = piv.gid and piv2.dat = piv.dat 

这是一个 POC,您必须使用动态 sql 来处理可变数量的 uid。如果您不知道如何使用动态 SQL,请告诉我,我会为您制作一个示例。

呃,没有。您无法使用 SQL 生成所需的 table。这不是一个有效的枢轴 table.

"the values of uid are not fixed, they may vary dynamically, so, a_orig, a_dis, b_orig, b_dis, etc cannot be hardcoded into SQL."

抱歉,这也不可能。您必须 指定要作为列 header 放置的确切值。无论何时编写 SELECT 语句,都必须指定要 returning 的列(字段)的名称。没有办法解决这个问题。

但是,以下是根据您的数据创建 "valid" SQL 服务器枢轴 table 所需的步骤:

我不得不承认,当我最近不得不在 SQL 服务器中编写我的第一个 PIVOT 时,我也疯狂地谷歌搜索,但不知道如何写它。

不过,我最终弄清楚了您需要做什么,所以这里是您在其他任何地方都找不到的 step-by-step 指南..!

(读者可以轻松改编这些说明,以使用您自己的数据!)

1.创建您的示例数据

如果您希望读者回复您的问题,您至少应该给他们 SQL 来创建您的样本数据,这样他们就有事情要做了。

那么,我将如何创建您问题中显示的数据:

CREATE TABLE tblSomething
(
    [gid] nvarchar(100),
    [uid] nvarchar(100),
    [dat] datetime,
    [origamt] int,
    [disamt] int
)
GO

INSERT INTO tblSomething VALUES ('AA', 'a', '2016-05-12', 200, 210)
INSERT INTO tblSomething VALUES ('AA', 'b', '2016-05-12', 300, 305)
INSERT INTO tblSomething VALUES ('AA', 'c', '2016-05-12', 150, 116)
INSERT INTO tblSomething VALUES ('BB', 'a', '2016-05-12', 120, 125)
INSERT INTO tblSomething VALUES ('BB', 'c', '2016-05-12', 130, 136)
INSERT INTO tblSomething VALUES ('CC', 'a', '2016-05-12', 112, 115)
INSERT INTO tblSomething VALUES ('CC', 'b', '2016-05-12', 135, 136)
GO

2。编写一个 SQL 查询,其中 return 正好是三列

第一列将包含将出现在 PIVOT table 的 left-hand 列中的值。

第二列将包含将显示在顶行的值列表。

第三列中的值将根据 row/column headers.

在您的 PIVOT table 中定位

好的,SQL 执行此操作:

SELECT [gid], [uid], [origamt]
FROM tblSomething

这是使用PIVOT的关键。您的数据库结构可以像您喜欢的那样复杂得可怕,但是当使用 PIVOT 时,您只能使用恰好 三个 个值。不多也不少。

所以,这就是 SQL 将要 return 的结果。我们的目标是创建一个 PIVOT table 包含(仅)这些值:

3。查找 header 行的不同值列表

请注意,在我打算创建的枢轴 table 中,我有三个列(字段),分别称为 abc。这些是 [uid] 列中的三个唯一值。

因此,要获得这些唯一值的 comma-concatenated 列表,我可以使用此 SQL:

DECLARE @LongString nvarchar(4000)

SELECT @LongString = COALESCE(@LongString + ', ', '') + '[' + [uid] + ']' 
FROM [tblSomething]
GROUP BY [uid]

SELECT @LongString AS 'Subquery'

当我 运行 根据您的数据进行此操作时,我得到的是:

现在,剪切并粘贴这个值:我们需要将它 两次 放在我们的整体 SQL SELECT 命令中以创建枢轴 table.

4.全部放在一起

这是棘手的一点。

您需要将第 2 步的 SQL 命令和第 3 步的结果合并为一个 SELECT 命令。

这是您的 SQL 的样子:

SELECT [gid],
        --  Here's the "Subquery" from part 3
        [a], [b], [c]
FROM (
    --  Here's the original SQL "SELECT" statement from part 2
    SELECT [gid], [uid], [origamt]
    FROM tblSomething

) tmp ([gid], [uid], [origamt])
pivot (
    MAX([origamt]) for [uid] in (
        --  Here's the "Subquery" from part 3 again
        [a], [b], [c]
    )
) p

...这是一张令人困惑的图片,它显示了组件的来源,以及 运行 执行此命令的结果。

如您所见,关键在于第 2 步中的 SELECT 语句,并将您选择的三个字段放在该命令中的正确位置。

而且,正如我之前所说,数据透视表 table 中的列(字段)来自步骤 3 中获得的值:

[a], [b], [c]

当然,您可以使用这些值的一个子集。也许您只想查看 [a], [b] 的 PIVOT 值并忽略 [c].

呸!

所以,这就是如何从您的数据中创建一个枢轴 table。

I will prefer least lines of code and least complexity.

是的,祝你好运..!!!

5.合并两个枢轴 tables

如果您真的想要,可以合并两个这样的 PIVOT table 的内容以获得您正在寻找的确切结果。

这很容易 SQL Shobhit 自己写。

你需要 dynamic SQL 才能获得这些内容。

首先使用您的数据创建 table:

CREATE TABLE #temp (
    rno int, 
    gid nvarchar(10), 
    [uid] nvarchar(10), 
    dat date, 
    origamt int, 
    disamt int 
) 

INSERT INTO #temp VALUES 
(1,     'AA',    'a',     '12-05-2016',    200,       210),
(2,     'AA',    'b',     '12-05-2016',    300,       305),
(3,     'AA',    'c',     '12-05-2016',    150,       116),
(4,     'BB',    'a',     '12-05-2016',    120,       125),
(5,     'BB',    'c',     '12-05-2016',    130,       136),
(6,     'CC',    'a',     '12-05-2016',    112,       115),
(7,     'CC',    'b',     '12-05-2016',    135,       136)

然后用列声明变量:

DECLARE @columns nvarchar(max), @sql nvarchar(max), @columns1 nvarchar(max), @columnsN nvarchar(max)
--Here simple columns like [a],[b],[c] etc
SELECT @columns =STUFF((SELECT DISTINCT ','+QUOTENAME([uid]) FROM #temp FOR XML PATH('')),1,1,'')
--Here with ISNULL operation ISNULL([a],0) as [a],ISNULL([b],0) as [b],ISNULL([c],0) as [c]
SELECT @columnsN = STUFF((SELECT DISTINCT ',ISNULL('+QUOTENAME([uid])+',0) as '+QUOTENAME([uid]) FROM #temp FOR XML PATH('')),1,1,'')
--Here columns for final table orig.a as a_orig, dis.a as a_dis,orig.b as b_orig, dis.b as b_dis,orig.c as c_orig, dis.c as c_dis
SELECT @columns1 = STUFF((SELECT DISTINCT ',orig.'+[uid] + ' as ' +[uid]+ '_orig, dis.'+[uid] + ' as ' +[uid]+ '_dis' FROM #temp FOR XML PATH('')),1,1,'')

和主要查询:

SELECT @sql = '
SELECT  orig.gid,
        orig.dat,
        '+@columns1+'
FROM (
        SELECT gid, dat, '+@columnsN+'
        FROM (
            SELECT gid, [uid], LEFT(dat,10) as dat, origamt
            FROM #temp
            ) as p
        PIVOT (
        SUM(origamt) FOR [uid] in ('+@columns+')
        ) as pvt
    ) as orig
LEFT JOIN (
        SELECT gid, dat, '+@columnsN+'
        FROM (
            SELECT gid, [uid], LEFT(dat,10) as dat, disamt
            FROM #temp
            ) as p
        PIVOT (
        SUM(disamt) FOR [uid] in ('+@columns+')
        ) as pvt
    ) as dis
    ON dis.gid = orig.gid and dis.dat = orig.dat'

EXEC(@sql)

输出:

gid dat         a_orig  a_dis   b_orig  b_dis   c_orig  c_dis
AA  2016-12-05  200     210     300     305     150     116
BB  2016-12-05  120     125     0       0       130     136
CC  2016-12-05  112     115     135     136     0       0