执行计划与存储过程不匹配

Rat*_*rol 2 t-sql execution-plan sql-server-2012

下面是一个存储过程,用于计算一个月内制作的小部件数量。如果没有制作小部件,则不存在任何记录。

执行计划显示INNER JOIN了 M 和 A 表中的一个,在语句中我正在执行一个LEFT OUTER JOIN.

我想计算一个时间范围内制作的小部件数量,将其加入月份表(1 月至 12 月),并将结果显示在 SSRS 报告中。加入是因为我无法获得不存在的COUNT数据。目前我得到:

MonthName   Widget Count
February    2
March       3
April       4
May         6
June        5
July        4 
August      6
September   2
October     4
November    1
December    2
Run Code Online (Sandbox Code Playgroud)

我希望列表中没有包含小部件的月份。

这是代码:

DECLARE @OName varchar(50)
DECLARE @Start_Date DATE 
DECLARE @End_Date DATE

SET @OName = 'John'
SET @Start_Date = '01/01/2012'
SET @End_Date   = '12/31/2012'

SELECT   M.[MonthName]
    ,COUNT(A.[Widget_ID]) AS 'Widget Count'
FROM [Connector].dbo.[Months] AS M 
        LEFT OUTER JOIN  [SERVER].[DATABASE].[dbo].[Widget] AS A
        ON MONTH(A.[Widget_Date]) = M.[MonthID]  
WHERE     (A.[Operator_Name] LIKE '%'+ @OName +'%')
          AND A.[PlantID] = '00000001'
          AND (
               (A.Widget_Date >= @Start_Date AND @End_Date IS NULL)
            OR (A.Widget_Datet <= @End_Date AND @Start_Date IS NULL)
            OR (A.Widget_Date >= @Start_Date AND A.Widget_Date <= @End_Date)
            OR (@Start_Date IS NULL AND @End_Date IS NULL)


GROUP BY  M.[MonthName], M.MonthID 
ORDER BY  M.[MonthID]
Run Code Online (Sandbox Code Playgroud)

下面是这个查询的执行计划。执行计划

::UPDATE1:: 我只是将月份的名称插入到此表中。我无法更改远程表。

CREATE TABLE [dbo].[Months](
    [MonthID] [smallint] IDENTITY(1,1) NOT NULL,
    [MonthName] [varchar](25) NOT NULL,
 CONSTRAINT [PK_Months] PRIMARY KEY CLUSTERED ([MonthID] ASC)
)
GO
INSERT INTO [dbo].[Months]
           ([MonthName])
     VALUES
           (<MonthName, varchar(25),>)
GO
Run Code Online (Sandbox Code Playgroud)

“小部件”表有超过 250 个字段,我需要很长时间才能匿名。

Aar*_*and 7

你的代码你正在做一个LEFT OUTER JOIN,但你真的吗?您的第一个WHERE子句(以及后面的每个子句)过滤行以从外部表中包含:

WHERE     (A.[Operator_Name] LIKE '%'+ @OName +'%')
Run Code Online (Sandbox Code Playgroud)

无论您是否有意,这都会将您LEFT OUTER JOIN变成INNER JOIN。也许您打算将这些过滤器移到ON子句中。

当然,我可能会从 中删除 non-sargeableMONTH()函数JOIN,因为这将强制对远程表进行完整扫描,并以这种方式编写它 - 也消除了与本地月份表的连接:

DECLARE 
  @OName      VARCHAR(50) = 'John', 
  @Start_Date DATE = NULL,--'20120101', -- use safe date formats 
  @End_Date   DATE = '20121231'; -- use semi-colons

-- deal with NULLs here so the query is simpler:

SELECT @Start_Date = COALESCE(@Start_Date, '20010101'),
       @End_Date   = COALESCE(@End_Date, CURRENT_TIMESTAMP);

;WITH n(n) AS 
(
  -- get the # of months you need instead of relying on your Connector table:
  SELECT TOP (DATEDIFF(MONTH, @Start_Date, @End_Date)+1) Number
    FROM master..spt_values
    WHERE [type] = N'P' AND Number >= 0
    ORDER BY Number
), m(m) AS
(
  -- convert those to months:
  SELECT DATEADD(MONTH, n, DATEADD(MONTH, DATEDIFF(MONTH, 0, @Start_Date), 0))
  FROM n
)
SELECT 
  m.m, 
  MonthName = DATENAME(MONTH, m.m),
  [Widget Count] = COUNT(w.Widget_ID)
FROM m
LEFT OUTER JOIN [Server].[Database].dbo.Widget AS w -- meaningful alias
ON 
  w.Widget_Date >= m.m
  AND w.Widget_Date < DATEADD(MONTH, 1, m.m) -- open-ended date range query
  AND w.Operator_Name LIKE '%' + @OName + '%'
GROUP BY m.m
ORDER BY m.m;
Run Code Online (Sandbox Code Playgroud)

只需忽略m输出中的列(您不能在不将其包含在输出中的情况下对其进行排序,并且按名称排序是没有意义的)。

另一个建议:不要'single quotes'用作别名分隔符。这种形式已被弃用,它还使列别名看起来像字符串文字。使用[square brackets]来代替。

  • @RateControl 将当前过滤器封装在括号中的 `A` 上,并添加一个 `OR A.JoinKey IS NULL` (2认同)