3
votes

Comment créer une vue indexée du nombre d'enfants

J'essaie de prendre une table avec une relation parent-enfant et d'obtenir le nombre d'enfants. Je voudrais créer une vue indexée du nombre d'enfants en utilisant COUNT_BIG (*) .

Le problème est que dans ma vue d'index, je ne veux pas éliminer les entités qui n'ont pas enfants, à la place je veux que le Count soit 0 pour ceux-ci.

Étant donné

    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

Je veux créer une vue indexée qui renvoie

> Entity | Count
> :----- | ----:
> A      |     2
> AA     |     3
> AB     |     2
> ABA    |     0
> ABB    |     0
> AAA    |     0
> AAB    |     0
> AAC    |     0

Voici mon SQL qui fonctionne, mais en utilisant un LEFT JOIN et un CTE (les deux ne sont pas autorisés dans une vue d'index)

> 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    


0 commentaires

3 Réponses :


0
votes

Vous pouvez créer APRÈS INSÉRER, METTRE À JOUR, SUPPRIMER trigger sur votre table example et une nouvelle table pour matérialiser les résultats.

Dans le déclencheur, vous êtes autorisé à utiliser n'importe quelle instruction. Vous pouvez le faire de deux manières, en fonction de la rapidité de votre requête initiale.

Par exemple, vous pouvez tronquer la table à chaque INSERT / UPDATE / DELETE puis calculer le nombre et l'insérer à nouveau (si la requête est rapide).

Ou vous pouvez vous fier aux tables insérées et supprimées qui sont des tables spéciales visibles dans le contexte du déclencheur et montrant comment les valeurs des lignes ont changé.

Par exemple, si un enregistrement existe dans la table insérée et non dans le supprimé - il s'agit d'une ou plusieurs nouvelle (s) ligne (s). Vous ne pouvez calculer le COUNT que pour eux.

Si un enregistrement n'existe que dans la table supprimé - il s'agit d'une suppression (nous devons supprimer la ligne de notre table précalculée).

Et une ligne existe dans les deux tables - il s'agit d'une mise à jour - nous devons effectuer un nouveau décompte pour l'enregistrement.

Une chose est très importante ici - ne manipulez pas les lignes une par une. Travaillez toujours par lots de lignes pour les trois cas ci-dessus ou vous vous retrouverez avec un déclencheur peu performant qui retardera les opérations CRUD avec la table d'origine.


1 commentaires

Excellente suggestion et agit certainement de la même manière qu'une vue indexée, mais avec un coût initial plus élevé pour que tout soit parfait, mais je comprends qu'en fin de compte, ils ont tous deux un impact similaire et une gestion requise en interne.J'aimerais toujours utiliser la solution. une vue normale indexée (matérialisée) sur une table matérialisée



2
votes

Je ne pense pas que vous puissiez y parvenir en utilisant un CTE ni un LEFT JOIN car il y en a beaucoup restriction utilisant les vues indexées .

Solution de contournement

Je suggère de diviser la requête en deux parties:

  1. Créer une vue indexée au lieu d'une expression de table commune (CTE)
  2. Créer une vue non indexée qui effectue la jointure à gauche

À côté de cela, créez un index non clusterisé sur la colonne Entity dans le tableau Exemple .

Ensuite, lorsque vous interrogez le -indexée, elle utilisera des index

SELECT * FROM View WHERE Entity = 'AA'

Donc, lorsque vous exécutez la requête suivante:

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

Vous pouvez voir que la vue Clustered index et Table Non-Clustered index sont utilisées dans le plan d'exécution:

 entrez la description de l'image ici

Informations supplémentaires

Je n'ai pas trouvé de solutions de contournement supplémentaires pour remplacer l'utilisation de LEFT JOIN ou UNION ou CTE dans les vues indexées, vous pouvez en vérifier plusieurs questions Stackoverflow similaires:


Mise à jour 1 - Vue fractionnée vs jointure cartésienne

Pour identifier la meilleure approche, j'ai essayé de comparer les deux approches suggérées.

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


2 commentaires

Oui - obtenir l'index clusterisé sur Parent sera certainement une amélioration des performances ... et convenez que c'est plus simple que la solution de l'OP.


Bien que ma jointure cartésienne corresponde un peu mieux à mon cas d'utilisation d'origine, je dois finalement convenir que c'est la solution privilégiée et la moins piratée à ce problème. Merci pour la comparaison des performances.



2
votes

J'ai pu accomplir ce que je cherchais en faisant une jointure cartésienne sur les lignes qui serait 0 count (N = 2).

Créer une table appelée deux lignes qui dupliquera les petits-enfants

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

Récupère la table d'origine

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

Créer l'index view

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

INSERT INTO TwoRows
VALUES (1),(2)

Je n'ai pas pu éviter d'utiliser COUNT_BIG(*)


8 commentaires

Je ne pense pas que ce soit performant lors de la manipulation d'énormes tables. Je suggère d'essayer les deux approches et de comparer les performances.


Remplacez également ON indexedView (Parent) par ON indexedView (Entity) car il n'y a pas de colonne appelée parent dans la vue


Merci, ouais, j'ai changé Parent en Entité dans une modification et j'ai oublié de changer l'index. Je vais le tester


Je pense que cela fonctionnera bien ... le coût sera absorbé lors de l'insertion / mise à jour / suppression comme avec un déclencheur.


@Parox il vaut mieux diviser la vue en 2 vues comme mentionné dans l'autre réponse, jetez également un œil aux références fournies dans la réponse, vous verrez que cette approche est utilisée par d'autres experts


@Clay comparé à l'approche de déclenchement, cette approche est certainement meilleure, mais avez-vous vérifié l'autre approche de réponse? c'est plus droit.


@Parox si vous cherchez à obtenir une meilleure logique que l'autre approche est meilleure, je ne sais pas quel est l'avantage d'utiliser cette approche. J'ai voté pour la question car c'est un sujet intéressant, mais je n'ai pas voté pour cette réponse car je ne trouve pas que cela soit un bon moyen de remplacer CTE et LEFT JOIN .


@ParoX j'ai essayé de faire une comparaison entre les deux approches, vérifiez ma mise à jour de réponse