将多个链接记录中的多个字符串连接成每个记录链的单个字符串的有效方法

时间:2016-10-31 12:08:24

标签: sql sql-server

我有一张表格,其中包含与员工缺勤详情有关的记录;更新缺席时,而不是更新现有行;而是创建一个新行,并使用新记录的Id填充前一记录的LinkedRecordId字段,LinkedRecordId字段中的NULL值表示该记录是链中的最新记录。

我需要能够检索所有缺席记录的数据集,并将每个父记录的注释组合在一个字符串中,然后列出链中最新记录的其他字段。

以下是包含数据的表格的简化版本:

CREATE TABLE [dbo].[AbsenceData](
    [Id] [int] IDENTITY(1,1) NOT NULL,
    [Notes] [varchar](max) NULL,
    [LinkedRecordId] [int] NULL,
    [CreatedAt] [datetime] NULL

)

我的问题是效果;我目前的解决方案是使用以下表值函数来收集和连接链中所有父记录的字符串:

CREATE FUNCTION [dbo].[AbsenceNotesFor](@AbsenceDataId INT)
RETURNS @return TABLE
(
    AbsenceDataId INT 
    ,Notes VARCHAR(MAX)
)
AS
BEGIN
    DECLARE @notes VARCHAR(MAX)
    ;WITH AbsenceNotes AS (
       SELECT
          ad.Id
          ,ad.Notes
          ,ad.CreatedAt
       FROM
          AbsenceData ad WITH (NOLOCK) 
       WHERE 
          ad.Id = @absenceDataId
       UNION ALL
       SELECT
          ad.Id
          ,ad.Notes
          ,ad.CreatedAt
       FROM
          AbsenceData ad WITH (NOLOCK) 
          INNER JOIN AbsenceNotes an ON an.Id = ad.LinkedRecordId
    )

    SELECT @notes = CONVERT(VARCHAR(11),CreatedAt, 105) + ' ' + CONVERT(VARCHAR(5),CreatedAt, 114)+ CHAR(13)+CHAR(10) + CAST(Notes AS VARCHAR(MAX)) + CHAR(13)+CHAR(10) + CHAR(13)+CHAR(10) + COALESCE(@notes,'')
    FROM AbsenceNotes   

    INSERT INTO @return
       SELECT AbsenceDataId = @AbsenceDataId, Notes = @notes

    RETURN;
END

以下是当前实施的简化版本:

SELECT
     Id
    ,n.Notes    
FROM AbsenceData
    CROSS APPLY dbo.AbsenceNotesFor(Id) n
WHERE 
    LinkedRecordId IS NULL

当针对几百条记录的数据集运行此操作时,我已经可以看到性能问题,这似乎是由于表值函数内的逻辑。

我正在寻找一种更有效的方法来做到这一点,任何想法?

我们正在使用MS SQL Server 2016标准版

以下是SQL Fiddle的示例: http://sqlfiddle.com/#!6/b9834

2 个答案:

答案 0 :(得分:1)

使用多语句 - 语法的表值函数以绝对糟糕的性能而众所周知。您应该尽可能避免使用BEGINEND

的TVF语法

内联 ad-hoc 功能相同的功能要好得多。如果没有样本数据,这是一次盲目的飞行,但我认为你会得到相同的结果,但有更好的表现:

CREATE FUNCTION [dbo].[AbsenceNotesFor](@AbsenceDataId INT)
RETURNS TABLE
AS
    RETURN
    WITH AbsenceNotes AS (
       SELECT
           ad.Id
          ,ad.Notes
          ,ad.CreatedAt
       FROM
          AbsenceData ad WITH (NOLOCK) 
       WHERE 
          ad.Id = @absenceDataId
       UNION ALL
       SELECT
          ad.Id
          ,ad.Notes
          ,ad.CreatedAt
       FROM
          AbsenceData ad WITH (NOLOCK) 
          INNER JOIN AbsenceNotes an ON an.Id = ad.LinkedRecordId
    )

    SELECT @AbsenceDataId AS AbsenceDataId
         ,(
             REPLACE
             (  
                  STUFF
                  (
                      (
                        (
                            SELECT '|#|'+ '|#|' 
                                        + CONVERT(VARCHAR(11),CreatedAt, 105) + ' ' 
                                        + CONVERT(VARCHAR(5),CreatedAt, 114)
                                        + '|#|' 
                                        + CAST(Notes AS VARCHAR(MAX))
                            FROM AbsenceNotes   
                            FOR XML PATH(''),TYPE
                        ).value('.','nvarchar(max)')
                      ),1,6,''
                  ),'|#|',CHAR(13)+CHAR(10)
            )
          ) AS Notes;

简短说明:

我没碰到你的CTE。

SELECT @variable=@variable + Something的字符串连接是一种表现非常糟糕的程序方法。我将其替换为FOR XML PATH('')。如果您搜索Group concat和Sql-Server ...

,您可以找到很多相关信息

我用一个魔术值(|#|)替换了换行符,以避免以后出现问题。

STUFF函数除了删除开头的6个字符(断行的魔法值的两倍)外别无其他功能

REPLACE函数将魔术值更改回实际换行符。

如果您希望Notes按降序排列(您的代码如下所示),只需向内部ORDER BY添加适当的SELECT

答案 1 :(得分:0)

在更大的集合上仍然存在性能问题 - 但是在更详细地执行执行计划之后;我意识到我遗漏了一些索引并且查询未被完全覆盖',添加缺少的索引,其中包含Id的密钥字段以及Notes和{的包含字段{1}}大大提高了表现;但是我不确定这是明智的,也许我需要就这个问题提出另一个问题......