将未定义的行数转换为列

时间:2014-03-12 14:44:33

标签: sql dynamic pivot transpose

我的表格如下所示:

Member, Contract_Start, Contract_End  
1,  1/1/2011,   12/30/2011  
1,  1/1/2012,   12/30/2012  
1,  1/1/2013,   12/30/2013  
2,  7/1/2012,   12/30/2012  
2,  1/1/2013,   12/30/2013  

成员可以只有1份合同,合同数量没有上限。 我想将表格切换如下:

Member, Contract_Start1, Contract_End1, Contract_Start2, Contract_End2.....   
1, 1/1/2011, 12/30/2011, 1/1/2012, 12/30/2012  
2, 7/1/2012, 12/30/2012, 1/1/2013, 12/30/2013  

感谢您提供任何帮助。

2 个答案:

答案 0 :(得分:0)

我已经使用过这样的解决方案,并建议不要走这条路,因为这样的动态sql的维护/调试会长时间变硬。

您可以尝试演示Here

    IF object_id('Test_Transpose') IS NOT NULL
        DROP TABLE Test_Transpose
    GO
    CREATE TABLE Test_Transpose
    (
        memberID    INT NOT NULL
        ,csdate     datetime2(2) NULL
        ,cedate     datetime2(2) NULL
    )
    INSERT INTO Test_Transpose (memberid,csdate,cedate)
                SELECT 1,'1/1/2001','1/1/2001'
    UNION ALL   SELECT 1,'1/2/2001','1/2/2001'
    UNION ALL   SELECT 1,'1/3/2001','1/2/2001'
    UNION ALL   SELECT 1,'1/4/2001','1/2/2001'
    UNION ALL   SELECT 1,'1/5/2001','1/2/2001'
    UNION ALL   SELECT 2,'1/2/2001','1/2/2001'
    UNION ALL   SELECT 2,'1/3/2001','1/3/2001'
    UNION ALL   SELECT 3,'1/2/2001','1/2/2001'
    UNION ALL   SELECT 3,'1/3/2001','1/3/2001'
    UNION ALL   SELECT 3,'1/4/2001','1/4/2001'
    UNION ALL   SELECT 4,'1/2/2001','1/2/2001'


    DECLARE @SQL NVARCHAR(MAX)=''
            ,@Startdate_SelectColumnList NVARCHAR(MAX)=''
            ,@EndDate_SelectColumnList NVARCHAR(MAX)=''
            ,@Final_SelectColumnList NVARCHAR(MAX)=''
            ,@PivotINColumnList NVARCHAR(MAX)=''
            ,@MaxContractDateCount INT=1

    SELECT TOP 1 @MaxContractDateCount = COUNT(1)
    FROM Test_Transpose
    GROUP BY memberid
    ORDER BY COUNT(1) desc


    WHILE @MaxContractDateCount > 0
    BEGIN
        SELECT @Startdate_SelectColumnList =    N'['+CAST(@MaxContractDateCount AS sysname)+N'] AS '
                                                +N'StartDate'+CAST(@MaxContractDateCount AS sysname)
                                                +CASE WHEN @Startdate_SelectColumnList=N'' THEN N'' ELSE N',' END
                                                +@Startdate_SelectColumnList

        SELECT @Enddate_SelectColumnList =      N'['+CAST(@MaxContractDateCount AS sysname)+N'] AS '
                                                +N'EndDate'+CAST(@MaxContractDateCount AS sysname)
                                                +CASE WHEN @Enddate_SelectColumnList=N'' THEN N'' ELSE N',' END
                                                +@Enddate_SelectColumnList

        SELECT @Final_SelectColumnList =        N'StartDate'+CAST(@MaxContractDateCount AS sysname)+N','
                                                +N'EndDate'+CAST(@MaxContractDateCount AS sysname)
                                                +CASE WHEN @Final_SelectColumnList=N'' THEN N'' ELSE N',' END
                                                +@Final_SelectColumnList

        SELECT @PivotINColumnList =             N'['+CAST(@MaxContractDateCount AS sysname)+N']'
                                                +CASE WHEN @PivotINColumnList=N'' THEN N'' ELSE N',' END
                                                +@PivotINColumnList

        SET @MaxContractDateCount=@MaxContractDateCount-1
    END

    --debug stmt
    --SELECT @Startdate_SelectColumnList,@Enddate_SelectColumnList,@Final_SelectColumnList,@PivotINColumnList

    SET @SQL = N'
                SELECT q1.memberid,'
                +@Final_SelectColumnList
                +N'
                FROM
                (
                    SELECT  memberid,'
                +@Startdate_SelectColumnList
                +N'
                    FROM
                    (
                    SELECT memberid,csdate,ROW_NUMBER() OVER (PARTITION BY memberid ORDER BY memberid,csdate) rowid
                    FROM test_transpose
                    )q
                    PIVOT
                    (MAX(csdate) FOR rowid IN ('+@PivotINColumnList+N'))pvt
                )q1
                JOIN
                (
                    SELECT  memberid,'
                +@Enddate_SelectColumnList
                +N'
                    FROM
                    (
                    SELECT memberid,cedate,ROW_NUMBER() OVER (PARTITION BY memberid ORDER BY memberid,csdate) rowid
                    FROM test_transpose
                    )q
                    PIVOT
                    (MAX(cedate) FOR rowid IN ('+@PivotINColumnList+N'))pvt
                )q2
                ON q1.memberid = q2.memberid
                '
    PRINT @SQL
    EXECUTE sp_executesql @SQL

答案 1 :(得分:0)

我有一个版本的Postgres db明智地完成这项工作。这是我的代码示例。

DROP TABLE IF EXISTS x;
CREATE TABLE x ( member NUMERIC , contract_start DATE, contract_end DATE);

INSERT INTO x VALUES( 1, '1/1/2011', '12/30/2011' )
,( 1, '1/1/2012', '12/30/2012' )
,( 1, '1/1/2013', '12/30/2013' )
,( 2, '7/1/2012', '12/30/2012' )
,( 2, '1/1/2013', '12/30/2013' )
,( 3, '1/1/2012', '12/30/2012' )
,( 3, '1/1/2013', '12/30/2013' )
,( 3, '8/1/2013', '12/30/2013' )
,( 3, '8/1/2013', '12/30/2013' );

-- CREATE LANGUAGE 'plpgsql';
-- DROP SEQUENCE IF EXISTS seq;
-- CREATE SEQUENCE seq;

CREATE OR REPLACE FUNCTION make_table() RETURNS void AS
$BODY$
DECLARE
  i RECORD;
  colcnt INTEGER;
BEGIN
  DROP TABLE IF EXISTS tmptable;
  SELECT max(count) INTO colcnt FROM (SELECT count(*) FROM x GROUP BY member) a;
  EXECUTE 'CREATE TABLE tmptable (member integer ,' || 
    (SELECT array_to_string(array_agg(x1.col1 || ' date, ' || x1.col2 || ' date'), ', ')
      FROM (SELECT 'contract_start' || col col1
      , 'contract_end'|| col col2 FROM generate_series(1,colcnt) t(col) ) x1 ) || ')';

  EXECUTE 'INSERT INTO tmptable (member) SELECT DISTINCT member FROM x';

  FOR i IN SELECT * FROM x ORDER BY member LOOP
    PERFORM setval('seq',1);
    EXECUTE 'UPDATE tmptable SET ' || x1.col 
      FROM (SELECT array_to_string(array_agg(' contract_start' 
      || nextval('seq')-1 || ' = ''' || i.contract_start || ''', contract_end' 
      || currval('seq')-1 || ' = ''' || i.contract_end ), ''', ') || ''' WHERE member = ' || member || ';' col 
      FROM x WHERE member = i.member GROUP BY member) x1;
  END LOOP;
END;
$BODY$ LANGUAGE plpgsql;

SELECT * FROM make_table();
SELECT * FROM tmptable;

这是我第一次回答。希望这是相关的。 (请只运行一次评论)。

此外,每次插入,更新或删除时调用函数的过程都可以通过触发器自动执行。以下代码将替换上面的代码。

-- following commented lines for first time run :
-- CREATE LANGUAGE 'plpgsql';
-- DROP SEQUENCE IF EXISTS seq;
-- CREATE SEQUENCE seq;
-- DROP FUNCTION IF EXISTS make_table();

CREATE OR REPLACE FUNCTION make_table() RETURNS TRIGGER AS
$BODY$
DECLARE
  i RECORD;
  colcnt INTEGER;
BEGIN
  DROP TABLE IF EXISTS tmptable;
  SELECT max(count) INTO colcnt FROM (SELECT count(*) FROM x GROUP BY member) a;
  EXECUTE 'CREATE TABLE tmptable (member integer ,' || 
    (SELECT array_to_string(array_agg(x1.col1 || ' date, ' || x1.col2 || ' date'), ', ')
      FROM (SELECT 'contract_start' || col col1
      , 'contract_end'|| col col2 FROM generate_series(1,colcnt) t(col) ) x1 ) || ')';

  EXECUTE 'INSERT INTO tmptable (member) SELECT DISTINCT member FROM x';

  FOR i IN SELECT * FROM x ORDER BY member LOOP
    PERFORM setval('seq',1);
    EXECUTE 'UPDATE tmptable SET ' || x1.col 
      FROM (SELECT array_to_string(array_agg(' contract_start' 
      || nextval('seq')-1 || ' = ''' || i.contract_start || ''', contract_end' 
      || currval('seq')-1 || ' = ''' || i.contract_end ), ''', ') || ''' WHERE member = ' || member || ';' col 
      FROM x WHERE member = i.member GROUP BY member) x1;
  END LOOP;
  RETURN new;
END;
$BODY$ LANGUAGE plpgsql;

CREATE TRIGGER create_tmptable AFTER INSERT OR UPDATE OR DELETE OR TRUNCATE 
ON x FOR STATEMENT EXECUTE PROCEDURE make_table();

-- For Test insert:
INSERT INTO x VALUES( 4, '1/1/2011', '12/30/2011' );
SELECT * FROM tmptable;