Vous n'écririez pas une application avec des fonctions de 200 lignes. Vous décomposeriez ces longues fonctions en fonctions plus petites, chacune avec une seule responsabilité clairement définie.
Pourquoi écrire votre SQL comme ça?
Décomposez vos requêtes, tout comme vous décomposez vos fonctions. Cela les rend plus courts, plus simples, plus faciles à comprendre, plus faciles à tester , plus faciles à refactoriser. Et cela vous permet d'ajouter des "shims" entre eux, et des "wrappers" autour d'eux, comme vous le faites dans le code procédural.
Comment est-ce que tu fais ça? En transformant chaque chose importante d'une requête en vue. Ensuite, vous composez des requêtes plus complexes à partir de ces vues plus simples, tout comme vous composez des fonctions plus complexes à partir de fonctions plus primitives.
Et ce qui est formidable, c'est que pour la plupart des compositions de vues, vous obtiendrez exactement les mêmes performances de votre SGBDR. (Pour certains, vous ne le ferez pas; et alors? L'optimisation prématurée est la racine de tous les maux. Commencez par coder correctement, puis optimisez si vous en avez besoin.)
Voici un exemple d'utilisation de plusieurs vues pour décomposer une requête complexe.
Dans l'exemple, étant donné que chaque vue n'ajoute qu'une seule transformation, chacune peut être testée indépendamment pour trouver des erreurs, et les tests sont simples.
Voici la table de base dans l'exemple:
create table month_value(
eid int not null, month int, year int, value int );
Ce tableau est imparfait, car il utilise deux colonnes, mois et année, pour représenter une donnée, un mois absolu. Voici nos spécifications pour la nouvelle colonne calculée:
Nous ferons cela comme une transformation linéaire, de sorte qu'elle trie de la même manière que (année, mois), et de telle sorte que pour tout tuple (année, mois), il y ait une et seule valeur, et toutes les valeurs sont consécutives:
create view cm_absolute_month as
select *, year * 12 + month as absolute_month from month_value;
Maintenant, ce que nous devons tester est inhérent à notre spécification, à savoir que pour tout tuple (année, mois), il y a un et un seul (mois_absolu), et que les (mois_absolus) sont consécutifs. Écrivons quelques tests.
Notre test sera une select
requête SQL , avec la structure suivante: un nom de test et une instruction case caténés ensemble. Le nom du test est simplement une chaîne arbitraire. L'instruction case n'est que case when
des instructions de test then 'passed' else 'failed' end
.
Les instructions de test ne seront que des sélections SQL (sous-requêtes) qui doivent être vraies pour que le test réussisse.
Voici notre premier test:
--a select statement that catenates the test name and the case statement
select concat(
-- the test name
'For every (year, month) there is one and only one (absolute_month): ',
-- the case statement
case when
-- one or more subqueries
-- in this case, an expected value and an actual value
-- that must be equal for the test to pass
( select count(distinct year, month) from month_value)
--expected value,
= ( select count(distinct absolute_month) from cm_absolute_month)
-- actual value
-- the then and else branches of the case statement
then 'passed' else 'failed' end
-- close the concat function and terminate the query
);
-- test result.
L'exécution de cette requête produit ce résultat: For every (year, month) there is one and only one (absolute_month): passed
Tant qu'il y a suffisamment de données de test dans month_value, ce test fonctionne.
Nous pouvons également ajouter un test pour des données de test suffisantes:
select concat( 'Sufficient and sufficiently varied month_value test data: ',
case when
( select count(distinct year, month) from month_value) > 10
and ( select count(distinct year) from month_value) > 3
and ... more tests
then 'passed' else 'failed' end );
Maintenant, testons c'est consécutif:
select concat( '(absolute_month)s are consecutive: ',
case when ( select count(*) from cm_absolute_month a join cm_absolute_month b
on ( (a.month + 1 = b.month and a.year = b.year)
or (a.month = 12 and b.month = 1 and a.year + 1 = b.year) )
where a.absolute_month + 1 <> b.absolute_month ) = 0
then 'passed' else 'failed' end );
Maintenant, mettons nos tests, qui ne sont que des requêtes, dans un fichier, et exécutons ce script sur la base de données. En effet, si nous stockons nos définitions de vue dans un script (ou des scripts, je recommande un fichier par vues associées) à exécuter sur la base de données, nous pouvons ajouter nos tests pour chaque vue au même script, de sorte que l'acte de (re -) la création de notre vue exécute également les tests de la vue. De cette façon, nous obtenons tous les deux des tests de régression lorsque nous recréons des vues et, lorsque la création de la vue s'exécute contre la production, la vue sera également testée en production.