Pourquoi SQL n'est-il pas plus refactorable? [fermé]

39

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:

  1. C'est déjà courant et je travaille avec des personnes inexpérimentées

  2. 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.

  3. Autre chose

les frères
la source
12
Il existe des organisations qui vous permettent uniquement d'interroger une base de données via des vues et de la modifier via des procédures stockées.
Pieter B
3
SQL est devenu beaucoup plus agréable pour moi quand j'ai finalement accepté que cela ne serait jamais aussi sec que mon code de procédure normal.
Graham
1
4. SQL est vraiment vieux et n'a pas été matériellement mis à jour depuis des décennies. Pour des tâches très complexes, de nombreuses équipes optent pour des procédures stockées. Vous pouvez ajouter différentes clauses pour cela. Parfois, il suffit d'exécuter des tâches pour organiser les données dans une table temporaire, puis les joindre. Découvrez à quel point les langages déclaratif et procédural sont différents.
Berin Loritsch
8
De plus, une des raisons est qu’il existe un problème de performances horrible appelé "jointure triangulaire" qui peut survenir lorsque vous utilisez des vues (tout à fait par accident, bien sûr). Si votre requête joint les vues A et B, mais que la vue A utilise également dans sa mise en oeuvre la vue B, vous commencez à voir ce problème. Ainsi, les gens commencent souvent par écrire une seule requête monolithique afin de voir ce qui fonctionnerait le mieux en termes de refactorisation des vues, puis de leur date limite, et le monolithe passera en production. Un peu comme 98% de tous les développeurs de logiciels, vraiment :) :)
Stephen Byrne
3
"Imaginez si d'autres types de programmeurs devaient soumettre une demande à chaque fois qu'ils créaient une fonction" ... euh. Vous ne faites pas de critiques de code?
svidgen

Réponses:

25

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:

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

  • DB / 2
  • PostGreSQL
  • Oracle
  • MS SQL Server
  • MySQL (dernière version; encore un peu nouvelle)
  • probablement d'autres

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.

Meower68
la source
4
Nous avons eu M. Big CTE homme être l'homme qui écrit le pire SQL. Le problème était que les CTE étaient de mauvais choix d’abstraction et que l’optimiseur ne pouvait pas annuler tous les algorithmes que vous
Joshua
3
De plus, ORM peut faire des choses assez abominables sur le plan des performances, aussi ... surtout lorsque vous utilisez juste des accesseurs et des passeurs pour récupérer un tas de données. Hibernate est bien connu pour utiliser des centaines de requêtes individuelles au lieu d'une grosse requête jointe, ce qui pose problème lorsqu'il y a une surcharge pour chaque requête.
user3067860
2
@Joshua Vous pouvez écrire un mauvais code dans n'importe quelle langue. Y compris SQL. Mais le refactoring aux CTE, effectué correctement, peut créer des conceptions ascendantes plus faciles à analyser par les humains. J'ai tendance à considérer cela comme un trait souhaitable, quelle que soit la langue avec laquelle je traite :-)
Meower68
2
Les autres réponses sont excellentes, mais c’est ce que je recherchais personnellement. "Pourquoi est-ce que je ne connais pas les CTE" était la majorité de mes problèmes.
ebrts
2
@ Meower68 L'utilisation intensive de CTE empêche-t-elle les personnes d'apprendre correctement les jointures et de se familiariser avec une bonne conception de bases de données? Je soutiens la valeur des CTE, mais cela facilite également le travail avec les sous-requêtes, là où vous ne devriez pas.
Pieter B
36

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.

Greg Burghardt
la source
"SQL ne traite que des structures de données concrètes, pas un comportement abstrait (ni une abstraction dans le sens du mot)." C’est une déclaration étrange, car de mon point de vue, SQL traite uniquement du comportement abstrait et non de la programmation concrète dans le sens du terme! Il suffit de considérer tous les énormes degrés de complexité qui sont résumés dans le simple mot "JOIN": vous dites que vous voulez un résultat fusionné tiré de deux ensembles de données différents, et laissez le SGBD déterminer les techniques concrètes impliquées. indexation, gestion de la différence entre les tables et les sous-requêtes, etc ...
Mason Wheeler
5
@MasonWheeler: J'imaginais que je pensais davantage à SQL du point de vue des données sur lesquelles il fonctionne que de la mise en œuvre des fonctionnalités du langage. Les tables d'une base de données ne semblent pas être une abstraction. Ils sont concrets, comme dans un tableau appelé "phone_numbers" qui contient des numéros de téléphone. Un numéro de téléphone n'est pas un concept abstrait.
Greg Burghardt
12

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.

Toni Kostelac
la source
6

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?

  • SQL, à bien des égards, n'est pas un langage de programmation.
  • Mauvaise conception de la base de données.
  • Les gens ne parlent pas vraiment SQL.
  • Aucun pouvoir sur la base de données (par exemple, ne pas être autorisé à utiliser des vues)
  • Des objectifs différents avec le refactoring.

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

Pieter B
la source
6
Les "sous-requêtes" sont tout aussi susceptibles de constituer une agrégation d'une base de données correctement normalisée que de la normalisation ad hoc d'une base de données non normalisée
Caleth
@ Caleth c'est tellement vrai.
Pieter B
5
Même dans des bases de données bien normalisées, il est souvent nécessaire de joindre des sous-requêtes plutôt que de joindre directement des tables. Par exemple, si vous avez besoin de rejoindre des données groupées.
Barmar
1
@Barmar certainement, d'où mon 9 sur 10 commentaire. Les sous-requêtes ont leur place mais je les vois surexploitées par des personnes inexpérimentées.
Pieter B
J'aime votre métrique de "nombre de sous-requêtes" comme une indication de la normalisation de la base de données (ou de l'absence de celle-ci).
Jason
2

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.

Christophe
la source
+1 Quelques points intéressants ici. Compte tenu de l'état de certains SQL, la réticence des administrateurs de bases de données à autoriser les vues est souvent parfaitement compréhensible. En outre, SQL peut certainement tirer parti de l'examen par les pairs s'il a besoin de beaucoup de ressources et / ou s'il est exécuté fréquemment.
Robbie Dee
1

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.

Robbie Dee
la source
1
Je n'ai jamais entendu parler d'une construction "mart". Qu'est-ce que c'est?
évêque
1
Les marchés ne sont qu'un sous-ensemble du référentiel (base de données master). Si des requêtes complexes spécifiques doivent être exécutées, une base de données spéciale peut être créée spécifiquement pour répondre à ces demandes. Un exemple très courant est un magasin de reportage.
Robbie Dee
1
Confus pourquoi cela a été voté. Ne répond pas directement à la question, mais donne une réponse implicite assez claire de "l'option 3: il existe de nombreuses façons de gérer cela, qui sont largement utilisées".
Dewi Morgan
TIL sur les dépôts de données. Avoir un +1!
évêque