分层SQL选择查询

时间:2014-10-20 10:50:08

标签: sql-server sql-server-2008

我正在使用MS SqlServer 2008.我有一个表'用户'。该表具有bigint的关键字段ID。还有一个字段为varchar的字段,它编码所有用户父ID的链。 例如:

用户表:

ID | Parents
1  | null
2  | ..
3  | ..
4  | 3,2,1

此处,用户1没有父母,用户4具有父母链3-> 2-> 1。我创建了一个函数,它解析用户的Parents字段并返回结果表,其中包含bigint的用户ID。

现在我需要一个查询,它将选择和加入一些被请求用户及其父母的ID(用户和他们的父母的顺序并不重要)。我不是SQL专家,所以我能想到的就是以下内容:

WITH CTE AS(
SELECT
    ID,
    Parents
FROM
[Users]
WHERE
(
     [Users].Name = 'John'
)

UNION ALL

SELECT
    [Users].Id,
    [Users].Parents
FROM [Users], CTE
WHERE
(
    [Users].ID in (SELECT * FROM GetUserParents(CTE.ID, CTE.Parents) )
)) 
SELECT * FROM CTE

基本上它起作用了。但是这个查询的性能非常差。我相信WHERE .. IN ..这里的表达是一个瓶颈。据我了解 - 它不是仅仅加入CTE的第一个子查询(已发现用户的ID)和GetUserParents(ID用户父母的ID)的结果,而是必须枚举Users表中的所有用户并检查是否它们中的每一个都是函数结果的一部分(并且判断执行计划 - Sql Server执行结果的不同顺序以提高WHERE ... IN ..语句的性能 - 这本身就是合乎逻辑的,但总的来说我的目标不是必需的。但是这个不同的顺序需要70%的查询执行时间)。所以我想知道如何改进这个查询,或者有人可能会提出另一种方法来解决这个问题呢?

感谢您的帮助!

2 个答案:

答案 0 :(得分:1)

问题中的递归查询看起来多余,因为您已经在GetUserParents中形成了所需的ID列表。也许可以使用SELECT / Users将此更改为GetUserParents() WHEREJOIN

select Users.*
from Users join
     (select ParentId
      from (SELECT * FROM Users where Users.Name='John') as U
           cross apply [GetDocumentParents](U.ID, U.Family, U.Parents))
     as gup
on Users.ID = gup.ParentId

由于GetDocumentParents期望标量和select... where生成一个表格,我们需要将该函数应用于表格的每一行(即使我们已经"知道"有" s只有一个)。这是apply的作用。

我使用缩进来强调查询的概念部分。 (select...) as gup是实体Users join' d; (select...) as U cross apply fn()FROM的参数。

理解此查询的关键知识是了解cross apply的工作原理:

  • 它是FROM子句的一部分(非常出乎意料;因此语法位于FROM (Transact-SQL)
  • 它转换表左侧的表达式,结果成为FROM的参数(我用缩进强调了这一点)

转换是:对于每一行,它

  • 使用此行运行表表达式(在本例中,调用表值函数)
  • 在结果集中添加后跟来自调用的列。 (在我们的例子中,从函数返回的表有一个名为ParentId的列
    • 因此,如果调用返回多行,则添加的记录将与附加函数中每一行的表中的行相同。

这是cross apply,因此只有在函数返回任何内容时才会添加行。如果这是另一种风格outer apply,则无论如何都会添加一行,如果没有返回任何内容,则会在函数列中添加NULL

答案 1 :(得分:0)

这"解析"事情甚至违反了1NF。使Parents字段仅包含直接父项(最好是外键),然后可以使用递归查询检索整个子树。