使用函数和视图的SQL查询非常慢

时间:2014-04-18 08:36:36

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

好的,首先,如果你仔细阅读了整篇文章,请提前感谢,因为它可能在几个层面上非常痛苦。

  1. 这是一篇很长的帖子
  2. 这是严重的
  3. 这可能会让你的大脑受伤
  4. 但从好的方面来说,在阅读完整篇文章之后,我感觉答案非常明显和简单,所以你有这样做。

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

    果壳

    • 我在SQL Server 2008r2中有一个查询需要很长时间才能完成。
    • 我有几个表,其中包含有关孩子及其父母的信息。
    • 一个表中的子项可以在另一个表中具有父项,然后可以在另一个表中具有父项(只有3个表)。
    • 我希望能够将一个孩子的名字作为一个字符串,并找出它是祖先的heirarchy并将其作为一个句点分隔的字符串返回。所以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的视图中。
    • 创建一个接收名称的函数。然后,它使用该视图来找出父对象的名称。然后它会查看父母对于那个父母是谁,并且一直持续到父母为空并且它停止(因为那是祖先链的顶端...我们一直到了没有父母的星球)。
    • 创建查询以查询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
    

    我可以看到为什么它可能需要花费很长时间才能执行需要随后抓取视图的函数。所以......

    我的问题

    • 我如何加快速度?
    • 我是否需要在视图上添加索引?
    • 我的方法是否已关闭,我应该采取不同的方式吗?
    • 如果是这样,你推荐什么?

    非常感谢你,如果你做到这一点!

1 个答案:

答案 0 :(得分:2)

首先使用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

虽然采用了这种方法,但我不知道它会产生什么样的性能。因此,您需要完成查询和测试。

希望它有所帮助。