Tout le monde sait que les nouveaux développeurs écrivent de longues fonctions. Au fur et à mesure que vous progressez, vous réussissez à diviser votre code en petits morceaux et l'expérience vous en apprend la valeur.
Entrez SQL. Oui, la façon SQL de penser le code est différente de la façon procédurale de penser le code, mais ce principe semble tout aussi applicable.
Disons que j'ai une requête qui prend la forme:
select * from subQuery1 inner join subQuerry2 left join subquerry3 left join join subQuery4
Utilisation de certains identifiants ou dates, etc.
Ces sous-requêtes sont elles-mêmes complexes et peuvent contenir leurs propres sous-requêtes. Dans aucun autre contexte de programmation, je ne pense que la logique des sous-requêtes complexes 1-4 appartient à ma requête parent qui les joint toutes. Il semble si simple que ces sous-requêtes doivent être définies comme des vues, tout comme elles seraient des fonctions si j'écrivais du code procédural.
Alors pourquoi cette pratique n'est-elle pas courante? Pourquoi les gens écrivent-ils si souvent ces longues requêtes SQL monolithiques? Pourquoi SQL n'encourage-t-il pas l'utilisation étendue des vues, tout comme la programmation procédurale encourage l'utilisation intensive des fonctions. (Dans de nombreux environnements d'entreprise, la création de vues n'est pas une tâche facile. Des demandes et des approbations sont nécessaires. Imaginez si d'autres types de programmeurs devaient soumettre une demande à chaque fois qu'ils créaient une fonction!)
J'ai pensé à trois réponses possibles:
C'est déjà courant et je travaille avec des gens inexpérimentés
Les programmeurs expérimentés n’écrivent pas de SQL complexe car ils préfèrent résoudre les problèmes de traitement des données avec du code procédural
Autre chose
Je pense que le principal problème est que toutes les bases de données ne prennent pas en charge les expressions de table communes.
Mon employeur utilise DB/2 pour beaucoup de choses. Les dernières versions de celui-ci prennent en charge les CTE, de sorte que je suis capable de faire des choses comme:
with custs as (
select acct# as accountNumber, cfname as firstName, clname as lastName,
from wrdCsts
where -- various criteria
)
, accounts as (
select acct# as accountNumber, crBal as currentBalance
from crzyAcctTbl
)
select firstName, lastName, currentBalance
from custs
inner join accounts on custs.accountNumber = accounts.accountNumber
Le résultat est que nous pouvons avoir des noms de table/champ fortement abrégés et je crée essentiellement des vues temporaires, avec des noms plus lisibles, que je peux ensuite utiliser. Bien sûr, la requête s'allonge. Mais le résultat est que je peux écrire quelque chose qui est assez clairement séparé (en utilisant les CTE comme vous utiliseriez les fonctions pour obtenir DRY) et vous retrouver avec du code qui est assez lisible. Et parce que je suis capable de décomposer mes sous-requêtes et d'avoir une référence de sous-requête à une autre, ce n'est pas tout "en ligne". J'ai, à l'occasion, écrit un CTE, puis quatre autres CTE l'ont tous référencé, puis j'ai demandé à l'union de requête principale les résultats de ces quatre derniers.
Cela peut être fait avec:
Mais cela va un long chemin vers un code plus propre, plus lisible, plus sec.
J'ai développé une "bibliothèque standard" de CTE que je peux brancher à diverses requêtes, me permettant de prendre un bon départ sur ma nouvelle requête. Certains d'entre eux commencent également à être adoptés par d'autres développeurs de mon organisation.
Avec le temps, il peut être judicieux de transformer certaines d'entre elles en vues, de sorte que cette "bibliothèque standard" soit disponible sans avoir besoin de copier/coller. Mais mes CTE finissent par être modifiés, très légèrement, pour divers besoins que je n'ai pas pu avoir un seul CTE utilisé SO WIDELY, sans mods, qu'il pourrait être utile de créer une vue.
Il semblerait qu'une partie de votre reproche soit "pourquoi ne suis-je pas au courant des CTE?" ou "pourquoi ma base de données ne prend-elle pas en charge les CTE?"
Quant aux mises à jour ... oui, vous pouvez utiliser des CTE mais, d'après mon expérience, vous devez les utiliser à l'intérieur de la clause set ET dans la clause where. Ce serait bien si vous pouviez en définir une ou plusieurs avant toute la déclaration de mise à jour, puis simplement avoir les parties "requête principale" dans les clauses set/where, mais cela ne fonctionne pas de cette façon. Et il est impossible d'éviter les noms de table/champ obscurs sur la table que vous mettez à jour.
Vous pouvez utiliser des CTE pour les suppressions. Il peut falloir plusieurs CTE pour déterminer les valeurs PK/FK des enregistrements que vous souhaitez supprimer de cette table. Encore une fois, vous ne pouvez pas éviter les noms de table/champ obscurs sur la table que vous modifiez.
Dans la mesure où vous pouvez effectuer une sélection dans un insert, vous pouvez utiliser des CTE pour les insertions. Comme toujours, vous pouvez avoir affaire à des noms de table/champ obscurs sur la table que vous modifiez.
SQL ne vous permet PAS de créer l'équivalent d'un objet de domaine, encapsulant une table, avec des getters/setters. Pour cela, vous devrez utiliser un ORM d'une certaine sorte, ainsi qu'un langage de programmation plus procédural/OO. J'ai écrit des choses de cette nature dans Java/Hibernate.
Le verrouillage de la création des vues de base de données est souvent effectué par des organisations paranoïaques aux problèmes de performances de la base de données. Il s'agit d'un problème de culture organisationnelle, plutôt que d'un problème technique avec SQL.
Au-delà de cela, de grandes requêtes SQL monolithiques sont écrites plusieurs fois, car le cas d'utilisation est si spécifique que très peu de code SQL peut être véritablement réutilisé dans d'autres requêtes. Si une requête complexe est nécessaire, c'est généralement pour un cas d'utilisation très différent. La copie du SQL à partir d'une autre requête est souvent un point de départ, mais en raison des autres sous-requêtes et JOIN dans la nouvelle requête, vous finissez par modifier le SQL copié juste assez pour casser toute sorte d'abstraction qu'une "fonction" dans une autre langue pourrait être utilisé pour. Ce qui m'amène à la raison la plus importante pour laquelle SQL est difficile à refactoriser.
SQL ne traite que de structures de données concrètes, pas d'un comportement abstrait (ou d'une abstraction dans n'importe quel sens du mot). Puisque SQL est écrit autour d'idées concrètes, il n'y a rien à résumer dans un module réutilisable. Les vues de base de données peuvent vous aider, mais pas au même niveau qu'une "fonction" dans une autre langue. Une vue de base de données n'est pas tant une abstraction qu'une requête. Eh bien, en fait, une vue de base de données est une requête. Il est essentiellement utilisé comme une table, mais exécuté comme une sous-requête, donc encore une fois, vous avez affaire à quelque chose de concret, pas abstrait.
C'est avec les abstractions que le code devient plus facile à refactoriser, car une abstraction cache les détails d'implémentation au consommateur de cette abstraction. Le SQL simple n'offre pas une telle séparation, bien que les extensions procédurales de SQL comme PL/SQL pour Oracle ou Transact-SQL pour SQL Server commencent à brouiller un peu les lignes.
La chose que je pense que vous pourriez manquer de votre question/point de vue est que SQL exécute des opérations sur des ensembles (en utilisant des opérations d'ensemble, etc.).
Lorsque vous opérez à ce niveau, vous abandonnez naturellement un certain contrôle au moteur. Vous pouvez toujours forcer du code de style procédural à l'aide de curseurs, mais comme l'expérience l'a montré 99/100 fois, vous ne devriez pas le faire.
Le refactoring SQL est possible, mais il n'utilise pas les mêmes principes de refactoring de code que ceux auxquels nous sommes habitués dans le code de niveau application. Au lieu de cela, vous optimisez la façon dont vous utilisez le moteur SQL lui-même.
Ça peut être fait de plusieurs façons. Si vous utilisez Microsoft SQL Server, vous pouvez utiliser SSMS pour vous fournir un plan d'exécution approximatif et vous pouvez l'utiliser pour voir quelles étapes vous pouvez effectuer pour régler votre code.
Dans le cas de la division du code en modules plus petits, comme l'a mentionné @ greg-burghardt, SQL est généralement un morceau de code spécialement conçu et par conséquent. Il fait la seule chose dont vous avez besoin et rien d'autre. Il adhère au S dans SOLID, il n'a qu'une seule raison d'être modifié/affecté et c'est à ce moment que vous avez besoin de cette requête pour faire autre chose. Le reste de l'acronyme (OLID) ne s'applique pas ici (AFAIK, il n'y a pas d'injection de dépendances, d'interfaces ou de dépendances en tant que telles dans SQL) selon la saveur du SQL que vous utilisez, vous pourrez peut-être étendre certaines requêtes en les encapsulant dans une procédure stockée/fonction de table ou en les utilisant comme sous-requêtes, je dirais que le principe ouvert-fermé s'appliquerait toujours, en quelque sorte. Mais je m'égare.
Je pense que vous devez changer votre paradigme en termes de visualisation du code SQL. En raison de la nature définie de celui-ci, il ne peut pas fournir beaucoup de fonctionnalités que les langages au niveau de l'application peuvent (génériques, etc.). SQL n'a jamais été conçu pour être quelque chose comme ça, c'est un langage pour interroger des ensembles de données, et chaque ensemble est unique à sa manière.
Cela étant dit, il existe des moyens de rendre votre code plus joli, si la lisibilité est une priorité élevée au sein de l'organisation. Stocker des bits de blocs SQL fréquemment utilisés (ensembles de données communs que vous utilisez) dans des procédures stockées/fonctions de valeur de table, puis les interroger et les stocker dans des tables/variables de table temporaires, puis les utiliser pour relier les éléments dans une seule transaction massive que vous écririez autrement est une option. À mon humble avis, il ne vaut pas la peine de faire quelque chose comme ça avec SQL.
En tant que langage, il est conçu pour être facilement lisible et compréhensible par n'importe qui, même les non-programmeurs. En tant que tel, à moins que vous ne fassiez quelque chose de très intelligent, il n'est pas nécessaire de refactoriser le code SQL en morceaux de taille octet plus petite. J'ai personnellement écrit des requêtes SQL massives tout en travaillant sur une solution ETL/Reporting d'entrepôt de données et tout était encore très clair en termes de ce qui se passait. Tout ce qui aurait pu paraître un peu bizarre à quelqu'un d'autre obtiendrait un bref ensemble de commentaires à côté pour fournir une brève explication.
J'espère que ça aide.
Je vais me concentrer sur les "sous-requêtes" dans votre exemple.
Pourquoi sont-ils utilisés si souvent? Parce qu'ils utilisent la façon naturelle de penser d'une personne: j'ai cet ensemble de données, et je veux faire une action sur un sous-ensemble et le joindre à un sous-ensemble d'autres données. 9 fois sur 10 que je vois une sous-requête, elle est mal utilisée. Ma blague courante sur les sous-requêtes est la suivante: les personnes qui ont peur des jointures utilisent des sous-requêtes.
Si vous voyez de telles sous-requêtes, c'est aussi souvent un signe de conception de base de données non optimale.
Plus votre base de données est normalisée, plus vous obtenez de jointures, plus votre base de données ressemble à une grande feuille Excel, plus vous obtenez de sous-sélections.
La refactorisation en SQL a souvent un objectif différent: obtenir plus de performances, de meilleurs temps de requête, "éviter les analyses de table". Ceux-ci peuvent même rendre le code moins lisible mais sont très précieux.
Alors pourquoi voyez-vous autant d'énormes requêtes monolithiques non refactorisées?
(pour moi, plus j'ai de l'expérience avec SQL, moins mes requêtes deviennent importantes, SQL a des moyens pour les personnes de tous niveaux de faire leur travail quoi qu'il arrive.)
Dans l'esprit SQL, la base de données est un actif partagé qui contient les données de l'entreprise et sa protection est d'une importance vitale. Entre dans le DBA en tant que gardien du temple.
La création d'une nouvelle vue dans la base de données est censée servir un objectif durable et être partagée par une communauté d'utilisateurs. Dans la vue DBA, cela n'est acceptable que si la vue est justifiée par la structure des données. Chaque changement de vue est alors associé à des risques pour tous ses utilisateurs actuels, même ceux qui n'utilisent pas l'application mais qui ont découvert la vue. Enfin, la création de nouveaux objets nécessite de gérer les autorisations, et dans le cas de la vue, de manière cohérente avec les autorisations des tables sous-jacentes.
Tout cela explique pourquoi les administrateurs de base de données n'aiment pas ajouter des vues uniquement pour le code d'une application individuelle.
Si vous décomposez l'une de vos requêtes complexes Nice, vous découvrirez peut-être que les sous-requêtes auront souvent besoin d'un paramètre qui dépend d'une autre sous-requête.
La transformation des sous-requêtes en vue n'est donc pas nécessairement aussi simple que prévu. Vous devez isoler les paramètres variables et concevoir votre vue afin que les paramètres puissent être ajoutés en tant que critères de sélection dans la vue.
Malheureusement, ce faisant, vous imposez parfois d'accéder à plus de données et moins efficacement que dans une requête personnalisée.
Vous pourriez espérer une refactorisation, en transférant certaines responsabilités aux extensions procédurales de SQL, comme PL/SQL ou T-SQL. Cependant, ceux-ci dépendent du fournisseur et créent une dépendance technologique. De plus, ces extensions s'exécutent sur le serveur de base de données, créant plus de charge de traitement sur une ressource qui est beaucoup plus difficile à mettre à l'échelle qu'un serveur d'applications.
Enfin, la séparation des tâches et la conception SQL avec sa force et ses limites sont-elles un vrai problème? Au final, ces bases de données se sont avérées efficaces et fiables pour gérer des données très critiques, y compris dans des environnements critiques.
Donc pour réussir une refactorisation:
considérez un meilleure communication. Essayez de comprendre les contraintes de votre DBA. Si vous prouvez à un DBA qu'une nouvelle vue est justifiée par les structures de données, qu'elle n'est pas une solution de contournement et qu'elle n'a pas d'impact sur la sécurité, il acceptera certainement de la laisser être créée. Parce que, alors ce serait un intérêt partagé.
nettoyez votre propre maison d'abord: Rien ne vous oblige à générer beaucoup de SQL dans beaucoup d'endroits. Refactorisez le code de votre application, pour isoler les accès SQL et pour créer les classes ou les fonctions afin de fournir des sous-requêtes réutilisables, si elles sont fréquemment utilisées.
améliorer sensibilisation de l'équipe: assurez-vous que votre application n'exécute pas des tâches qui pourraient être exécutées plus efficacement par le moteur de SGBD. Comme vous l'avez souligné à juste titre, l'approche procédurale et l'approche orientée données ne sont pas également maîtrisées par les différents membres de l'équipe. Cela dépend de leurs antécédents. Mais afin d'optimiser le système dans son ensemble, votre équipe doit le comprendre dans son ensemble. Alors, faites de la sensibilisation, pour être sûr que les joueurs moins expérimentés ne réinventent pas la roue et partagent leurs pensées DB avec des membres plus expérimentés.
Concernant les points 1 et 3: les vues ne sont pas le seul moyen. Il existe également des tables temporaires, des marts, des variables de table, des colonnes agrégées, des CTE, des fonctions, des procédures stockées et éventuellement d'autres constructions en fonction du SGBDR.
Les DBA (et je parle en tant que DBA et développeur) ont tendance à voir le monde d'une manière assez binaire, donc ils sont souvent contre des choses comme les vues et les fonctions en raison de la pénalité de performance perçue.
Plus récemment, le besoin de jointures complexes a diminué avec la reconnaissance du fait que les tables dénormalisées, bien qu'elles ne soient pas optimales du point de vue NF , sont très performantes.
Il y a aussi la tendance à faire des requêtes côté client avec des technologies comme LINQ que vous soulevez au point 2.
Bien que je convienne que SQL peut être difficile à modulariser, de grands progrès ont été réalisés bien qu'il y ait toujours une dichotomie entre le code côté client et SQL - bien que 4GL a quelque peu brouillé les lignes.
Je suppose que cela dépend vraiment de la mesure dans laquelle vos DBA/architectes/responsables techniques sont prêts à céder à cet égard. S'ils refusent d'autoriser autre chose que Vanilla SQL avec beaucoup de jointures, d'énormes requêtes pourraient en résulter. Si vous êtes coincé avec cela, ne vous cognez pas la tête contre un mur de briques, escaladez-le. Il existe généralement de meilleures façons de faire les choses avec un peu de compromis - surtout si vous pouvez en prouver les avantages.