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
3 Réponses :
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.
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
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 .
Je suggère de diviser la requête en deux parties:
À 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:
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:
Pour identifier la meilleure approche, j'ai essayé de comparer les deux approches suggérées.
SELECT * FROM dbo.ExampleView_2 WHERE Entity = 'A'
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.
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(*)
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