如果一列不为空,则将多列转换为一行
If a column is not null then convert into a row for multiple columns
我有一个 table 如下所示,我希望将 col4 col5 和 col6 转置为行,但采用下面列出的特定模式
col1
col2
col3
col4
col5
col6
a
b
c
500
200
w
x
y
1000
300
z
g
h
200
600
我想把它转换成下面的样子
col1
col2
col3
col4
col5
col6
a
b
c
500
a
b
c
200
w
x
y
1000
w
x
y
300
z
g
h
200
z
g
h
600
我正在使用 unpivot 进行尝试,但无法获得所需的结果
基本上,如果在其中一列中找到空值,例如 col4 中的第一条记录,那么 SQL 查询应该忽略具有空值的 col4,但将 a b c col5 (500) 转置为一行加上 a b c col6 (200) 到另一行
您可以使用apply
然后过滤掉所有-NULL
值:
select t.col1, t.col2, t.col3, v.*
from t cross apply
(values (col4, null, null), (null, col5, null), (null, null, col6)
) v(col4, col5, col6)
where v.col4 is not null or v.col5 is not null or v.col6 is not null;
CROSS APPLY
结合 UNION ALL
在这里非常有用:
SELECT
t.col1, t.col2, t.col3,
v.*
FROM table t
CROSS APPLY (
SELECT col4, NULL, NULL
WHERE col4 IS NOT NULL
UNION ALL
SELECT NULL, col5, NULL
WHERE col5 IS NOT NULL
UNION ALL
SELECT NULL, NULL, col6
WHERE col6 IS NOT NULL
) v
如果您有很多列,这会很乏味。此外,这种 table 设计通常是不正确的。你需要的是一个简单的 UNPIVOT
:
SELECT
upvt.col1,
upvt.col2,
upvt.col3,
upvt.ColName,
upvt.Value
FROM table t
UNPIVOT ( Value FOR ColName IN
(Col4, Col5, Col6, Col7, Col8, Col9)
) upvt
通常有两种方法(好吧,三种):
- 使用大量输入来创建涵盖所有列的声明。这很丑但很快。最大的缺点:以后加一列会强制你重新查询。
- 使用适用于任意数量列的通用方法。最大的缺点:这个不会很快。
- 动态SQL:使用元数据创建建议1.动态的陈述。最大的缺点:这在 ad-hoc/inline 查询中永远不起作用。
为了向您展示一种通用方法,您可以对此进行测试:
DECLARE @tbl TABLE(col1 VARCHAR(10),col2 VARCHAR(10),col3 VARCHAR(10),col4 INT,col5 INT,col6 INT);
INSERT INTO @tbl VALUES
('a','b','c',NULL,500,200)
,('w','x','y',1000,300,NULL)
,('z','g','h',200,NULL,600);
--通用逆透视查询
SELECT t.col1,col2,col3
,ROW_NUMBER() OVER(PARTITION BY col1,col2,col3 ORDER BY B.attr) AS GroupIndex
,B.attr.value('local-name(.)','nvarchar(max)') ColumnName
,B.attr.value('.','int') ColumnValue
FROM @tbl t
CROSS APPLY(SELECT(SELECT t.* FOR XML RAW,TYPE)
.query('<cols>{/row/@*[not(local-name()=("col1","col2","col3"))]}</cols>')) A(x)
CROSS APPLY A.x.nodes('/cols/@*') B(attr);
--我们可以在 PIVOT
查询中使用
SELECT p.*
FROM
(
SELECT t.col1,col2,col3
,ROW_NUMBER() OVER(PARTITION BY col1,col2,col3 ORDER BY B.attr) AS GroupIndex
,B.attr.value('local-name(.)','nvarchar(max)') ColumnName
,B.attr.value('.','int') ColumnValue
FROM @tbl t
CROSS APPLY(SELECT(SELECT t.* FOR XML RAW,TYPE)
.query('<cols>{/row/@*[not(local-name()=("col1","col2","col3"))]}</cols>')) A(x)
CROSS APPLY A.x.nodes('/cols/@*') B(attr)
) intermediateResult
PIVOT
(
MAX(ColumnValue) FOR ColumnName IN(col4,col5,col6)
)p;
简而言之:
- 我们使用
APPLY
创建中间 XML 列表示 在 col1、col2 和 col3 之后。为了实现这一点,我们首先创建一个XML,并使用query()
将return除col1,col2,col3之外的所有列作为每行中的属性。
- 我们使用 XML 的默认值 省略 NULL 值
- 我们将其作为列
A.x
添加到结果集中。
- 另一个
APPLY
将调用XML方法.nodes()
。这将为每个现有属性添加一行。
intermediateResult
是一个 未旋转的 集合。 这是您实际应该使用的存储格式...
- 现在我们可以使用
PIVOT
来获得想要的输出。
最大的优点是,您可以添加 col7
而无需更改它(在通用部分)。 PIVOT
的输出列表将明确需要任何新列。
我有一个 table 如下所示,我希望将 col4 col5 和 col6 转置为行,但采用下面列出的特定模式
col1 | col2 | col3 | col4 | col5 | col6 |
---|---|---|---|---|---|
a | b | c | 500 | 200 | |
w | x | y | 1000 | 300 | |
z | g | h | 200 | 600 |
我想把它转换成下面的样子
col1 | col2 | col3 | col4 | col5 | col6 |
---|---|---|---|---|---|
a | b | c | 500 | ||
a | b | c | 200 | ||
w | x | y | 1000 | ||
w | x | y | 300 | ||
z | g | h | 200 | ||
z | g | h | 600 |
我正在使用 unpivot 进行尝试,但无法获得所需的结果
基本上,如果在其中一列中找到空值,例如 col4 中的第一条记录,那么 SQL 查询应该忽略具有空值的 col4,但将 a b c col5 (500) 转置为一行加上 a b c col6 (200) 到另一行
您可以使用apply
然后过滤掉所有-NULL
值:
select t.col1, t.col2, t.col3, v.*
from t cross apply
(values (col4, null, null), (null, col5, null), (null, null, col6)
) v(col4, col5, col6)
where v.col4 is not null or v.col5 is not null or v.col6 is not null;
CROSS APPLY
结合 UNION ALL
在这里非常有用:
SELECT
t.col1, t.col2, t.col3,
v.*
FROM table t
CROSS APPLY (
SELECT col4, NULL, NULL
WHERE col4 IS NOT NULL
UNION ALL
SELECT NULL, col5, NULL
WHERE col5 IS NOT NULL
UNION ALL
SELECT NULL, NULL, col6
WHERE col6 IS NOT NULL
) v
如果您有很多列,这会很乏味。此外,这种 table 设计通常是不正确的。你需要的是一个简单的 UNPIVOT
:
SELECT
upvt.col1,
upvt.col2,
upvt.col3,
upvt.ColName,
upvt.Value
FROM table t
UNPIVOT ( Value FOR ColName IN
(Col4, Col5, Col6, Col7, Col8, Col9)
) upvt
通常有两种方法(好吧,三种):
- 使用大量输入来创建涵盖所有列的声明。这很丑但很快。最大的缺点:以后加一列会强制你重新查询。
- 使用适用于任意数量列的通用方法。最大的缺点:这个不会很快。
- 动态SQL:使用元数据创建建议1.动态的陈述。最大的缺点:这在 ad-hoc/inline 查询中永远不起作用。
为了向您展示一种通用方法,您可以对此进行测试:
DECLARE @tbl TABLE(col1 VARCHAR(10),col2 VARCHAR(10),col3 VARCHAR(10),col4 INT,col5 INT,col6 INT);
INSERT INTO @tbl VALUES
('a','b','c',NULL,500,200)
,('w','x','y',1000,300,NULL)
,('z','g','h',200,NULL,600);
--通用逆透视查询
SELECT t.col1,col2,col3
,ROW_NUMBER() OVER(PARTITION BY col1,col2,col3 ORDER BY B.attr) AS GroupIndex
,B.attr.value('local-name(.)','nvarchar(max)') ColumnName
,B.attr.value('.','int') ColumnValue
FROM @tbl t
CROSS APPLY(SELECT(SELECT t.* FOR XML RAW,TYPE)
.query('<cols>{/row/@*[not(local-name()=("col1","col2","col3"))]}</cols>')) A(x)
CROSS APPLY A.x.nodes('/cols/@*') B(attr);
--我们可以在 PIVOT
查询中使用
SELECT p.*
FROM
(
SELECT t.col1,col2,col3
,ROW_NUMBER() OVER(PARTITION BY col1,col2,col3 ORDER BY B.attr) AS GroupIndex
,B.attr.value('local-name(.)','nvarchar(max)') ColumnName
,B.attr.value('.','int') ColumnValue
FROM @tbl t
CROSS APPLY(SELECT(SELECT t.* FOR XML RAW,TYPE)
.query('<cols>{/row/@*[not(local-name()=("col1","col2","col3"))]}</cols>')) A(x)
CROSS APPLY A.x.nodes('/cols/@*') B(attr)
) intermediateResult
PIVOT
(
MAX(ColumnValue) FOR ColumnName IN(col4,col5,col6)
)p;
简而言之:
- 我们使用
APPLY
创建中间 XML 列表示 在 col1、col2 和 col3 之后。为了实现这一点,我们首先创建一个XML,并使用query()
将return除col1,col2,col3之外的所有列作为每行中的属性。 - 我们使用 XML 的默认值 省略 NULL 值
- 我们将其作为列
A.x
添加到结果集中。 - 另一个
APPLY
将调用XML方法.nodes()
。这将为每个现有属性添加一行。 intermediateResult
是一个 未旋转的 集合。 这是您实际应该使用的存储格式...- 现在我们可以使用
PIVOT
来获得想要的输出。
最大的优点是,您可以添加 col7
而无需更改它(在通用部分)。 PIVOT
的输出列表将明确需要任何新列。