如何创建 children 计数的索引视图

How to create indexed view of children count

我正在尝试取table与parentchild关系,得到children的人数。我想通过利用 COUNT_BIG(*).

创建 children 数量的索引视图

问题是,在我的索引视图中,我不想消除没有 children 的实体,相反,我希望 Count 对于那些实体为 0。

给出

> Id | Entity | Parent
> -: | :----- | :-----
>  1 | A      | null  
>  2 | AA     | A     
>  3 | AB     | A     
>  4 | ABA    | AB    
>  5 | ABB    | AB    
>  6 | AAA    | AA    
>  7 | AAB    | AA    
>  8 | AAC    | AA    

我想创建一个 returns

的索引视图
> Entity | Count
> :----- | ----:
> A      |     2
> AA     |     3
> AB     |     2
> ABA    |     0
> ABB    |     0
> AAA    |     0
> AAB    |     0
> AAC    |     0

这是我的 SQL 有效,但使用了 LEFT JOIN 和 CTE(在索引视图中均不允许)

    DROP TABLE IF EXISTS Example
    CREATE TABLE Example (
      Id INT primary key,
      Entity varchar(50),
      Parent varchar(50)
    )
    
    
    INSERT INTO Example
    VALUES 
       (1, 'A', NULL)
      ,(2, 'AA',  'A')
      ,(3, 'AB','A')
      ,(4, 'ABA', 'AB')
      ,(5, 'ABB', 'AB')
      ,(6, 'AAA', 'AA')
      ,(7, 'AAB', 'AA')
      ,(8, 'AAC', 'AA')
    
    
    
    SELECT *
    FROM Example
    
    ;WITH CTE AS (
     SELECT Parent, COUNT(*) as Count
      FROM dbo.Example
      GROUP BY Parent
    )
      
    SELECT e.Entity, COALESCE(Count,0) Count
    FROM dbo.Example e
    LEFT JOIN CTE g
    ON e.Entity = g.Parent


GO

您可以在 example table 上创建 AFTER INSERT,UPDATE, DELETE trigger 和一个新的 table 来实现结果。

在触发器中,您可以使用任何语句。您可以通过两种方式执行此操作,具体取决于您的查询初始查询的速度。

例如,您可以在每个 INSERT/UPDATE/DELETE 上截断 table,然后计算计数并再次插入(如果查询速度很快)。

或者您可以依赖 inserteddeleted tables,它们是特殊的 tables 在触发器上下文中可见,并显示行值如何更改。

例如,如果一条记录存在于 inserted table 而不是 deleted - 这是一个新行。你可以只为他们计算COUNT

如果记录仅存在于 deleted table - 这是一个删除(我们需要删除我们预先计算的行 table)。

并且在两个 table 中都存在一行 - 这是一个更新 - 我们需要为记录执行新的计数。

这里有一件事非常重要——不要逐行操作。对于上述三种情况,请始终成批处理行,否则您最终会得到性能不佳的触发器,这将延迟原始 table 的 CRUD 操作。

我不认为你可以使用 CTE 或 LEFT JOIN 来实现,因为有很多 restriction using the indexed views

解决方法

我建议将查询分成两部分:

  1. 创建索引视图而不是常见的 table 表达式 (CTE)
  2. 创建执行 LEFT JOIN 的非索引视图

除此之外,在 Table Example 中的 Entity 列上创建一个非聚集索引。

那么当你查询非索引视图时,它会使用索引

--CREATE TABLE
CREATE TABLE Example (
  Id INT primary key,
  Entity varchar(50),
  Parent varchar(50)
)

--INSERT VALUES
INSERT INTO Example
VALUES 
   (1, 'A', NULL)
  ,(2, 'AA',  'A')
  ,(3, 'AB','A')
  ,(4, 'ABA', 'AB')
  ,(5, 'ABB', 'AB')
  ,(6, 'AAA', 'AA')
  ,(7, 'AAB', 'AA')
  ,(8, 'AAC', 'AA')

--CREATE NON CLUSTERED INDEX
CREATE NONCLUSTERED INDEX idx1 ON dbo.Example(Entity);

--CREATE Indexed View

CREATE VIEW dbo.ExampleView_1
    WITH SCHEMABINDING
    AS 
 SELECT Parent, COUNT_BIG(*) as Count
  FROM dbo.Example
  GROUP BY Parent

CREATE UNIQUE CLUSTERED INDEX idx ON dbo.ExampleView_1(Parent);

--Create non-indexed view
CREATE VIEW dbo.ExampleView_2
    WITH SCHEMABINDING
    AS 
    SELECT e.Entity, COALESCE(Count,0) Count
    FROM dbo.Example e
    LEFT JOIN dbo.ExampleView_1 g
    ON e.Entity = g.Parent

因此,当您执行以下查询时:

SELECT * FROM dbo.ExampleView_2 WHERE Entity = 'A'

可以看到执行计划中使用了视图Clustered index和Table Non-Clustered index:

附加信息

我没有找到其他解决方法来替代索引视图中 LEFT JOINUNIONCTE 的使用,您可以查看许多类似的 Whosebug 问题:

  • Indexing views with a CTE
  • What to replace left join in a view so i can have an indexed view?
  • Create an index on SQL view with UNION operators? Will it really improve performance?

更新 1 - 拆分视图与笛卡尔连接

为了确定更好的方法,我尝试比较了两种建议的方法。

--The other approach (cartesian join)
CREATE TABLE TwoRows (
    N INT primary key
)

INSERT INTO TwoRows
VALUES (1),(2)

CREATE VIEW dbo.indexedView  WITH SCHEMABINDING AS
    SELECT 
        IIF(T.N = 2, Entity, Parent) as Entity
        , COUNT_BIG(*) as CountPlusOne
        , COUNT_BIG(ALL IIF(T.N = 2, NULL, 1)) as Count
    FROM dbo.Example E1
    INNER JOIN dbo.TwoRows T
        ON 1=1
    WHERE IIF(T.N = 2, Entity, Parent) IS NOT NULL
    GROUP BY IIF(T.N = 2, Entity, Parent)
GO

CREATE UNIQUE CLUSTERED INDEX testIndex ON indexedView(Entity)

我在单独的数据库上创建了每个索引视图并执行了以下查询:

SELECT * FROM View WHERE Entity = 'AA'

拆分视图

笛卡尔连接

时间统计

时间统计显示笛卡尔连接方法执行时间高于拆分视图方法,如下图所示(右边的笛卡尔连接):

添加 WITH(NOEXPAND)

我还尝试在笛卡尔连接方法中添加 WITH(NOEXPAND) 选项,以强制数据库引擎使用索引视图聚集索引,结果如下:

我清除了所有缓存并进行了比较,时间统计比较显示拆分视图方法仍然比笛卡尔连接方法更快WITH(NOEXPAND) 右边的方法):

我能够通过对计数为 0 (N=2) 的行执行 笛卡尔连接 来完成我想要的。

创建 table 调用的两行,这将复制孙子

DROP TABLE IF EXISTS TwoRows
CREATE TABLE TwoRows (
    N INT primary key
)

INSERT INTO TwoRows
VALUES (1),(2)

获取原文table

DROP TABLE IF EXISTS Example
CREATE TABLE Example (
    Id INT primary key,
    Entity varchar(50),
    Parent varchar(50)
)


INSERT INTO Example
VALUES 
     (1, 'A', NULL)
    ,(2, 'AA',  'A')
    ,(3, 'AB','A')
    ,(4, 'ABA', 'AB')
    ,(5, 'ABB', 'AB')
    ,(6, 'AAA', 'AA')
    ,(7, 'AAB', 'AA')
    ,(8, 'AAC', 'AA')

创建索引视图

DROP VIEW IF EXISTS dbo.indexedView 
CREATE VIEW dbo.indexedView  WITH SCHEMABINDING AS
    SELECT 
        IIF(T.N = 2, Entity, Parent) as Entity
        , COUNT_BIG(*) as CountPlusOne
        , COUNT_BIG(ALL IIF(T.N = 2, NULL, 1)) as Count
    FROM dbo.Example E1
    INNER JOIN dbo.TwoRows T
        ON 1=1
    WHERE IIF(T.N = 2, Entity, Parent) IS NOT NULL
    GROUP BY IIF(T.N = 2, Entity, Parent)
GO

CREATE UNIQUE CLUSTERED INDEX testIndex ON indexedView(Entity)

SELECT *
FROM indexedView

我无法避免使用 COUNT_BIG(*)