如果一列不为空,则转换为多列的行
Posted
技术标签:
【中文标题】如果一列不为空,则转换为多列的行【英文标题】:If a column is not null then convert into a row for multiple columns 【发布时间】:2021-01-21 01:44:11 【问题描述】:我有一个如下表所示的表格,我希望将 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)进入另一行
【问题讨论】:
向我们展示您正在尝试的查询。 请不要使用图像作为数据,它是完美的,因为它是格式化的文本。 请不要使用表格图片。我们最近添加了一个表格降价,如果您使用它,它会对我们有所帮助,因为我们可以很容易地查看和提取我们需要的数据。请看:Uploading images of tables considered harmful? “第一条记录”是什么意思?表中的行没有顺序。 【参考方案1】: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
如果您有很多列,这将变得乏味。此外,这种类型的表格设计通常是不正确的。你需要的是一个简单明了的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
【讨论】:
我在原始表中有 3000 条记录,所以我应该为要转换为行的 30 列组合编写 3000 条联合所有语句吗?还有其他更好的方法吗?跨度> 不,您应该转入一列。那我可以给你一个更简单的解决方案 谢谢,我可以取消透视到单个列,但是我需要带有值的原始列名标签,以便我知道哪个值属于哪个列。如果我取消透视并将所有列的所有值放在 1 列下,它不会帮助我区分它们。 不确定你是否理解UNPIVOT
,它有列名,见sqlfiddle.com/#!18/0bee8/3/0
知道了,谢谢。我误解了您将所有值放入 1 列,因为这是我已经尝试过并且与之相关的内容。我会试试你的 SQL 并在明天回复你,但这应该可以。非常感谢大家的帮助。【参考方案2】:
您可以使用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;
【讨论】:
感谢您的回复,我要转换的原始表中有数千条记录和大约 50 列。如果我必须使用值,那将是太多的组合/概率 @msv 听起来是个糟糕的设计...一定有更好的方法... @msv 。 . .它是 50 个“组合”,每列一个,正如您所说的那样。 是的,它是一个旧表,我正在尝试对其进行永久转换并将其加载回新目标【参考方案3】:一般来说有两种方法(嗯,应该是三种):
-
使用大量输入来创建涵盖所有列的语句。这很丑陋但很快。最大的缺点:将来添加一列将迫使您重新进行查询。
使用通用方法,该方法适用于任意数量的列。最大的缺点:这不会很快。
动态 SQL:使用元数据创建建议 1 的语句。动态。最大的缺点:这在即席/内联查询中永远不会起作用。
为了向您展示一种通用方法,您可以对此进行测试:
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
为您的列在 col1、col2 和col3 创建中间XML 表示。为了实现这一点,我们首先创建一个 XML 并使用query()
将除 col1、col2、col3 之外的所有列作为每行的属性返回。
我们使用 XML 的默认值省略 NULL 值
我们将此作为列 A.x
添加到结果集中。
另一个APPLY
将调用XML 方法.nodes()
。这将为每个现有属性添加一行。
intermediateResult
是一个未透视 集。 这是您实际上应该用于存储的格式...
现在我们可以使用PIVOT
来获得所需的输出。
最大的优点是,您可以添加col7
而无需更改它(在通用部分)。 PIVOT
的输出列表将明确需要任何新列。
【讨论】:
感谢您的选择,上面带有简单 unpivot 的查询对我有用。以上是关于如果一列不为空,则转换为多列的行的主要内容,如果未能解决你的问题,请参考以下文章