将未定义的行数转置为列
Posted
技术标签:
【中文标题】将未定义的行数转置为列【英文标题】:Transposing Undefined Number of Rows to Columns 【发布时间】:2014-03-12 14:44:33 【问题描述】:我有一个如下所示的表格:
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
成员最多可以拥有一份合约,并且合约数量没有上限。 我想将表格切换为如下所示:
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
感谢您提供的任何帮助。
【问题讨论】:
理想情况下在您的报告层或消费应用程序中进行。 SQL 不擅长处理不同长度的记录。 您必须准备动态 SQL 以动态提供 Select 和 PIVOT 列列表。只是好奇,以这种方式转置数据的基本需求是什么。因为当您查看 Member-1 的转置数据时,可能有 10 个合同日期,而 member-2 可能只有 1 个。所以 9 列将为空。 Anup,我对此的基本需求是确定会员合同覆盖范围的差距。 什么数据库?后格雷斯?甲骨文? SQLite? 【参考方案1】:我已经使用过这样的解决方案,建议不要走这条路,因为长时间维护/调试这种动态 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
【讨论】:
【参考方案2】:我有一个 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;
【讨论】:
以上是关于将未定义的行数转置为列的主要内容,如果未能解决你的问题,请参考以下文章