避免重复引用多个连接表的子查询

时间:2012-08-06 10:39:51

标签: sql sql-server tsql

我有一个子查询( LastActivityOn ),我想在三个地方使用,我的投影(SELECTed output),ORDER BY和WHERE子句。

SELECT TOP 175
  (SELECT MAX(ActivityDate) FROM (VALUES
    (UserRegistration.CreatedOn),
    (UserRegistration.ActivatedOn),
    (UserRegistration.LastLoginOn),
    (UserRegistration.UpdatedOn),
    (UserProfile.LastPostedOn)) AS AllDates(ActivityDate)) LastActivityOn,
  UserRegistration.FirstName,
  UserRegistration.LastName,
  [15 more columns of various calculated distances, coalesces, etc...]
FROM
  UserRegistration
  INNER JOIN UserProfile ON UserRegistration.Id = UserProfile.RegistrationId
  INNER JOIN (
    SELECT PostalCode, GeoCenter, PrimaryCity, StateOrProvince
    FROM PostalCodes 
    WHERE @OriginPostalCode IS NULL OR PostalCodes.GeoCenter.STDistance(@OriginPoint) < @WithinMeters
  ) AS ProximalPostalCodes ON ProximalPostalCodes.PostalCode = UserRegistration.PostalCode
  [7 more joins including full-text queries]
WHERE
  LastActivityOn > @OldestUserToSearch AND
  [20 more lines of filtering logic]
ORDER BY
  LOG(DATEDIFF(WEEK, LastActivityOn, @Today))/LOG(2),
  FullTextRelevance

注意LastConctivityOn的三次出现。另请注意,LastActivityOn子查询引用了两个表。我想因为它依赖于父查询中的join子句,它本质上是一个相关的子查询?

当我只通过User-Defined-Function获取最多两个日期时,我能够在WHERE和ORDER BY中使用结果值。现在我不能。

似乎我有几个选项...我可以将整个事情包装在另一个查询中,只使用添加的活动重复投影。好像我可以使用&#34; WITH&#34; (CTE)以同样的方式。

但是因为我没有清楚地理解我何时能够和不能按照我想要的方式使用子查询的规则,所以我很容易丢失一些东西。有什么想法吗?

或者SQL SERVER可能足够智能,只能为每个输出行执行一次计算,我不应该担心它吗?

编辑:当前正在运行SQL Server 2008 Standard,但升级将在某些时候进行。此外,RE:日志功能 - 我正在努力将相关性与加权总数相结合,以便进行中的工作。我会用INT修剪它以用作排名类型,或者将其添加到线性调整的相关性。

更正:我能够在ORDER BY中使用子查询别名,但不能在任何其他计算或where子句中使用。感谢ypercube指出这一点。

3 个答案:

答案 0 :(得分:6)

我不会尝试修改您的查询,但您可能需要common table expression

答案 1 :(得分:2)

您无法在LastActivityOn子句中使用WHERE别名,但可以在ORDER BY中使用它。

如果你不想在2个地方重复代码(SELECT和WHERE),你可以在派生表中使用CTE或选择这个LastActivityOn结果 - 以及整个子查询,然后在外部水平:

SELECT TOP 175
  LastActivityOn,
  FirstName,
  LastName,
  ...
FROM
    ( SELECT
        ( SELECT MAX(ActivityDate) 
          FROM 
            ( VALUES
                (UserRegistration.CreatedOn),
                (UserRegistration.ActivatedOn),
                (UserRegistration.LastLoginOn),
                (UserRegistration.UpdatedOn),
                (UserProfile.LastPostedOn)
            ) AS AllDates(ActivityDate)
        ) LastActivityOn,
        UserRegistration.FirstName,
        UserRegistration.LastName,
        [15 more columns of various calculated distances, coalesces, etc...]
      FROM
        UserRegistration
        INNER JOIN UserProfile ON UserRegistration.Id = UserProfile.RegistrationId
        INNER JOIN (
          SELECT PostalCode, GeoCenter, PrimaryCity, StateOrProvince
          FROM PostalCodes 
          WHERE @OriginPostalCode IS NULL 
             OR PostalCodes.GeoCenter.STDistance(@OriginPoint) < @WithinMeters
        ) AS ProximalPostalCodes 
            ON ProximalPostalCodes.PostalCode = UserRegistration.PostalCode
        [7 more joins including full-text queries]
      WHERE
        [20 or more lines of filtering logic]
    ) AS tmp
WHERE
  LastActivityOn > @OldestUserToSearch AND
  [any of the 20 lines that has "LastActivityO"]
ORDER BY
  LOG(DATEDIFF(WEEK, LastActivityOn, @Today))/LOG(2),
  FullTextRelevance ;

SQL-Server可能足够聪明,不会执行两次相同的代码,但这可能取决于您运行的版本。从版本2000到2012,优化器已经取得了很大的进步(Express或其他版本可能没有与标准版或企业版相同的功能)


与问题无关,但我认为因为LOG()函数是单调的,所以:

ORDER BY
  LOG(DATEDIFF(WEEK, LastActivityOn, @Today))/LOG(2)

相当于更简单:

ORDER BY
  DATEDIFF(WEEK, LastActivityOn, @Today))

答案 2 :(得分:2)

我认为包含此联接可能会做我需要的事情:

OUTER APPLY (SELECT MAX(ActivityDate) LastActivityOn FROM (VALUES
    (UserRegistration.CreatedOn),
    (UserRegistration.ActivatedOn),
    (UserRegistration.LastLoginOn),
    (UserRegistration.UpdatedOn),
    (UserProfile.PostedOn)) AS AllDates(ActivityDate)) LastActivity

还将其添加为条件WHERE条件,使用NULL参数禁用它:

WHERE
  (@OldestUserToSearch IS NULL OR
  LastActivityOn > @OldestUserToSearch) AND

<强>结果

在SELECT中使用它并引用它的性能与SQL Server 2008上的子选择相同。

当我添加WHERE谓词时,事情开始变得毛茸茸。您可以在原始问题中看到的邮政编码半径搜索是计算中最重要的部分,并且它在搜索顶部最有效,最接近“TOP 175”。不幸的是,当我在多个地方重用“OUTER APPLY”输出时,优化器将它更深入地移动到执行计划中,其中距离计算最终针对更多行执行。结果是查询运行了大约6倍。

因为相同形状查询的性能相同,并且导致代码更少(不需要重新调整我的投影或将整个查询包装在CTE或子查询中),我将调用OUTER APPLY我的回答是寻找。另外,如果我需要在所有情况下强制GIS搜索到最外面的嵌套循环,我将不得不重新制定它的查询。

提供的选项摘要:How can I avoid repeating a calculated expression multiple times in the same select?

APPLY的一些有用的类似用途:

子查询和CTE的本地示例(我作为答案拒绝了):

无关/无用的相关标题文章: