Tout le monde sait que les nouveaux développeurs écrivent de longues fonctions. Au fur et à mesure que vous progressez, vous parvenez de mieux en mieux à diviser votre code et l'expérience vous apprend l'intérêt de le faire.
Entrez SQL. Oui, la façon de penser SQL sur 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
Utiliser des identifiants ou des dates, etc.
Ces sous-requêtes sont elles-mêmes complexes et peuvent contenir des sous-requêtes qui leur sont propres. Dans aucun autre contexte de programmation, je ne penserais que la logique des sous-requêtes complexes 1 à 4 appartient à la requête parent qui les joint toutes. Il semble si simple que ces sous-requêtes soient définies comme des vues, comme si elles étaient des fonctions si j'écrivais du code de procédure.
Alors pourquoi n'est-ce pas une pratique courante? Pourquoi les gens écrivent-ils si souvent ces longues requêtes SQL monolithiques? Pourquoi SQL n'encourage-t-il pas une utilisation étendue des vues, tout comme la programmation procédurale encourage une utilisation intensive des fonctions. (Dans de nombreux environnements d'entreprise, la création de vues n'est même 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 personnes inexpérimentées
Les programmeurs expérimentés n'écrivent pas de code SQL complexe, car ils préfèrent résoudre les problèmes difficiles de traitement de données avec du code procédural.
Autre chose
la source
Réponses:
Je pense que le problème principal 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, tels que je suis capable de faire des choses comme:
Le résultat est que nous pouvons avoir des noms de table / champ très 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 des fonctions pour obtenir DRY) et me retrouver avec un code assez lisible. Et parce que je suis capable de décomposer mes sous-requêtes et d'avoir une sous-requête en référence à une autre, tout n'est pas "en ligne". J'ai parfois écrit un CTE, puis quatre autres CTE y ont fait référence, puis la requête principale regroupait les résultats des quatre derniers.
Cela peut être fait avec:
Mais cela contribue grandement à rendre le code plus propre, plus lisible, plus sec.
J'ai développé une "bibliothèque standard" de CTE que je peux intégrer à différentes requêtes, ce qui me permet de prendre un bon départ avec ma nouvelle requête. Certains d'entre eux commencent également à être adoptés par d'autres développeurs de mon organisation.
À terme, il peut être judicieux de transformer certaines de ces vues en vues, de sorte que cette "bibliothèque standard" soit disponible sans qu'il soit nécessaire de copier / coller. Mais mes CTE finissent par être légèrement modifiés, pour des besoins divers, pour lesquels je n’ai pas pu utiliser un seul CTE si largement utilisé, sans mods, qu’il serait peut-être intéressant de créer une vue.
Il semblerait qu'une partie de votre problème soit "pourquoi je ne connais pas les CTE?" ou "pourquoi ma base de données ne prend-elle pas en charge les CTE?"
En ce qui concerne les mises à jour ... oui, vous pouvez utiliser les CTE mais, selon mon expérience, vous devez les utiliser dans la clause set AND dans la clause where. Ce serait bien si vous pouviez définir une ou plusieurs en avant de la déclaration de mise à jour complète, puis placer les parties "requête principale" dans les clauses set / where, mais cela ne fonctionnerait pas ainsi. Et il n'y a pas moyen d'éviter les noms de table / champ obscurs sur la table que vous mettez à jour.
Vous pouvez utiliser les CTE pour les suppressions. Il faut parfois plusieurs CTE pour déterminer les valeurs PK / FK des enregistrements à supprimer de cette table. Encore une fois, vous ne pouvez pas éviter les noms obscurs de table / champ sur la table que vous modifiez.
Dans la mesure où vous pouvez effectuer une sélection dans une insertion, 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 quelconque, avec un langage de programmation / procédure plus procédural. J'ai écrit des choses de cette nature en Java / Hibernate.
la source
Le verrouillage de la création des vues de base de données est souvent effectué par des organisations paranoïaques de problèmes de performances dans la base de données. Il s’agit d’un problème de culture organisationnelle plutôt que technique avec SQL.
Au-delà de cela, les requêtes SQL monolithiques volumineuses sont écrites plusieurs fois, car le cas d'utilisation est si spécifique que très peu de code SQL peut être réellement 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. Copier le SQL à partir d’une autre requête est souvent un point de départ, mais en raison des autres sous-requêtes et JOIN de la nouvelle requête, vous modifiez le SQL copié juste assez pour rompre toute sorte d’abstraction qu’une "fonction" dans un autre langage être utilisé pour. Ce qui m'amène à la raison la plus importante pour laquelle SQL est difficile à refactoriser.
SQL ne traite que des structures de données concrètes, pas un comportement abstrait (ou une abstraction dans le sens du mot). Comme 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 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. 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. Vous avez donc affaire à quelque chose de concret et non d’abstrait.
C'est avec les abstractions que le code devient plus facile à refactoriser, car une abstraction cache les détails d'implémentation du consommateur de cette abstraction. Straight SQL n'offre aucune séparation de ce type, bien que les extensions de procédure de SQL, telles que PL / SQL pour Oracle ou Transact-SQL pour SQL Server, commencent à brouiller un peu les lignes.
la source
Ce qui me manque dans votre question / point de vue, c'est que SQL exécute des opérations sur des ensembles (à l'aide d'opérations de l'ensemble, etc.).
Lorsque vous travaillez à ce niveau, vous abandonnez naturellement un certain contrôle sur le moteur. Vous pouvez toujours forcer certains codes de style procédural à l'aide de curseurs, mais comme l'expérience le montre 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 utilisés dans le code au niveau de l'application. Au lieu de cela, vous optimisez la manière 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 que vous pouvez utiliser pour voir les étapes à suivre pour ajuster votre code.
Comme @ greg-burghardt a divisé le code en modules plus petits, SQL est généralement un élément de code spécialement conçu à cet effet. Il ne fait qu'une 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-là 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épendance, d'interfaces ou de dépendances telles que dans SQL), selon le type de code SQL que vous utilisez, vous pourrez éventuellement étendre certaines requêtes en les encapsulant. Dans une procédure stockée / une fonction de table ou en les utilisant comme sous-requêtes, je dirais que le principe d'ouverture-fermeture s'appliquerait toujours, d'une certaine manière. Mais je m'égare.
Je pense que vous devez modifier votre paradigme en ce qui concerne la manière dont vous visualisez le code SQL. En raison de la nature de celui-ci, il ne peut pas fournir beaucoup des fonctionnalités que les langages de niveau application peuvent (génériques, etc.). Le langage SQL n'a jamais été conçu pour ressembler à cela. C'est un langage permettant d'interroger des ensembles de données et chaque ensemble est unique à sa manière.
Cela étant dit, il existe des moyens d'améliorer l'apparence de votre code, si la lisibilité est une priorité absolue au sein de l'organisation. Stocker des bits de blocs SQL fréquemment utilisés (ensembles de données courants 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 utiliser ceux-ci pour relier les pièces dans une transaction massive que vous auriez écrit autrement est une option. IMHO ce n'est 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 quiconque, 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 plus petits en octets. J'ai personnellement écrit des requêtes SQL massives alors que je travaillais sur une solution ETL / Reporting d'entrepôt de données et tout était encore très clair en ce qui concerne ce qui se passait. Tout ce qui aurait pu paraître un peu bizarre à quiconque obtiendrait un bref ensemble de commentaires pour fournir une brève explication.
J'espère que ça aide.
la source
Je vais me concentrer sur les "sous-requêtes" de 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 effectuer une action sur un sous-ensemble d’entre elles et le joindre à un sous-ensemble d’autres données. 9 fois sur 10, je vois une sous-requête, elle est mal utilisée. Ma blague 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 le signe d'une 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.
Le refactoring 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 tant d’énormes requêtes monolithiques non refactorisées?
(Pour moi, plus SQL est expérimenté, moins mes requêtes sont volumineuses, SQL offre aux personnes de tous les niveaux des moyens de faire leur travail, quoi qu'il en soit.)
la source
Séparation des tâches
Dans l’esprit SQL, la base de données est un actif partagé contenant les données de la société et sa protection est d’une importance vitale. Entre dans la 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 des autorisations de gestion et, dans le cas d’une vue, cohérentes avec les autorisations des tables sous-jacentes.
Tout cela explique pourquoi les administrateurs de base de données n'aiment pas ajouter des vues destinées uniquement au code d'une application individuelle.
Conception SQL
Si vous décomposez une de vos requêtes complexes, vous découvrirez peut-être que les sous-requêtes auront souvent besoin d'un paramètre dépendant d'une autre sous-requête.
Donc, transformer les sous-requêtes en vue n’est pas nécessairement aussi simple qu’il est indiqué. Vous devez isoler les paramètres de variable et concevoir votre vue de manière à ce que les paramètres puissent être ajoutés en tant que critères de sélection dans la vue.
Malheureusement, vous imposez parfois d’accéder à plus de données et avec moins d’efficacité que lors d’une requête personnalisée.
Extensions propriétaires
Vous pouvez espérer une refactorisation en transférant certaines responsabilités à des extensions procédurales de SQL, telles que PL / SQL ou T-SQL. Cependant, ceux-ci dépendent des fournisseurs et créent une dépendance technologique. En outre, ces extensions s'exécutent sur le serveur de base de données, ce qui crée une charge de traitement supplémentaire sur une ressource beaucoup plus difficile à mettre à l'échelle qu'un serveur d'applications.
Mais quel est le problème à la fin?
Enfin, la séparation des tâches et la conception SQL avec sa force et ses limites constituent-elles un problème réel? En fin de compte, ces bases de données ont réussi à gérer de manière fiable et fiable des données très critiques, y compris dans des environnements critiques.
Donc, pour réussir une refactorisation:
envisager une meilleure communication . Essayez de comprendre les contraintes de votre DBA. Si vous prouvez à un administrateur de base de données qu'une nouvelle vue est justifiée par les structures de données, qu'il ne s'agit pas d'une solution de remplacement jetable et qu'elle n'a pas d'incidence sur la sécurité, il / elle acceptera certainement de la laisser être créée. Parce que alors ce serait un intérêt partagé.
Commencez par nettoyer votre maison : rien ne vous oblige à générer beaucoup de SQL dans de nombreux endroits. Refactorisez le code de votre application pour isoler les accès SQL et créer les classes ou les fonctions afin de fournir des sous-requêtes réutilisables, si celles-ci sont fréquemment utilisées.
améliorer la visibilité des équipes : assurez-vous que votre application n'effectue pas de tâches pouvant ê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 maîtrisées de la même manière par différents membres de l'équipe. Cela dépend de leurs antécédents. Mais pour optimiser le système dans son ensemble, votre équipe doit le comprendre dans son ensemble. Créez donc une prise de conscience et assurez-vous que les joueurs moins expérimentés ne réinventent pas la roue et ne partagent pas leurs pensées avec les membres plus expérimentés.
la source
Points 1 et 3: Les vues ne sont pas le seul moyen. Il existe également des tables temporaires, des marchés, 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 administrateurs de base de données (et je parle en tant que personne qui a été à la fois administrateur de base de données et développeur) ont tendance à voir le monde d'une manière assez binaire, de sorte qu'ils s'opposent souvent à des éléments tels que les vues et les fonctions en raison de la pénalité de performance perçue.
Dernièrement, le besoin de jointures complexes a diminué avec la reconnaissance du fait que les tables dénormalisées, bien qu’elles soient sous-optimales d’un point de vue NF , sont très performantes.
Il existe également une tendance à effectuer des requêtes côté client avec des technologies telles que LINQ, que vous abordez au point 2.
Bien que je convienne que la modularisation de SQL peut être difficile, de grands progrès ont été accomplis, même s'il restera toujours une dichotomie entre le code côté client et SQL - bien que 4GL ait un peu flouté les lignes.
Je suppose que cela dépend vraiment de la mesure dans laquelle vos DBA / architectes / responsables techniques sont disposés à céder à cet égard. S'ils refusent d'autoriser tout ce qui n'est pas SQL vanille avec beaucoup de jointures, des requêtes énormes pourraient en résulter. Si vous êtes coincé avec ceci, ne vous cognez pas la tête contre un mur de briques, escaladez-le. Il y a généralement de meilleures façons de faire les choses avec un peu de compromis - surtout si vous pouvez prouver les avantages.
la source