首页 文章

如何透过sql数据,并按每个ID的日期将结果压缩到非空行

提问于
浏览
0

这个帖子不是一个好头衔,但希望它会引起一些注意 .

我在T-SQL中遇到了一个非常复杂的情况,我无法完成 . 我'm hoping someone with expertise knows an elegant and fast solution so that my performance is not impacted. I' m处理数十亿行 .

PREFACE

我有一个名为 Customers 的表,其中包含唯一的ID . 这些客户有 Files ,文件有属性,每个属性 Name 对应一个 Value .

Tables:

  • 客户

  • 档案 -

  • 属性 - 包含名称和值

Customer ID 出现在所有这些表中,审计字段如 UpdatedDtmCreationDtm 也是如此 .

USE CASE

我需要将所有客户加入他们的文件(过滤一些),然后将每个文件绑定到他们的属性(再次过滤这些文件) . 这很容易,但会产生很多行,每个行一个 customer x file x property .

我知道属性名称永远不会改变,我想只返回少数几个,所以我使用了一个数据透镜并产生了一个漂亮的表,但是在我开始执行更复杂的查询后它就崩溃了 .

THE PROBLEM 首先,属性具有更改它们的日期时间( UpdatedDtm ),并且我需要在File表中返回从创建日期的1小时( CreationDtm )更改的所有内容 .

这导致我减少了我的潜在属性列表,但是现在我有一个每个ID为 RowNumber() 的表,并且没有好的方法来转动并选择第一个使用 Dynamic SQL 并且将它放在索引的临时表中 . Composite KeyCustomerIDFileName .

BEFORE PIVOT

| 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           |

请不要问我为什么以这种方式设置数据库或更改架构 . 这是一成不变的,是我无法控制的 . 我需要能够解决所描述的用例 .

AFTER PIVOT (Expectation)

| 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值和缺失值来展示我需要保留相同的柱状属性而不管它们是否有数据,但我还需要按照日期范围内的第一个非null值来压缩数据 .

CODE (My attempt)

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语句的组合来尝试为每一行选择第一个非空值,以便数据全部在一行上,但最终更像是 .

FileOne NULL NULL Open NULL
FileOne NULL JPG  NULL NULL

等等...

我一直在努力解决这个特殊情况一段时间,我即将废弃它,它做了一些程序化的循环,但这会杀死我的查询时间和性能 .

谁有一个很好的解决方案?我是否过度思考?

2 回答

  • 0

    你应该在 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
    

    这是一个嵌套的查询版本,可以更容易嵌入/放入动态的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
    
  • 1

    您可能需要在CTE定义中添加WHERE条件,以将日期/时间范围限制为所需的范围 .

    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
    

相关问题