Как создать индексированное представление количества детей - PullRequest
0 голосов
/ 14 февраля 2019

Я пытаюсь взять таблицу с родительскими и дочерними отношениями и узнать количество детей.Я хотел бы создать индексированное представление количества дочерних элементов, используя 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 голосов
/ 17 февраля 2019

Я не думаю, что вы можете достичь этого, используя CTE и LEFT JOIN, потому что существует множество ограничений , использующих индексированные представления .

Обходной путь

Я предлагаю разделить запрос на две части:

  1. Создать индексированное представление вместо общего табличного выражения (CTE)
  2. Создайте неиндексированное представление, которое выполняет LEFT JOIN

Помимо этого, создайте некластеризованный индекс для столбца Entity в таблице 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

Таким образом, когда вы выполняете следующий запрос:

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

Вы можете видеть, что представление Кластеризованный индекс и Таблица Некластеризованный индексиспользуются в плане выполнения:

enter image description here

Дополнительная информация

Не найдено дополнительных обходных путей для замены использования LEFT JOIN или UNION или CTE в индексированных представлениях вы можете проверить много похожих вопросов Stackoverflow:


Обновление 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'

Представление с разделением

enter image description here

Декартово соединение

enter image description here

Статистика времени

Статистика времени показывает, что время выполнения подхода декартового объединения больше, чем подхода представления расщепления, как показано на рисунке ниже (декартовое соединение справа):

enter image description here

Добавление WITH (NOEXPAND)

Также я попытался добавить опцию WITH(NOEXPAND) для подхода декартового объединения, чтобы заставить базу данныхмеханизм для использования индексированного представления кластеризованного индекса, и результат был следующим:

enter image description here

I cПосле того, как все кэши были выполнены и проведено сравнение, сравнение статистики времени показывает, что подход представления с разделением по-прежнему быстрее, чем подход декартового объединения (WITH(NOEXPAND) подход справа) :

enter image description here

0 голосов
/ 17 февраля 2019

Я смог выполнить то, что хотел, выполнив декартово соединение в строках, которое будет равно 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(*)

0 голосов
/ 14 февраля 2019

Вы можете создать AFTER INSERT,UPDATE, DELETE триггер для своей таблицы example и новую таблицу для материализации результатов.

В триггере вы можете использовать любой оператор.Вы можете сделать это двумя способами, в зависимости от того, насколько быстро ваш первоначальный запрос.

Например, вы можете обрезать таблицу на каждом INSERT/UPDATE/DELETE, а затем вычислить число и вставить его снова (еслизапрос выполняется быстро).

Или вы можете положиться на таблицы inserted и deleted, которые являются специальными таблицами, видимыми в контексте триггера и показывающими, как изменились значения строк.

Например,если запись существует в таблице inserted, а не в deleted - это новая строка (строки).Вы можете рассчитать COUNT только для них.

Если запись существует только в таблице deleted - это удаление (нам нужно удалить строку для нашей предварительно вычисленной таблицы).

И в обеих таблицах есть строки - это обновление - нам нужно выполнить новый подсчет для записи.

Здесь очень важно одно - не манипулируйте строками одна за другой.Всегда работайте партиями строк для трех вышеописанных случаев, иначе вы получите неэффективный триггер, который задержит операции CRUD с исходной таблицей.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...