如何创建儿童计数的索引视图

Posted

技术标签:

【中文标题】如何创建儿童计数的索引视图【英文标题】:How to create indexed view of children count 【发布时间】:2019-02-14 06:00:30 【问题描述】:

我正在尝试获取具有父子关系的表并获取子节点的数量。我想使用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
db<>fiddle demo

【问题讨论】:

【参考方案1】:

我认为使用 CTE 和 LEFT JOIN 都无法实现这一点,因为有很多 restriction using the indexed views。

解决方法

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

    创建索引视图而不是公用表表达式 (CTE) 创建一个执行 LEFT JOIN 的非索引视图

除此之外,在表 ExampleEntity 列上创建一个非聚集索引。

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

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

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

附加信息

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

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'

拆分视图

笛卡尔连接

时间统计

时间统计显示笛卡尔join方法的执行时间高于Splitting view方法,如下图所示(cartesian join向右):

添加 WITH(NOEXPAND)

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

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

【讨论】:

是的 - 将聚集索引添加到 Parent 肯定会提高性能......并且同意这比 OP 的解决方案更直接。 虽然我的笛卡尔连接更适合我的原始用例,但最终我不得不同意这是解决这个问题的首选且不那么麻烦的解决方案。感谢您比较性能。【参考方案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(*)

【讨论】:

我认为它在处理大表时性能不佳。我建议尝试这两种方法并比较性能。 还要将ON indexedView(Parent) 更改为ON indexedView(Entity),因为视图中没有名为 parent 的列 谢谢,是的,我在编辑中将 Parent 更改为 Entity 并忘记更改索引。我会测试一下 我认为它会运行良好...成本将像触发器一样被吸收在插入/更新/删除上。 @Parox 最好将视图拆分为其他答案中提到的 2 个视图,还请查看答案中提供的参考资料,您会看到其他专家使用了这种方法【参考方案3】:

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

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

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

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

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

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

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

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

【讨论】:

很好的建议,绝对与索引视图相同,但需要更多的前期成本才能让一切都恰到好处,但是我知道最终它们都有相似的影响和内部需要的管理我'仍然想在物化表上使用普通索引(物化)视图的解决方案

以上是关于如何创建儿童计数的索引视图的主要内容,如果未能解决你的问题,请参考以下文章

Firebase:按儿童计数排序的查询

javascript 计数菜单儿童

如何在儿童版块中为 iOS 应用设置家长门

反应原生儿童视图 justifyContent 属性不起作用

在向儿童故事添加儿童故事时,TFS故事从董事会中消失

在使用pandas MultiIndex时,如何基于索引值进行插值?