0

我有一个非常复杂的查询,但我应用了一些索引,现在它在不到 1 秒的时间内运行得非常顺利。查询的结构是这样的(我发现没有必要发布完整的查询,因为我稍后会证明 - 错误不在查询本身):

DECLARE @period varchar(6);
SET @period = '201302';

DECLARE @day datetime;
SET @day = dba.fnu_firstdate(@period);//returns 2013-02-01

SELECT
    user_id,
    (SELECT CAST(MAX(c1) AS varchar) FROM table t WHERE t.user_id = table.user_id AND when = DATEADD(day, 0, @day)) Day01,
    ...    
    (SELECT CAST(MAX(c1) AS varchar) FROM table t WHERE t.user_id = table.user_id AND when = DATEADD(day, 30, @day)) Day31
FROM
    table

所以是的,如果我执行这个查询,大约需要 1 秒才能完成,这对我来说非常好。但是,如您所见,我需要为其提供参数。因此,我将其更改为表值函数,以便可以轻松地从中进行选择查询:

CREATE FUNCTION fnu_data(@period varchar(6))
RETURNS @results TABLE
(
    id int,
    Day01 varchar(10) null,
    ...
    Day31 varchar(10) null
)
AS
BEGIN
    DECLARE @day datetime;
    SET @day = dba.fnu_firstdate(@period);

    INSERT INTO @results 
    (
        id,     
        Day01,
        ...
        Day31
    )
    SELECT

SELECT
    user_id,
    (SELECT CAST(MAX(c1) AS varchar) FROM table t WHERE t.user_id = table.user_id AND when = DATEADD(day, 0, @day)) Day01,
    ...    
    (SELECT CAST(MAX(c1) AS varchar) FROM table t WHERE t.user_id = table.user_id AND when = DATEADD(day, 30, @day)) Day31
FROM
    table

RETURN

现在当我做

SELECT * FROM dba.fnu_data('201302')

需要6秒,太长了。在我的同事的建议下,我尝试在 id 上添加主索引并将每个子选择替换为连接,但它将执行查询的时间延长到 8 秒。(PS 查询返回约 3200 行)。

在我看来,罪魁祸首是插入,但我不知道如何摆脱它。

我可以做些什么来改进我的查询?

4

2 回答 2

1

不确定是什么可能导致独立SELECTINSERT ... SELECT作为函数的一部分之间的性能差异,但我可以建议重写您的 SELECT 语句,因为您的 SELECT 对我来说看起来绝对不是最佳的。

您似乎正在做一个支点,SQL Server 2005+ 中有本机语法。考虑以下查询:

WITH data AS (
  SELECT
    user_id,
    DAY([when]) AS day,
    c1
  FROM [table] t
  CROSS APPLY (
    SELECT CAST(@period + '01' AS date)  -- this is supposed to be a replacement
                                         -- for dba.fnu_firstdate(), but you
                                         -- could use your function here instead
  ) x (startdate)
  WHERE t.day >= x.startdate
    AND t.day <  DATEADD(MONTH, 1, startdate)
)
INSERT INTO @results
(
  id,
  Day01,
  ...
  Day31
)
SELECT
  id,     
  [1],
  ...
  [31]
FROM data
PIVOT (
  MAX(c1) FOR day IN ([1], [2], ..., [30], [31])
) p
;

它使用公用表表达式将指定月份的数据作为单独的步骤准备好,然后使用 PIVOT 语法通过聚合对结果进行透视。

请注意,上面使用单个语句完成整个工作,这也是一个 SELECT 语句。这意味着您可以将多语句 TVF 转换为内联TVF:

IF OBJECT_ID('dba.fnu_data') IS NOT NULL
  DROP FUNCTION dba.fnu_data
GO
CREATE FUNCTION dba.fnu_data(@period varchar(6))
RETURNS TABLE
RETURN (
  WITH data AS (
    SELECT
      user_id,
      DAY([when]) AS day,
      c1
    FROM [table] t
    CROSS APPLY (
      SELECT CAST(@period + '01' AS date)
    ) x (startdate)
    WHERE t.day >= x.startdate
      AND t.day <  DATEADD(MONTH, 1, startdate)
  )
  SELECT
    id,
    CAST([1] AS varchar(30)) AS Day01,
    ...
    CAST([31] AS varchar(30)) AS Day31
  FROM data
  PIVOT (
    MAX(c1) FOR day IN ([1], [2], ..., [30], [31])
  ) p
)
GO

内联 TVF 在多语句 TVF 之前的优势在于,它的计划是根据调用函数的整个查询来选择的。在这方面,内联 TVF 就像一个视图。

请注意,必须使用DROP+来完成转换CREATE,就像上面一样,因为多语句 TVF 和内联 TVF 是 SQL Server 中不同类型的对象,不能将一个对象更改为另一个对象。

于 2013-02-05T08:06:42.623 回答
0

我不认为你的问题是物理插入......而是插入的最终选择。

此特定查询将成为您的瓶颈,因为它执行至少 30 次单独的 SELECT 以生成结果集,然后您将其填充回您的表变量中。

SELECT
    user_id,
    (SELECT CAST(MAX(c1) AS varchar) FROM table t WHERE t.user_id = table.user_id AND when = DATEADD(day, 0, @day)) Day01,
    ...    
    (SELECT CAST(MAX(c1) AS varchar) FROM table t WHERE t.user_id = table.user_id AND when = DATEADD(day, 30, @day)) Day31
FROM
    table

你能解释一下你想从这个查询中实现什么吗?

于 2013-02-05T07:16:20.377 回答