4

我有一个子查询(LastActivityOn),我想在三个地方使用它,我的投影(SELECTed 输出)、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

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

当我只通过用户定义函数获取最多两个日期时,我能够在 WHERE 和 ORDER BY 中使用结果值。我现在不可以。

似乎我有几个选择......我可以将整个事情包装在另一个查询中,仅使用添加的活动重复投影。看来我可以以同样的方式使用“WITH”(CTE)。

但是因为我不清楚何时可以和不能以我想要的方式使用子查询的规则,所以我很容易遗漏一些东西。有任何想法吗?

或者也许 SQL SERVER 足够聪明,只为每个输出行执行一次计算,我不应该担心吗?

编辑:目前正在运行 SQL Server 2008 Standard,但升级将在某个时候按顺序进行。此外,RE:日志功能 - 我正在努力将其与相关性结合为加权总数,因此这是一项正在进行的工作。我将使用 INT 对其进行修剪以用作一种排名,或者通过线性调整将其添加到相关性中。

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

4

3 回答 3

6

我不会尝试修改您的查询,但可能是您需要的公用表表达式。

于 2012-08-06T10:44:51.100 回答
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 或其他版本可能与 Standard 或 Enterprise 版的功能不同)


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

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

相当于更简单的:

ORDER BY
  DATEDIFF(WEEK, LastActivityOn, @Today))
于 2012-08-06T11:09:58.187 回答
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”输出时,优化器将其移动到执行计划中更深的 5 个级别,其中距离计算最终针对更多行执行。结果是查询运行了大约 6 倍的时间。

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

提供的选项摘要:如何避免在同一个选择中多次重复计算表达式?

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

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

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

于 2012-08-06T12:35:15.210 回答