如何创建子项索引视图

时间:2019-02-14 06:00:30

标签: sql sql-server tsql common-table-expression indexed-view

我想拿一张与父母子女关系的桌子来获取孩子的数量。我想利用COUNT_BIG(*)创建一个孩子数量的索引视图。

问题在于,在索引视图中,我不想消除没有子项的实体,而是希望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    

我想创建一个返回

的索引视图
> 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

3 个答案:

答案 0 :(得分:3)

我认为您既不能使用CTE也不能使用LEFT JOIN来实现这一点,因为有很多restriction using the indexed views

解决方法

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

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

除此之外,在表Entity的{​​{1}}列上创建一个非聚集索引。

然后,当您查询非索引视图时,它将使用索引

Example

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

--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

您可以看到执行计划中使用了视图聚集索引和表非聚集索引:

enter image description here

其他信息

我没有找到其他替代方法来代替在索引视图中使用SELECT * FROM dbo.ExampleView_2 WHERE Entity = 'A' LEFT JOINUNION,您可以检查许多类似的Stackoverflow问题:


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

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

CTE

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

--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)

拆分视图

enter image description here

笛卡尔加入

enter image description here

时间统计

时间统计数据显示笛卡尔联接方法的执行时间比拆分视图方法高,如下图所示(笛卡尔联接在右侧):

enter image description here

添加WITH(NOEXPAND)

我还尝试在笛卡尔联接方法中添加SELECT * FROM View WHERE Entity = 'AA' 选项,以强制数据库引擎使用索引视图聚集索引,结果如下:

enter image description here

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

enter image description here

答案 1 :(得分:2)

通过对行进行笛卡尔联接(计数为0(N = 2)),我可以完成我想做的事情。

创建称为两行的表,该表将复制孙子孙

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

INSERT INTO TwoRows
VALUES (1),(2)

获取原始表格

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(*)

答案 2 :(得分:0)

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

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

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

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

例如,如果一条记录存在于inserted表中而不存在于deleted中-这是一个新行。您只能为它们计算COUNT

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

两个表中都存在一行-这是一个更新-我们需要对该记录执行新的计数。

一件事在这里非常重要-不要一一操作行。对于以上三种情况,请始终按批处理行,否则最终将导致触发器性能不佳,这将延迟原始表的CRUD操作。