如何在没有聚集且不知道列值的情况下使用SQL PIVOT表?

时间:2018-08-07 20:18:51

标签: sql sql-server pivot sql-server-2016

我正在处理旧版数据库,需要开发一个SQL查询以提供给客户。作为遗留数据库,它在设计时就没有考虑这种类型的查询。我简化了我需要选择的两个表,以使示例更容易理解。我有一个“长桌”,需要使其“变宽”。我尝试使用PIVOT,但是遇到两个问题:

  1. 没有要聚合的东西-只是一个简单的矩阵变换。
  2. 我不知道我需要增加多少列标题的实际值。

我需要一个SQL查询,该查询将针对以下给定架构输出如下结果:

| [Id] | [Author] | [PublishedYear] | [Title]   |
-------------------------------------------------
| 1    | 'Robert' | '2017'          | null      |
| 2    | 'Tim'    | null            | null      |
| 3    | null     | '2018'          | null      |
| 4    | null     | null            | 'Winning' |

要构建的SQL示例:

CREATE TABLE [Book] (
    [Id] int
);

INSERT INTO [Book] ([Id])
VALUES (1);

INSERT INTO [Book] ([Id])
VALUES (2);

INSERT INTO [Book] ([Id])
VALUES (3);

INSERT INTO [Book] ([Id])
VALUES (4);

CREATE TABLE [BookProperty] (
    [Name] VARCHAR(100),
    [Value] VARCHAR(100),
    [BookId] int
);

INSERT INTO [BookProperty] ([Name], [Value], [bookId])
VALUES ('Author', 'Robert', 1);

INSERT INTO [BookProperty] ([Name], [Value], [bookId])
VALUES ('Author', 'Tim', 2);

INSERT INTO [BookProperty] ([Name], [Value], [bookId])
VALUES ('PublishedYear', '2018', 3);

INSERT INTO [BookProperty] ([Name], [Value], [bookId])
VALUES ('PublishedYear', '2017', 1);

INSERT INTO [BookProperty] ([Name], [Value], [bookId])
VALUES ('Title', 'Winning', 4);

2 个答案:

答案 0 :(得分:2)

您可以尝试使用条件汇总函数MAXCASE WHENGROUP BY

CREATE TABLE [Book] (
    [Id] int
);

INSERT INTO [Book] ([Id])
VALUES (1);

INSERT INTO [Book] ([Id])
VALUES (2);

INSERT INTO [Book] ([Id])
VALUES (3);

INSERT INTO [Book] ([Id])
VALUES (4);

CREATE TABLE [BookProperty] (
    [Name] VARCHAR(100),
    [Value] VARCHAR(100),
    [BookId] int
);

INSERT INTO [BookProperty] ([Name], [Value], [bookId])
VALUES ('Author', 'Robert', 1);

INSERT INTO [BookProperty] ([Name], [Value], [bookId])
VALUES ('Author', 'Tim', 2);

INSERT INTO [BookProperty] ([Name], [Value], [bookId])
VALUES ('PublishedYear', '2018', 3);

INSERT INTO [BookProperty] ([Name], [Value], [bookId])
VALUES ('PublishedYear', '2017', 1);

INSERT INTO [BookProperty] ([Name], [Value], [bookId])
VALUES ('Title', 'Winning', 4);

查询1

SELECT id,
       MAX(CASE WHEN Name = 'Author' THEN Value END) as 'Author',
       MAX(CASE WHEN Name = 'PublishedYear' THEN Value END) as 'PublishedYear',
       MAX(CASE WHEN Name = 'Title' THEN Value END) as 'Title'
FROM [Book] b INNER JOIN [BookProperty] bp
on b.Id = bp.BookId
GROUP BY id

Results

| id | Author | PublishedYear |   Title |
|----|--------|---------------|---------|
|  1 | Robert |          2017 |  (null) |
|  2 |    Tim |        (null) |  (null) |
|  3 | (null) |          2018 |  (null) |
|  4 | (null) |        (null) | Winning |

编辑

您可以尝试使用动态枢纽来达到期望。

使用STUFF函数动态创建条件聚合函数 execute语句,然后使用execute动态执行您的sql。

DECLARE @cols AS NVARCHAR(MAX),
        @query  AS NVARCHAR(MAX);



SET @cols = STUFF((SELECT DISTINCT ',MAX(CASE WHEN Name = '''  + Name  +''' THEN [Value] END) as ''' + Name + ''' '
             FROM [Book] b INNER JOIN [BookProperty] bp
             on b.Id = bp.BookId 
            FOR XML PATH(''), TYPE
            ).value('.', 'NVARCHAR(MAX)') 
        ,1,1,'')



set @query = 'SELECT ID, '+  @cols  + ' FROM [Book] b INNER JOIN [BookProperty] bp on b.Id = bp.BookId GROUP BY id'

execute(@query)

答案 1 :(得分:0)

面临的挑战是您拥有一个EAV,并试图调整价值。这是您必须使用的设计中的丑陋事物之一。您将需要动态sql。我仍将使用条件聚合(又称为交叉表),但您需要制作一个动态版本。这是一个例子,与我几年前写的内容并没有太大不同。您应该可以很容易地将其调整到您的数据集。如果您需要帮助修改此内容,请告诉我,我会帮忙。

if OBJECT_ID('tempdb..#Something') is not null
    drop table #Something

if OBJECT_ID('tempdb..#ColumnNames') is not null
    drop table #ColumnNames

create table #Something
(
    REQUEST_ID int
    , ITEM_ID int
    , ErrorType varchar(50)
)

insert #Something values
(6019, 5054257, 'Under construction')
, (6024, 5054712, 'KSCV417W')
, (6024, 5054713, 'Under construction')
, (6024, 5054715, 'Under construction')
, (6029, 5164288, 'KSAC680E')
, (6029, 5164289, 'KSAC680E')
, (6029, 5164290, 'KSAC680E')
, (6029, 5164292, 'KSAC680E')

create table #ColumnNames --we need some way to get the Column Names in a table so we can join to this to generate the dynamic columns.
(
    ColNum int identity
    , ColName sysname
)

insert #ColumnNames
select distinct ErrorType
from #Something
order by ErrorType desc

declare @StaticPortion nvarchar(2000) = 
'with OrderedResults as
(
    select REQUEST_ID
        , ErrorType
        , GroupCount = count(*)
        , ColNum
    from #Something s
    join #ColumnNames cn on cn.ColName = s.ErrorType
    group by REQUEST_ID
        , ErrorType
        , ColNum
)
select REQUEST_ID';

declare @DynamicPortion nvarchar(max) = '';


with E1(N) AS (select 1 from (values (1),(1),(1),(1),(1),(1),(1),(1),(1),(1))dt(n)),
E2(N) AS (SELECT 1 FROM E1 a, E1 b), --10E+2 or 100 rows
E4(N) AS (SELECT 1 FROM E2 a, E2 b), --10E+4 or 10,000 rows max
cteTally(N) AS 
(
    SELECT  ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) FROM E4
)

select @DynamicPortion = @DynamicPortion + 
    ', MAX(Case when ColNum = ' + CAST(N as varchar(6)) + ' then convert(varchar(4), GroupCount) end) as [' + cn.ColName + ']'
from cteTally t
join #ColumnNames cn on cn.ColNum = t.N
where t.N <= 
(
    select top 1 Count(*)
    from #Something
    group by REQUEST_ID
    order by COUNT(*) desc
)

declare @FinalStaticPortion nvarchar(2000) = ' from OrderedResults Group by REQUEST_ID order by REQUEST_ID';

declare @SqlToExecute nvarchar(max) = @StaticPortion + @DynamicPortion + @FinalStaticPortion;

--select @SqlToExecute --You can use this to help debug the dynamic sql

exec sp_executesql @SqlToExecute