透视自定义字段

时间:2014-05-23 18:23:42

标签: sql sql-server-2008

所以我有以下架构: http://sqlfiddle.com/#!3/f69de

我的问题是我不确定如何透视自定义字段表。自定义字段是动态的,有n个字段。

如何显示如下数据:

Header: [PeriodEndDate], [CustomField 1], [CustomField 2], [CustomField 3]... [Customfield n]
Values (rows): 
'2013-11-24', 'Value 1', 'Value 2', 'Value 3
'2013-11-24', 'Value 1', '', 'Value 3'

编辑:

我认为展示这个的最佳方式是展示一个真实世界的例子。

以下查询显示一个“JOB”的结果,但真实查询应根据“PERIODENDDATE”返回


Select Id, JobId, Reg, OT, DT, Expenses, PerDiem 
From WeeklyContractBillings
WHere JobId = 44 and PeriodEndDate = '2014-05-25'


Select t.CustomFieldId, cfli.Name, cf.Name as CustomFieldName 
From Trackings t
Inner Join CustomFields cf on t.CustomFieldId = cf.CustomFieldId and cf.CustomFieldTypeId = 2 and cf.IsActive = 1 and cf.CompanyId = 25
LEFT Outer Join [ContractJobCustomFields] cjcf on t.CustomFieldId = cjcf.CustomFieldId and cjcf.JobId = 44
LEFT OUTER Join CustomFieldListItems cfli on cjcf.CustomFieldId = cfli.CustomFieldId and cjcf.CustomFieldValue = cfli.CustomFieldListItemId
Where t.ServiceOfferingId = 1
union
Select t.CustomFieldId, up.FirstName + ' ' + up.LastName as Name, cf.Name as CustomFieldName 
From Trackings t
Inner Join CustomFields cf on t.CustomFieldId = cf.CustomFieldId and cf.CustomFieldTypeId = 1 and cf.IsActive = 1 and cf.CompanyId = 25
LEFT Outer Join [ContractJobCustomFields] cjcf on t.CustomFieldId = cjcf.CustomFieldId and cjcf.JobId = 44
LEFT OUTER Join [dbo].[UserProfiles] up on cjcf.CustomFieldValue = up.UserId
Where t.ServiceOfferingId = 1

以下是每个查询的结果:

查询1:

Id  JobId   Reg OT  DT  Expenses    PerDiem
2509    44  16  0   0   0.00    0.00

查询2:


CustomFieldId   Name    CustomFieldName
9   Jason Mogera    Sales Rep
10  NULL    Staffing Manager
14  NULL    Recruiter
16  Clerical    Market Segment
20  NULL    Location

我要做的是将这些值组合在一行中,但第二个查询需要是基于这样的列

Id  JobId   Reg OT  DT  Expenses    PerDiem Sales Rep Staffing Manager Recruiter Market Segment Location
2509 44 16  0   0   0.00    0.00   Jason Mogera None None Clerical None

这个问题被视为重复,但看着它并没有真正增加在那里有另一个查询的复杂性。

注意示例GIVEN是按JOBID过滤,但在实际查询中,它只需按PERIODENDDATE过滤。换句话说,查询将分别返回多行作业及其自定义字段。

请帮忙!

1 个答案:

答案 0 :(得分:0)

这非常复杂......

首先,我使用元数据跟踪系统,以便单个过程可以通过一次执行来维护许多视图。该过程改变(或创建枢轴视图不存在)。以下评论相对较好地解释了它。我将您的示例数据包含在此处,如果您的需求扩大,我可以回答有关配置的更多问题。

IF OBJECT_ID('JobWeeklysCustomFields') IS NOT NULL DROP VIEW JobWeeklysCustomFields
GO
IF OBJECT_ID('Weeklys') IS NOT NULL DROP TABLE Weeklys
GO
IF OBJECT_ID('CustomFields') IS NOT NULL DROP TABLE CustomFields
GO
IF OBJECT_ID('Jobs') IS NOT NULL DROP TABLE Jobs
GO
IF OBJECT_ID('JobsCustomFields') IS NOT NULL DROP TABLE JobsCustomFields
GO
/**
    JCPivot by Jaaz Cole
    Covered by Creative Commons : CC BY-SA
**/
IF OBJECT_ID('JCPivot.CreatePivotViews') IS NOT NULL DROP PROCEDURE JCPivot.CreatePivotViews
GO
IF OBJECT_ID('JCPivot.UniqueTablesAggregateOrColumn') IS NOT NULL DROP VIEW JCPivot.UniqueTablesAggregateOrColumn
GO
IF OBJECT_ID('JCPivot.UniqueTablesRow') IS NOT NULL DROP VIEW JCPivot.UniqueTablesRow
GO
IF OBJECT_ID('JCPivot.TablesTo') IS NOT NULL DROP TABLE JCPivot.TablesTo
GO
IF SCHEMA_ID('JCPivot') IS NOT NULL DROP SCHEMA JCPivot
GO
CREATE SCHEMA JCPivot
GO
/*
   Metadata table: distinct table and/or view names will be run
   and have a special view generated for each with a _Pivot
   suffix. So pivoting [dbo].[Emps] through this interface would
   create [dbo].[Emps_Pivot] as defined here. RowColAgg can only
   be set to 'Row', 'Col', or 'Agg'.

   'Row' column names will be selected to the left of the dataset,
       as normal columns. Limit 1 distinct column name per object.
   'Col' Will use the distinct values in this column as column headers.
       Limit 1 per object. Must have a 'Col' and an 'Agg' column 
       for an object to generate a pivot view.
   'Agg' is the column to aggregate across the 'Col' headers, and
       Also requires [AggFunction] contain only the name (no parens!)
       of an aggregate function to perform for the pivot.
       Limit 1 per object. Must have a 'Col' and an 'Agg' column 
       for an object to generate a pivot view.
*/
CREATE TABLE JCPivot.TablesTo (
      schemaName SYSNAME
    , objectName SYSNAME
    , columnName SYSNAME
    , RowColAgg VARCHAR(3) NOT NULL
    , AggFunction NVARCHAR(128)
    )
GO
/* Enforce no more than 1 each Aggregate and Column Header */
CREATE VIEW JCPivot.UniqueTablesAggregateOrColumn
WITH SCHEMABINDING
AS
SELECT schemaName, objectName, RowColAgg FROM JCPivot.TablesTo WHERE RowColAgg = 'Agg' OR RowColAgg = 'Col';
GO    
CREATE UNIQUE CLUSTERED INDEX PK_UniqueAggOrCol ON JCPivot.UniqueTablesAggregateOrColumn(schemaName, objectName, RowColAgg);
GO
/* Enforce each Row header selected only once. */
CREATE VIEW JCPivot.UniqueTablesRow
WITH SCHEMABINDING
AS
SELECT schemaName, objectName, columnName, RowColAgg FROM JCPivot.TablesTo WHERE RowColAgg = 'Row';
GO    
CREATE UNIQUE CLUSTERED INDEX PK_UniqueTablesRow ON JCPivot.UniqueTablesRow(schemaName, objectName, columnName);
GO
/* This Procedure Creates the Views */
CREATE PROCEDURE JCPivot.CreatePivotViews
AS
BEGIN
SET NOCOUNT ON
DECLARE
      @SchemaName SYSNAME
    , @ObjectName SYSNAME
    , @ColumnHeaderColumnName SYSNAME
    , @AggregateFunction SYSNAME
    , @AggregateColumnName SYSNAME

DECLARE
      @RowHeaderColumnNames TABLE (RowHeader SYSNAME, typeName nvarchar(128))

DECLARE ObjectCursor CURSOR FAST_FORWARD FOR
    SELECT schemaName, objectName
    FROM JCPivot.TablesTo
    GROUP BY schemaName, objectName
    HAVING SUM(CASE WHEN RowColAgg IN ('Agg','Col') THEN 1 ELSE 0 END) = 2
    ORDER BY schemaName, objectName

OPEN ObjectCursor
FETCH NEXT FROM ObjectCursor INTO @SchemaName, @ObjectName

WHILE @@FETCH_STATUS = 0
BEGIN
    DELETE FROM @RowHeaderColumnNames
    INSERT INTO @RowHeaderColumnNames (RowHeader) SELECT columnName FROM JCPivot.TablesTo WHERE schemaName = @SchemaName AND objectName = @ObjectName AND RowColAgg = 'Row'

    SELECT @ColumnHeaderColumnName = columnName FROM JCPivot.TablesTo WHERE schemaName = @SchemaName AND objectName = @ObjectName AND RowColAgg = 'Col'
    SELECT @AggregateFunction = AggFunction, @AggregateColumnName = columnName FROM JCPivot.TablesTo WHERE schemaName = @SchemaName AND objectName = @ObjectName AND RowColAgg = 'Agg'

    --Internal declarations
    DECLARE @CursSQL NVARCHAR(MAX)
    DECLARE @ColumnHeaderType NVARCHAR(30)
    SELECT @ColumnHeaderType = C.DATA_TYPE
            + CASE
                WHEN C.DATA_TYPE in ('varchar','nvarchar') THEN N'(' + cast(C.CHARACTER_MAXIMUM_LENGTH as nvarchar) + N')'
                WHEN C.DATA_TYPE = 'numeric' THEN N'(' + cast(C.NUMERIC_PRECISION as nvarchar) + N',' + cast(C.NUMERIC_SCALE as nvarchar) + N')'
                ELSE ''
            END
    FROM  INFORMATION_SCHEMA.COLUMNS C
    WHERE C.COLUMN_NAME = @ColumnHeaderColumnName
        AND C.TABLE_SCHEMA = @SchemaName
        AND C.TABLE_NAME = @ObjectName

    DECLARE @RowHeaderType as nvarchar(30)
    UPDATE RHCN
    SET TypeName = C.DATA_TYPE
        + CASE
            WHEN C.DATA_TYPE in ('varchar','nvarchar') THEN N'(' + cast(C.CHARACTER_MAXIMUM_LENGTH AS NVARCHAR(10)) + N')'
            WHEN C.DATA_TYPE = 'numeric' THEN N'(' + cast(C.NUMERIC_PRECISION AS NVARCHAR(10)) + N',' + cast(C.NUMERIC_SCALE AS NVARCHAR(10)) + N')'
            ELSE ''
        END
    FROM INFORMATION_SCHEMA.COLUMNS C
        INNER JOIN @RowHeaderColumnNames RHCN on C.COLUMN_NAME = RHCN.RowHeader
            AND C.TABLE_SCHEMA = @SchemaName
            AND C.TABLE_NAME = @ObjectName

    DECLARE @AggregateColumnType SYSNAME = N''
    SELECT @AggregateColumnType = C.DATA_TYPE
            + CASE
                WHEN C.DATA_TYPE in ('varchar','nvarchar') THEN N'(' + cast(C.CHARACTER_MAXIMUM_LENGTH AS NVARCHAR(10)) + N')'
                WHEN C.DATA_TYPE = 'numeric' THEN N'(' + cast(C.NUMERIC_PRECISION AS NVARCHAR(10)) + N',' + cast(C.NUMERIC_SCALE AS NVARCHAR(10)) + N')'
                ELSE ''
            END
    FROM  INFORMATION_SCHEMA.COLUMNS C
    WHERE C.COLUMN_NAME = @AggregateColumnName
        AND C.TABLE_SCHEMA = @SchemaName
        AND C.TABLE_NAME = @ObjectName

    DECLARE @RowHeaderNames NVARCHAR(MAX) = N'', @RowHeaderDeclarations NVARCHAR(MAX) = N''

    SELECT
          @RowHeaderNames = @RowHeaderNames + R.RowHeader + ', '
        , @RowHeaderDeclarations = @RowHeaderDeclarations + R.RowHeader + ' ' + R.typeName + ', '
    FROM @RowHeaderColumnNames R

    SELECT
          @RowHeaderNames = LEFT(@RowHeaderNames, LEN(@RowHeaderNames)-1)
        , @RowHeaderDeclarations = LEFT(@RowHeaderDeclarations, LEN(@RowHeaderDeclarations)-1)

    SET @CursSQL=N'
    DECLARE @SQL NVARCHAR(MAX), @value ' + @ColumnHeaderType + N'
        , @ColumnHeaderNames NVARCHAR(MAX) = '''', @ColumnHeaderDeclarations NVARCHAR(MAX) = ''''

    SELECT
          @ColumnHeaderNames = @ColumnHeaderNames + QUOTENAME(' + @ColumnHeaderColumnName + N') + '',''
        , @ColumnHeaderDeclarations = @ColumnHeaderDeclarations + QUOTENAME(' + @ColumnHeaderColumnName + N') + '' ' + @AggregateColumnType + N''' + '',''
    FROM ' + QUOTENAME(@SchemaName) + N'.' + QUOTENAME(@ObjectName) + N'
    WHERE ' + @ColumnHeaderColumnName + N' IS NOT NULL
    GROUP BY ' + @ColumnHeaderColumnName + N'

    SELECT
          @ColumnHeaderNames = LEFT(@ColumnHeaderNames, LEN(@ColumnHeaderNames)-1)
        , @ColumnHeaderDeclarations = LEFT(@ColumnHeaderDeclarations, LEN(@ColumnHeaderDeclarations)-1)

    SET @sql=N''
    '' + CASE WHEN OBJECT_ID(''' + QUOTENAME(@SchemaName) + N'.' + QUOTENAME(@ObjectName + '_Pivot') + N''') IS NOT NULL THEN ''ALTER'' ELSE ''CREATE'' END + '' VIEW ' + QUOTENAME(@SchemaName) + N'.' + QUOTENAME(@ObjectName + '_Pivot') + N' AS
        SELECT ' + @RowHeaderNames + N', '' + @ColumnHeaderNames + N''
        FROM (
            SELECT ' + @RowHeaderNames + N',' + QUOTENAME(@AggregateColumnName) + N' AggCol,' + QUOTENAME(@ColumnHeaderColumnName) + N' HeaderCol
            FROM ' + QUOTENAME(@SchemaName) + N'.' + QUOTENAME(@ObjectName) + N'
            ) dsr
        PIVOT (
            ' + @AggregateFunction + N'(AggCol)
            FOR HeaderCol IN ('' +  @ColumnHeaderNames + '')) P''

    EXEC sp_executesql @SQL'

    EXEC sp_executesql @CursSQL
    FETCH NEXT FROM ObjectCursor INTO @SchemaName, @ObjectName
END
CLOSE ObjectCursor
DEALLOCATE ObjectCursor
SET NOCOUNT OFF
END
GO

/* This is Job Information for Automatic Daily upkeep of the view definitions */
/*
USE [msdb]
GO
DECLARE @jobId BINARY(16)
EXEC  msdb.dbo.sp_add_job @job_name=N'UpdatePivotViews', 
        @enabled=1, 
        @notify_level_eventlog=0, 
        @notify_level_email=2, 
        @notify_level_netsend=2, 
        @notify_level_page=2, 
        @delete_level=0, 
        @description=N'PivotViews Updated', 
        @category_name=N'[Uncategorized (Local)]', 
        @owner_login_name=N'sa', @job_id = @jobId OUTPUT
select @jobId
GO
EXEC msdb.dbo.sp_add_jobserver @job_name=N'UpdatePivotViews', @server_name = N'<MyServer>'
GO
USE [msdb]
GO
EXEC msdb.dbo.sp_add_jobstep @job_name=N'UpdatePivotViews', @step_name=N'ExecuteScript', 
        @step_id=1, 
        @cmdexec_success_code=0, 
        @on_success_action=1, 
        @on_fail_action=2, 
        @retry_attempts=0, 
        @retry_interval=0, 
        @os_run_priority=0, @subsystem=N'TSQL', 
        @command=N'EXEC JCPivot.CreatePivotViews
', 
        @database_name=N'BRNOperational', 
        @flags=0
GO
USE [msdb]
GO
EXEC msdb.dbo.sp_update_job @job_name=N'UpdatePivotViews', 
        @enabled=1, 
        @start_step_id=1, 
        @notify_level_eventlog=0, 
        @notify_level_email=2, 
        @notify_level_netsend=2, 
        @notify_level_page=2, 
        @delete_level=0, 
        @description=N'PivotViews Updated', 
        @category_name=N'[Uncategorized (Local)]', 
        @owner_login_name=N'sa', 
        @notify_email_operator_name=N'', 
        @notify_netsend_operator_name=N'', 
        @notify_page_operator_name=N''
GO
USE [msdb]
GO
DECLARE @schedule_id int
EXEC msdb.dbo.sp_add_jobschedule @job_name=N'UpdatePivotViews', @name=N'Daily', 
        @enabled=1, 
        @freq_type=4, 
        @freq_interval=1, 
        @freq_subday_type=1, 
        @freq_subday_interval=0, 
        @freq_relative_interval=0, 
        @freq_recurrence_factor=1, 
        @active_start_date=20140613, 
        @active_end_date=99991231, 
        @active_start_time=0, 
        @active_end_time=235959, @schedule_id = @schedule_id OUTPUT
select @schedule_id
GO
*/

/** BEGIN CONFIG **/
/* Your schema... */
CREATE TABLE Jobs
    ([JobId] int, [Name] nvarchar(50))
;

INSERT INTO Jobs
    ([JobId], [Name])
VALUES
    (1,'Job A'),
    (2,'Job B'),
    (3,'Job C')
;


GO
CREATE TABLE Weeklys
    ([Id] int, [JobId] int, [PeriodEndDate] nvarchar(50))
;

INSERT INTO Weeklys
    ([Id], [JobId], [PeriodEndDate])
VALUES
    (1, 1, '2013-11-24'),
    (2, 1, '2013-11-24'),
    (3, 2, '2013-11-24'),
    (4, 2, '2013-12-22'),
    (5, 3, '2013-12-22')
;
GO
CREATE TABLE CustomFields
    ([CustomFieldId] int, [CompanyId] int, [Name] nvarchar(50))
;

INSERT INTO CustomFields
    ([CustomFieldId], [CompanyId], [Name])
VALUES
    (1, 1, 'Custom Field 1'),
    (2, 1, 'Custom Field 2'),
    (3, 2, 'Custom Field 3'),
    (4, 2, 'Custom Field 4'),
    (5, 3, 'Custom Field 5')
;
GO
CREATE TABLE JobsCustomFields
    ([JobId] int, [CustomFieldId] int, [CustomFieldValue] nvarchar(50))
;

INSERT INTO JobsCustomFields
    ([JobId], [CustomFieldId], [CustomFieldValue])
VALUES
    (1, 1, 'Value 1'),
    (1, 2, 'Value 1'),
    (1, 3, 'Value 1')
;
GO
/* A new view... */
CREATE VIEW JobWeeklysCustomFields AS
    SELECT w.Id, w.PeriodEndDate, CF.CompanyId, J.JobId, J.Name AS JobName, CF.Name AS CustomFieldName, JCF.CustomFieldValue
    FROM Weeklys W
        LEFT JOIN Jobs J on J.JobId = W.JobId
        LEFT JOIN JobsCustomFields JCF ON JCF.JobId = J.JobId
        LEFT JOIN CustomFields CF ON JCF.CustomFieldId = CF.CustomFieldId
GO
/* Define the Pivot Metadata... */
INSERT INTO JCPivot.TablesTo (schemaName, objectName, columnName , RowColAgg , AggFunction)
    VALUES ('dbo', 'JobWeeklysCustomFields', 'CustomFieldName', 'Col', NULL);
INSERT INTO JCPivot.TablesTo (schemaName, objectName, columnName , RowColAgg , AggFunction)
    VALUES ('dbo', 'JobWeeklysCustomFields', 'CustomFieldValue', 'Agg', 'max');
INSERT INTO JCPivot.TablesTo (schemaName, objectName, columnName , RowColAgg , AggFunction)
    VALUES ('dbo', 'JobWeeklysCustomFields', 'Id', 'Row', NULL);
INSERT INTO JCPivot.TablesTo (schemaName, objectName, columnName , RowColAgg , AggFunction)
    VALUES ('dbo', 'JobWeeklysCustomFields', 'PeriodEndDate', 'Row', NULL);
INSERT INTO JCPivot.TablesTo (schemaName, objectName, columnName , RowColAgg , AggFunction)
    VALUES ('dbo', 'JobWeeklysCustomFields', 'CompanyId', 'Row', NULL);
INSERT INTO JCPivot.TablesTo (schemaName, objectName, columnName , RowColAgg , AggFunction)
    VALUES ('dbo', 'JobWeeklysCustomFields', 'JobId', 'Row', NULL);
INSERT INTO JCPivot.TablesTo (schemaName, objectName, columnName , RowColAgg , AggFunction)
    VALUES ('dbo', 'JobWeeklysCustomFields', 'JobName', 'Row', NULL);

/** END CONFIG **/
/* Run the Procedure to generate/refresh the pivot view */
EXEC JCPivot.CreatePivotViews

/* Enjoy the fruits. */
SELECT * FROM JobWeeklysCustomFields_Pivot