【问题标题】:TSQL query that uses function and view is very slow使用函数和视图的 TSQL 查询非常慢
【发布时间】:2014-06-02 17:57:56
【问题描述】:

好的,首先感谢您通读整篇文章,因为它在多个层面上可能会非常痛苦。

  1. 这篇文章很长
  2. 太恶心了
  3. 这可能会让你的大脑受伤

但从好的方面来说,通读整篇文章后,我觉得答案非常明显和简单,所以你可以做到。

所以我会简单地告诉你这个问题,然后更详细地告诉你:

简而言之

  • 我在 SQL Server 2008r2 中有一个查询需要很长时间才能完成。
  • 我有几个表,其中包含有关孩子及其父母的信息。
  • 一个表中的子级可以在另一个表中有父级,然后在另一个表中可以有父级(只有 3 个表)。
  • 我希望能够将孩子的名字作为字符串,并找出其祖先的层次结构并将其作为句点分隔的字符串返回。所以Grandpappy.Grandpa.Dad.Me
  • 我已经完成了这一切,只是需要很长时间,所以我在做一些愚蠢的事情,或者性能很差,或者很可能两者兼而有之。
  • 我无法控制这些表,它们就是它们本来的样子,我无法对它们做任何事情。我创建了一个视图和一个函数(您将在下面看到),这就是我所能控制的。
  • 下面的表名和值显然是虚构的。

详细说明

以下是表示孩子和父母的表格。在本例中,我们将处理水果、蔬菜和行星。

  • 行星没有父母。
  • 水果的父母是行星或水果。
  • 蔬菜的父母是水果、行星或蔬菜。

让我们来看看它们......

表 1 = Planets(我没有父母)

ID, Name
1, Earth
2, Saturn

表 2 = Fruits(我的父母要么是行星要么是水果)

ID, Name, PlanetName, FruitName
1, Kiwi, Earth, null
2, Strawberry, Saturn, null
3, Banana, null, Strawberry

表 3 = Vegetables(我的父母是行星或水果或蔬菜)

ID, Name, FruitName, PlanetName, VegetableName
1, Potato, Kiwi, null, null
2, Squash, null, Earth, null
3, Pumpkin, null, null, Potato

表 4 = BigTable(这将是主要的慢查询使用的表。它有一列只包含一个孩子的名字,它可能是一颗行星、一种水果或一种蔬菜)

ID, Name, OneOfTheThree
1, John, Earth
2, Steve, Kiwi
3, Joe, Saturn
4, Jane, Potato

我们有我们的表和我们的数据,我现在想做什么?

我想创建一个查询,查看 BigTable 中的所有 OneOfTheThree 值并找出他们的血统(父亲、祖父母等是谁)并将其返回给调用者。

所以我的想法是这样做:

  • 创建一个视图,将三个表(Planet、Fruit、Vegetable)拉到一个显示 Name 和 Parent 的视图中。
  • 创建一个接受名称的函数。然后它使用该视图来找出该名称的父级。然后它会查看该 Parent 的 Parent 是谁,并且一直持续到 Parent 为 null 并且它停止(因为这是祖先链的顶部......我们一直到没有父母的 Planet )。
  • 创建一个查询来查询 BigTable,然后在 BigTable 的 OneOfTheThree 列上使用上述函数来获取 OneOfTheThree 中名称的祖先。

所以我是这样做的:

我的看法

查看 = vwEverybodyAndTheirParents

-- Planets
SELECT Name, null AS Parent
FROM Planets
UNION
-- Fruits
SELECT Name, PlanetName AS Parent
FROM Fruits
UNION
-- Vegetables
SELECT Name, CASE WHEN FruitName IS NOT NULL THEN FruitName WHEN PlanetName IS NOT NULL THEN Planet ELSE NULL END AS Parent
FROM Vegetables

好的,这给了我一切,它是父母。现在让函数抓取该视图并给我完整祖先的句点分隔字符串:

我的功能

CREATE FUNCTION dbo.fnGetMyParent(@NameToGetParentsFor varchar(255))
RETURNS varchar(255)
AS
    DECLARE @InternalName varchar(255)
    DECLARE @ParentName varchar(255)
    DECLARE @ConcatenatedParentStringToReturn varchar(max)

    SELECT @ParentName = Parent
            ,@ConcatenatedParentStringToReturn = Name
    FROM vwEverybody
    WHERE Name = @NameToGetParentsFor

    WHILE @ParentName IS NOT NULL
    BEGIN
        SELECT @InternalName = Name, 
                @ParentName = Parent
        FROM vwEverybody
        WHERE Name = @ParentName

        SET @ConcatenatedParentStringToReturn = RTRIM(InternalName) + "." + RTRIM(@ConcatenatedParentStringToReturn)
    END

    RETURN @ConcatenatedParentStringToReturn
END 

这个函数工作得很好(虽然可能编码很差并且性能很差?),所以如果我这样称呼它,上面所有的例子:

dbo.fnGetMyParent('Potato')

我得到连接的字符串:

Earth.Kiwi.Potato

问题

好的,现在终于要解决问题了……这个需要很长时间的大查询:

SELECT Name,
       OneOfTheThree,
       fnGetMyParent(OneOfTheThree) as HeirarchyOfParents
FROM BigTable

我明白为什么它可能需要很长时间,因为它执行需要然后爬取视图的函数的每个值。所以...

我的问题

  • 如何加快速度?
  • 是否需要在视图上放置索引?
  • 我的方法是否无效,我应该采取不同的做法吗?
  • 如果是,您有什么建议?

非常感谢您能做到这一点!

【问题讨论】:

  • 可以给原始表添加索引吗?你能创建新表吗?数据更新频率如何?
  • 原表无法添加索引,更新频繁。但是,我可以创建新表、存储过程和函数。

标签: sql sql-server performance tsql sql-server-2008-r2


【解决方案1】:

首先,在使用 sql 时,应尽可能避免使用循环(除非情况需要)

其次,不需要视图或函数,因为您的查询应该很容易一次性编写。

select 
  bt.Name
  ,bt.OneOfTheThree 
  ,p.Name+'.'+isnull(f.Name,'')+'.'+isnull(v.Name,'')+'.'+bt.Name as HeirarchyOfParents
from BigTable bt
left join Vegetables v
  on bt.OneOfTheThree = v.name
left join Fruits f
  on coalesce(v.FruitName,bt.OneOfTheThree) = f.Name
left join Planets p
  on coalesce(f.PlanetName,v.PlanetName,bt.OneOfTheThree) = p.Name

如果表与其他表一致,您可以删除的最后一个连接,因为它不会带来新信息(行星名称已经存在)。

如果您能够做到的话,您可以在此带来的改进是对表的索引。

好的,有了新信息,我能想到的最简单的方法如下:

;with ftemp as (
  select 
    name as path
    ,PlanetName
    ,name as root
    ,name as name
    ,FruitName as parent
    ,0 as cnt
  from fruits
  union all
  select 
    fruits.name + '.' + ftemp.path
    ,ftemp.PlanetName
    ,root
    ,fruits.name
    ,cnt+1
  from fruits
  join ftemp
    on fruits.name= ftemp.parent
)
,fg as (
  select 
    name
    ,max(cnt) as cnt
  from ftemp
  group by name
)
,f as (
  select 
    ftemp.*
  from ftemp
  join fg
    on  ftemp.cnt = fg.cnt
    and ftemp.name = fg.name
)
,vtemp (same ideea)
,vg (same ideea)
,v (same ideea)
select 
  bt.Name
  ,bt.OneOfTheThree 
  ,p.Name+'.'+isnull(f.Path+'.','')+isnull(v.Path+'.','')+bt.Name as HeirarchyOfParents
from BigTable bt
left join v
  on bt.OneOfTheThree = v.name
left join f
  on coalesce(v.FruitName,bt.OneOfTheThree) = f.Name
left join Planets p
  on coalesce(f.PlanetName,v.PlanetName,bt.OneOfTheThree) = p.Name

虽然使用这种方法.. 我不知道它会产生什么性能。因此,由您完成查询和测试。

希望对你有帮助。

【讨论】:

  • 我建议在ISNULL() 内连接分隔符,以防缺少任何级别,即ISNULL(f.name+'.', '')。否则@Dumirescu 有它。
  • 为了简化我的问题,我想我过度简化了。我现在已经对其进行了编辑并将数据添加到示例表中以更加具体,因为我遗漏了一个保证执行视图/功能的关键细节。也就是说,水果和蔬菜也可以有来自他们自己表的父母。例如,一个水果可以有另一个水果作为父母,而蔬菜可以有一个蔬菜作为父母。
猜你喜欢
  • 1970-01-01
  • 2012-11-20
  • 2011-04-01
  • 1970-01-01
  • 2019-09-02
  • 2017-01-07
  • 2018-01-30
  • 2020-01-10
  • 1970-01-01
相关资源
最近更新 更多