如何旋转 sql 数据,并将结果按每个 ID 的日期压缩为 non-null 行
How to pivot sql data, and squash results into non-null rows by Date per ID
post 的标题不是很好,但希望它能引起一些注意。
我在 T-SQL 中有一个非常复杂的情况,我无法完成。我希望有专业知识的人知道一个优雅而快速的解决方案,这样我的表现就不会受到影响。我正在处理 十亿行 .
前言
我有一个名为 Customers 的 table,具有唯一 ID。这些客户有 Files,Files 有 Properties,每个 属性 Name 对应一个 Value.
表格:
- 客户
- 文件 -
- 属性 - 包含名称和值
客户 ID 存在于所有这些 table 中,审计字段如 UpdatedDtm 和 CreationDtm.
用例
我需要将所有客户加入他们的文件(过滤一些),然后将每个文件绑定到他们的属性(再次过滤这些)。这很简单,但是会产生很多行,每个 customer x file x 属性.
我知道 属性 名字永远不会改变,我想 return 只是 select 几个,所以我使用了一个枢轴并得到了一个很好的 table, 但在我开始做更复杂的查询后它崩溃了。
问题
首先,这些属性有一个 DateTime,表示它们何时被更改 (UpdatedDtm),我需要 return 从创建日期的 1 小时开始更改的所有内容 (文件 table.
中的 CreationDtm)
这导致我缩减了我的潜在属性列表,但现在我有一个 table 每个 ID 有一个 RowNumber() 并且没有好的方法来转换select 第一个不为 null 并且仍然保留 table 定义的列数。这很重要,因为我正在使用 Dynamic SQL 并将其放置在带有 Composite Key 的索引温度 table 中在 CustomerID 和 FileName 上。
枢轴前
| UpdatedDtm | CustomerID | FileName | Property | Value |
| ---------- | ---------- | ---------- | -------- | -------------- |
| 1/1/2015 | 1 | FileOne | Size | NULL |
| 1/1/2015 | 1 | FileOne | Format | JPG |
| 1/7/2015 | 1 | FileOne | Size | 88KB |
| 1/7/2015 | 1 | FileOne | Format | JPG |
| 1/7/2015 | 1 | FileOne | Comment | NULL |
| 1/11/2015 | 1 | FileOne | Comment | NULL |
| 1/1/2015 | 1 | FileTwo | Size | 91KB |
| 1/1/2015 | 1 | FileTwo | Format | PNG |
| 1/11/2015 | 1 | FileTwo | Comment | NULL |
| 1/2/2015 | 2 | FileThree | Size | 74KB |
| 1/2/2015 | 2 | FileThree | Format | XLS |
| 1/2/2015 | 2 | FileThree | State | Open |
| 1/7/2015 | 2 | FileThree | State | Closed |
| 1/10/2015 | 2 | FileThree | Comment | NULL |
| 1/1/2015 | 3 | FileFour | Size | 2KB |
| 1/2/2015 | 3 | FileFour | Size | 10KB |
| 1/3/2015 | 3 | FileFour | Size | 13KB |
| 1/4/2015 | 3 | FileFour | Size | 21KB |
| 1/5/2015 | 3 | FileFour | Size | 27KB |
| 1/6/2015 | 3 | FileFour | Size | 32KB |
| 1/7/2015 | 3 | FileFour | Size | 39KB |
| 1/8/2015 | 3 | FileFour | Size | 44KB |
| 1/1/2015 | 3 | FileFour | Format | TXT |
| 1/1/2015 | 3 | FileFour | Comment | NULL |
请不要问我为什么要这样设置数据库或更改架构。那是一成不变的,我无法控制。我需要能够按照描述解决用例。
枢轴后(预期)
| CustomerID | FileName | Size | Format | State | Comment |
| ---------- | ---------- | ---- | ------ | ------ | ------- |
| 1 | FileOne | 88KB | JPG | NULL | NULL |
| 1 | FileTwo | 91KB | PNG | NULL | NULL |
| 2 | FileThree | 74KB | XLS | Closed | NULL |
| 3 | FileFour | 44KB | TXT | NULL | NULL |
我已经包含了一些 NULL 值和缺失值来展示我需要保留相同的柱状属性,无论它们是否有数据,但我还需要通过第一个 non-null 值压缩数据我的日期范围。
CODE(我的尝试)
IF Object_id('tempdb..#FilesQuery') IS NOT NULL DROP TABLE #FilesQuery;
CREATE TABLE #FilesQuery (
SeqNum int,
CustomerID numeric(16,0),
FileName varchar(64),
PropertyName varchar(64),
PropertyValue varchar(64)
)
INSERT INTO #FilesQuery
SELECT
CASE WHEN P.[Value] IS NOT NULL
THEN ROW_NUMBER() OVER (partition by C.CustomerID order by UpdatedDtm)
ELSE 0
END as SeqNum,
C.CustomerID
,F.Name as FileName
,P.Name as PropertyName
,P.Value as PropertyValue
FROM Customers C
INNER JOIN Files F ON F.CustomerID = C.CustomerID
LEFT JOIN Properties P
ON P.CustomerID = C.CustomerID
AND P.FileID = F.FileID
WHERE F.FileName IN ('FileOne','FileTwo','FileThree','FileFour')
AND P.Name IN ('Size','Format','State','Comment')
--PIVOT
DECLARE @cols AS nvarchar(MAX)
SELECT @cols = STUFF(
(SELECT DISTINCT ',' + QUOTENAME(PropertyName)
FROM #FilesQuery fq
FOR XML PATH(''),TYPE).value('.','NVARCHAR(MAX)'),1,1,'')
DECLARE @dynSql AS nvarchar(MAX)
SET @dynSql = '
SELECT DISTINCT *
FROM (
SELECT
fq.CustomerID,
fq.FileName,
fq.PropertyName,
fq.PropertyValue
FROM #FilesQuery fq
) SRC
PIVOT (
Max([PropertyValue])
FOR PropertyName IN (' + @cols + ')
) PVT
'
IF Object_id('tempdb..#Results') IS NOT NULL DROP TABLE #Results;
CREATE TABLE #Results (
CustomerID varchar(16) NOT NULL,
FileName varchar(64) NOT NULL,
FileSize varchar(64) NULL,
FileFormat varchar(64) NULL,
FileState varchar(64) NULL,
FileComment varchar(64) NULL,
CONSTRAINT pk_CustDoc PRIMARY KEY (CustomerID,FileName)
)
INSERT INTO #Results EXEC @dynSql;
很抱歉这段代码不完整,这是我的工作部分。我进行的其他尝试导致数据提取错误。
我尝试使用 SeqNum 和 case 语句的组合来尝试 select 每行的第一个 non-null 值,以便数据全部在一行上,但最终变得更多喜欢。
FileOne NULL NULL Open NULL
FileOne NULL JPG NULL NULL
等等...
一段时间以来,我一直在努力解决这个特殊情况,我即将放弃它,它会通过循环执行一些程序性的操作,但这会降低我的查询时间和性能。
谁有好的解决办法?我是over-thinking东西吗?
你应该在 PIVOT
之前过滤你的数据,你会得到你想要的结果。这是一个cte版本,向您展示如何获得所需内容的步骤。
;WITH cteDefineRowPrecedence AS (
SELECT *
,ROW_NUMBER() OVER (PARTITION BY CustomerId, FileName, Property ORDER BY
CASE WHEN Value IS NOT NULL THEN 0 ELSE 1 END
,UpdatedDtm DESC) as RowNum
FROM
@Table
)
, cteDesiredRwows AS (
SELECT
CustomerId
,FileName
,Property
,Value
FROM
cteDefineRowPrecedence t
WHERE
t.RowNum = 1
AND t.Value IS NOT NULL
)
SELECT *
FROM
cteDesiredRwows t
PIVOT (
MAX(Value)
FOR Property IN (Size,[Format],[State],Comment)
) p
ORDER BY
CustomerId
,FileName
这是一个嵌套查询版本,可以让您更轻松地 embed/put 在您的动态 sql...
SELECT *
FROM
(
SELECT CustomerId, FileName, Property, Value
FROM
(SELECT *
,ROW_NUMBER() OVER (PARTITION BY CustomerId, FileName, Property ORDER BY
CASE WHEN Value IS NOT NULL THEN 0 ELSE 1 END
,UpdatedDtm DESC) as RowNum
FROM
@Table) r
WHERE
r.RowNum = 1
AND r.Value IS NOT NULL
) t
PIVOT (
MAX(Value)
FOR Property IN (Size,[Format],[State],Comment)
) p
ORDER BY
CustomerId
,FileName
您可能需要在 CTE 定义中添加 WHERE 条件以将 date/time 范围限制为您想要的范围。
WITH CTE AS (
SELECT DISTINCT
CustomerID
, FileName
, Property
, Value
FROM
<table_name>
)
SELECT *
FROM
CTE
PIVOT (MAX(value) FOR Property IN( 'Size', 'Format', 'State', 'Comment')) p
post 的标题不是很好,但希望它能引起一些注意。
我在 T-SQL 中有一个非常复杂的情况,我无法完成。我希望有专业知识的人知道一个优雅而快速的解决方案,这样我的表现就不会受到影响。我正在处理 十亿行 .
前言
我有一个名为 Customers 的 table,具有唯一 ID。这些客户有 Files,Files 有 Properties,每个 属性 Name 对应一个 Value.
表格:
- 客户
- 文件 -
- 属性 - 包含名称和值
客户 ID 存在于所有这些 table 中,审计字段如 UpdatedDtm 和 CreationDtm.
用例
我需要将所有客户加入他们的文件(过滤一些),然后将每个文件绑定到他们的属性(再次过滤这些)。这很简单,但是会产生很多行,每个 customer x file x 属性.
我知道 属性 名字永远不会改变,我想 return 只是 select 几个,所以我使用了一个枢轴并得到了一个很好的 table, 但在我开始做更复杂的查询后它崩溃了。
问题
首先,这些属性有一个 DateTime,表示它们何时被更改 (UpdatedDtm),我需要 return 从创建日期的 1 小时开始更改的所有内容 (文件 table.
这导致我缩减了我的潜在属性列表,但现在我有一个 table 每个 ID 有一个 RowNumber() 并且没有好的方法来转换select 第一个不为 null 并且仍然保留 table 定义的列数。这很重要,因为我正在使用 Dynamic SQL 并将其放置在带有 Composite Key 的索引温度 table 中在 CustomerID 和 FileName 上。
枢轴前
| UpdatedDtm | CustomerID | FileName | Property | Value |
| ---------- | ---------- | ---------- | -------- | -------------- |
| 1/1/2015 | 1 | FileOne | Size | NULL |
| 1/1/2015 | 1 | FileOne | Format | JPG |
| 1/7/2015 | 1 | FileOne | Size | 88KB |
| 1/7/2015 | 1 | FileOne | Format | JPG |
| 1/7/2015 | 1 | FileOne | Comment | NULL |
| 1/11/2015 | 1 | FileOne | Comment | NULL |
| 1/1/2015 | 1 | FileTwo | Size | 91KB |
| 1/1/2015 | 1 | FileTwo | Format | PNG |
| 1/11/2015 | 1 | FileTwo | Comment | NULL |
| 1/2/2015 | 2 | FileThree | Size | 74KB |
| 1/2/2015 | 2 | FileThree | Format | XLS |
| 1/2/2015 | 2 | FileThree | State | Open |
| 1/7/2015 | 2 | FileThree | State | Closed |
| 1/10/2015 | 2 | FileThree | Comment | NULL |
| 1/1/2015 | 3 | FileFour | Size | 2KB |
| 1/2/2015 | 3 | FileFour | Size | 10KB |
| 1/3/2015 | 3 | FileFour | Size | 13KB |
| 1/4/2015 | 3 | FileFour | Size | 21KB |
| 1/5/2015 | 3 | FileFour | Size | 27KB |
| 1/6/2015 | 3 | FileFour | Size | 32KB |
| 1/7/2015 | 3 | FileFour | Size | 39KB |
| 1/8/2015 | 3 | FileFour | Size | 44KB |
| 1/1/2015 | 3 | FileFour | Format | TXT |
| 1/1/2015 | 3 | FileFour | Comment | NULL |
请不要问我为什么要这样设置数据库或更改架构。那是一成不变的,我无法控制。我需要能够按照描述解决用例。
枢轴后(预期)
| CustomerID | FileName | Size | Format | State | Comment |
| ---------- | ---------- | ---- | ------ | ------ | ------- |
| 1 | FileOne | 88KB | JPG | NULL | NULL |
| 1 | FileTwo | 91KB | PNG | NULL | NULL |
| 2 | FileThree | 74KB | XLS | Closed | NULL |
| 3 | FileFour | 44KB | TXT | NULL | NULL |
我已经包含了一些 NULL 值和缺失值来展示我需要保留相同的柱状属性,无论它们是否有数据,但我还需要通过第一个 non-null 值压缩数据我的日期范围。
CODE(我的尝试)
IF Object_id('tempdb..#FilesQuery') IS NOT NULL DROP TABLE #FilesQuery;
CREATE TABLE #FilesQuery (
SeqNum int,
CustomerID numeric(16,0),
FileName varchar(64),
PropertyName varchar(64),
PropertyValue varchar(64)
)
INSERT INTO #FilesQuery
SELECT
CASE WHEN P.[Value] IS NOT NULL
THEN ROW_NUMBER() OVER (partition by C.CustomerID order by UpdatedDtm)
ELSE 0
END as SeqNum,
C.CustomerID
,F.Name as FileName
,P.Name as PropertyName
,P.Value as PropertyValue
FROM Customers C
INNER JOIN Files F ON F.CustomerID = C.CustomerID
LEFT JOIN Properties P
ON P.CustomerID = C.CustomerID
AND P.FileID = F.FileID
WHERE F.FileName IN ('FileOne','FileTwo','FileThree','FileFour')
AND P.Name IN ('Size','Format','State','Comment')
--PIVOT
DECLARE @cols AS nvarchar(MAX)
SELECT @cols = STUFF(
(SELECT DISTINCT ',' + QUOTENAME(PropertyName)
FROM #FilesQuery fq
FOR XML PATH(''),TYPE).value('.','NVARCHAR(MAX)'),1,1,'')
DECLARE @dynSql AS nvarchar(MAX)
SET @dynSql = '
SELECT DISTINCT *
FROM (
SELECT
fq.CustomerID,
fq.FileName,
fq.PropertyName,
fq.PropertyValue
FROM #FilesQuery fq
) SRC
PIVOT (
Max([PropertyValue])
FOR PropertyName IN (' + @cols + ')
) PVT
'
IF Object_id('tempdb..#Results') IS NOT NULL DROP TABLE #Results;
CREATE TABLE #Results (
CustomerID varchar(16) NOT NULL,
FileName varchar(64) NOT NULL,
FileSize varchar(64) NULL,
FileFormat varchar(64) NULL,
FileState varchar(64) NULL,
FileComment varchar(64) NULL,
CONSTRAINT pk_CustDoc PRIMARY KEY (CustomerID,FileName)
)
INSERT INTO #Results EXEC @dynSql;
很抱歉这段代码不完整,这是我的工作部分。我进行的其他尝试导致数据提取错误。
我尝试使用 SeqNum 和 case 语句的组合来尝试 select 每行的第一个 non-null 值,以便数据全部在一行上,但最终变得更多喜欢。
FileOne NULL NULL Open NULL
FileOne NULL JPG NULL NULL
等等...
一段时间以来,我一直在努力解决这个特殊情况,我即将放弃它,它会通过循环执行一些程序性的操作,但这会降低我的查询时间和性能。
谁有好的解决办法?我是over-thinking东西吗?
你应该在 PIVOT
之前过滤你的数据,你会得到你想要的结果。这是一个cte版本,向您展示如何获得所需内容的步骤。
;WITH cteDefineRowPrecedence AS (
SELECT *
,ROW_NUMBER() OVER (PARTITION BY CustomerId, FileName, Property ORDER BY
CASE WHEN Value IS NOT NULL THEN 0 ELSE 1 END
,UpdatedDtm DESC) as RowNum
FROM
@Table
)
, cteDesiredRwows AS (
SELECT
CustomerId
,FileName
,Property
,Value
FROM
cteDefineRowPrecedence t
WHERE
t.RowNum = 1
AND t.Value IS NOT NULL
)
SELECT *
FROM
cteDesiredRwows t
PIVOT (
MAX(Value)
FOR Property IN (Size,[Format],[State],Comment)
) p
ORDER BY
CustomerId
,FileName
这是一个嵌套查询版本,可以让您更轻松地 embed/put 在您的动态 sql...
SELECT *
FROM
(
SELECT CustomerId, FileName, Property, Value
FROM
(SELECT *
,ROW_NUMBER() OVER (PARTITION BY CustomerId, FileName, Property ORDER BY
CASE WHEN Value IS NOT NULL THEN 0 ELSE 1 END
,UpdatedDtm DESC) as RowNum
FROM
@Table) r
WHERE
r.RowNum = 1
AND r.Value IS NOT NULL
) t
PIVOT (
MAX(Value)
FOR Property IN (Size,[Format],[State],Comment)
) p
ORDER BY
CustomerId
,FileName
您可能需要在 CTE 定义中添加 WHERE 条件以将 date/time 范围限制为您想要的范围。
WITH CTE AS (
SELECT DISTINCT
CustomerID
, FileName
, Property
, Value
FROM
<table_name>
)
SELECT *
FROM
CTE
PIVOT (MAX(value) FOR Property IN( 'Size', 'Format', 'State', 'Comment')) p