Quels sont les anti-modèles SQL les plus courants? [fermé]
nous tous qui travaillons avec des bases de données relationnelles avons appris (ou apprenons) que SQL est différent. Obtenir les résultats souhaités, et le faire efficacement, implique un processus fastidieux caractérisé en partie par l'apprentissage de paradigmes inconnus, et de découvrir que certains de nos modèles de programmation les plus familiers ne fonctionnent pas ici. Quels sont les antimodèles communs que vous avez vu (ou vous avez commis)?
30 réponses
je suis constamment déçu par la tendance de la plupart des programmeurs à mélanger leur logique UI-dans la couche d'accès aux données:
SELECT
FirstName + ' ' + LastName as "Full Name",
case UserRole
when 2 then "Admin"
when 1 then "Moderator"
else "User"
end as "User's Role",
case SignedIn
when 0 then "Logged in"
else "Logged out"
end as "User signed in?",
Convert(varchar(100), LastSignOn, 101) as "Last Sign On",
DateDiff('d', LastSignOn, getDate()) as "Days since last sign on",
AddrLine1 + ' ' + AddrLine2 + ' ' + AddrLine3 + ' ' +
City + ', ' + State + ' ' + Zip as "Address",
'XXX-XX-' + Substring(
Convert(varchar(9), SSN), 6, 4) as "Social Security #"
FROM Users
normalement, les programmeurs font cela parce qu'ils ont l'intention de lier leur ensemble de données directement à une grille, et il est juste pratique D'avoir SQL Serveur format Côté Serveur que le format sur le client.
Les requêtescomme celle ci-dessus sont extrêmement fragiles parce qu'elles relient étroitement la couche de données à la couche UI. Sur le dessus de cela, ce style de programmation empêche complètement les procédures stockées d'être réutilisables.
Voici mon top 3.
numéro 1. Défaut de spécifier une liste de champs. (Edit: pour éviter la confusion: c'est un code de production de la règle. Il ne s'applique pas aux scripts d'analyse ponctuels-sauf si je suis l'auteur.)
SELECT *
Insert Into blah SELECT *
devrait être
SELECT fieldlist
Insert Into blah (fieldlist) SELECT fieldlist
numéro 2. En utilisant un curseur et une boucle while, quand une boucle while avec une variable loop fera l'affaire.
DECLARE @LoopVar int
SET @LoopVar = (SELECT MIN(TheKey) FROM TheTable)
WHILE @LoopVar is not null
BEGIN
-- Do Stuff with current value of @LoopVar
...
--Ok, done, now get the next value
SET @LoopVar = (SELECT MIN(TheKey) FROM TheTable
WHERE @LoopVar < TheKey)
END
numéro 3. DateLogic à travers la chaîne de caractères type.
--Trim the time
Convert(Convert(theDate, varchar(10), 121), datetime)
devrait être
--Trim the time
DateAdd(dd, DateDiff(dd, 0, theDate), 0)
j'ai vu un pic récent de " une question vaut mieux que deux, n'est-ce pas?"
SELECT *
FROM blah
WHERE (blah.Name = @name OR @name is null)
AND (blah.Purpose = @Purpose OR @Purpose is null)
Cette requête nécessite deux ou trois différents plans d'exécution en fonction des valeurs des paramètres. Un seul plan d'exécution est généré et collé dans le cache pour ce texte sql. Ce plan sera utilisée quelle que soit la valeur des paramètres. Ce entraîne une mauvaise performance intermittente. Il est beaucoup mieux d'écrire deux requêtes (une requête prévu par le plan d'exécution).
-
champs de mot de passe lisibles par L'homme , egad. Auto-explicatif.
-
utilisant comme contre indexé colonnes, et je suis presque tenté de juste dire, COMME en général.
-
Recyclage SQL généré valeurs de clé primaire.
-
Surprise personne n'a mentionné l' Dieu-table encore. Rien ne dit "organiques" comme 100 colonnes de bit drapeaux, grandes chaînes et entiers.
-
puis il y a le" je manque .ini fichiers " pattern: stockage CSVs, pipe chaînes délimitées ou autres parties les données requises dans les grands champs de texte.
-
et pour MS SQL server l'utilisation de curseurs du tout . Il y a un mieux façon de faire n'importe quelle tâche de curseur donnée.
édité parce qu'il y en a tellement!
N'ont pas à creuser profondément pour elle: ne pas utiliser des déclarations préparées.
utilisant des alias de table sans signification:
from employee t1,
department t2,
job t3,
...
rend la lecture D'un grand énoncé SQL tellement plus difficile qu'elle n'a besoin d'être
var query = "select COUNT(*) from Users where UserName = '"
+ tbUser.Text
+ "' and Password = '"
+ tbPassword.Text +"'";
- Blindly trusted user input
- ne Pas utiliser les requêtes paramétrées
- texte en Clair des mots de passe
mes buggbears sont les 450 tableaux d'accès colonne qui ont été mis en place par le fils de 8 ans des meilleurs amis du Directeur Général Toiletteur Chien et la table de recherche douteuse qui n'existe que parce que quelqu'un ne sait pas comment normaliser une infrastructure de données correctement.
typiquement, cette table de recherche ressemble à ceci:
ID INT, Name NVARCHAR(132), IntValue1 INT, IntValue2 INT, CharValue1 NVARCHAR(255), CharValue2 NVARCHAR(255), Date1 DATETIME, Date2 DATETIME
j'ai perdu le compte du nombre de clients que j'ai vu qui ont des systèmes qui comptent sur des abominations comme celle-ci.
ceux que je déteste le plus sont
-
utilisation d'espaces lors de la création de tables, sprocs, etc. Je suis d'accord avec CamelCase ou under_scores et singulier ou plurals et Majuscule ou minuscule mais devoir se référer à une table ou colonne [avec des espaces], surtout si [ il est curieusement espacé] (Oui, j'ai couru dans cela) m'irrite vraiment.
-
données dénormalisées. Une table ne doit pas être parfaite. normalisé, mais quand je rencontre un tableau d'employés qui ont de l'information sur leur cote d'évaluation actuelle ou leur cote principale, cela me dit que je vais probablement avoir besoin de faire un tableau séparé à un moment donné et puis essayer de les garder synchronisés. Je vais normaliser les données d'abord et ensuite si je vois un endroit où la dénormalisation aide, je vais l'envisager.
-
surutilisation de vues ou de curseurs. Les vues ont un but, mais quand chaque table est enveloppée dans un avis c'est trop. J'ai dû utiliser des curseurs plusieurs fois, mais en général, vous pouvez utiliser d'autres mécanismes pour cela.
-
accès. Un programme peut-il être anti-pattern? Nous avons SQL Server à mon travail, mais un certain nombre de personnes utilisent l'accès en raison de sa disponibilité, "facilité d'utilisation" et "convivialité" pour les utilisateurs non techniques. Il y a trop de choses ici, mais si vous avez été dans un environnement similaire, vous savez.
utiliser SP comme préfixe du nom de la procédure de stockage parce qu'il va d'abord chercher dans l'emplacement des procédures du système plutôt que ceux personnalisés.
pour stocker les valeurs de temps, seul UTC timezone doit être utilisé. Heure locale ne doit pas être utilisé.
en utilisant @ @ IDENTITY au lieu de SCOPE_IDENTITY ()
Cité cette réponse :
- @@IDENTITY retourne la dernière valeur d'identité générée pour n'importe quelle table dans la session en cours, dans tous les domaines. Vous devez être prudent ici, car il est à travers les portées. Vous pouvez obtenir une valeur à partir d'un déclencheur, au lieu de votre déclaration actuelle.
- SCOPE_IDENTITY retourne la dernière valeur d'identité générée pour n'importe quelle table dans la session courante et le champ d'application courant. Généralement ce que vous souhaitez utiliser.
- IDENT_CURRENT retourne la dernière valeur d'identité générée pour une table spécifique dans n'importe quelle session et n'importe quelle portée. Cela vous permet de spécifier de quelle table vous voulez la valeur, dans le cas où les deux ci-dessus ne sont pas tout à fait ce dont vous avez besoin (très rare). Vous pouvez utiliser ceci si vous voulez obtenir la valeur D'identité courante pour une table dans laquelle vous n'avez pas inséré d'enregistrement.
réutiliser un champ "mort" pour quelque chose auquel il n'était pas destiné (par exemple stocker des données utilisateur dans un champ "Fax") - très tentant comme solution rapide!
select some_column, ...
from some_table
group by some_column
et en supposant que le résultat sera trié par some_column. J'ai vu cela un peu avec Sybase où l'hypothèse tient (pour le moment).
SELECT FirstName + ' ' + LastName as "Full Name", case UserRole when 2 then "Admin" when 1 then "Moderator" else "User" end as "User's Role", case SignedIn when 0 then "Logged in" else "Logged out" end as "User signed in?", Convert(varchar(100), LastSignOn, 101) as "Last Sign On", DateDiff('d', LastSignOn, getDate()) as "Days since last sign on", AddrLine1 + ' ' + AddrLine2 + ' ' + AddrLine3 + ' ' + City + ', ' + State + ' ' + Zip as "Address", 'XXX-XX-' + Substring(Convert(varchar(9), SSN), 6, 4) as "Social Security #" FROM Users
Ou, s'entasser tout en une seule ligne.
-
la syntaxe
FROM TableA, TableB WHERE
pour jointures plutôt queFROM TableA INNER JOIN TableB ON
-
faire des hypothèses qu'une requête sera retournée triée d'une certaine manière sans mettre une commande par clause dans, juste parce que c'était la façon dont il est apparu lors des tests dans l'outil de requête.
je dois mettre mon propre favori actuel ici, juste pour faire la liste complète. Mon antipattern préféré est ne pas tester vos requêtes .
ceci s'applique lorsque:
- votre requête implique plus d'une table.
- vous pensez que vous avez une conception optimale pour une requête, mais ne prenez pas la peine de tester vos hypothèses.
- vous acceptez la première requête qui fonctionne, sans aucune idée quant à savoir s'il est encore proche de optimisés.
et tous les tests effectués contre des données atypiques ou insuffisantes ne comptent pas. Si c'est une procédure stockée, mettez le test énoncé dans un commentaire et enregistrer les résultats. Sinon, le mettre dans un commentaire dans le code avec les résultats.
apprendre SQL dans les six premiers mois de leur carrière et ne jamais apprendre quoi que ce soit d'autre pour les 10 prochaines années. En particulier ne pas apprendre ou utiliser efficacement fenêtrage / fonctions D'analyse SQL. En particulier l'utilisation de over () et partition by.
les fonctions de fenêtre, comme l'agrégat fonctions, effectuer une agrégation sur un ensemble défini (un groupe) de lignes, mais plutôt que de retourner une valeur par groupe, les fonctions de fenêtre peuvent retourner plusieurs valeurs pour chaque groupe.
Voir O'Reilly SQL livre de cuisine de l'Annexe A pour une belle vue d'ensemble de fonctions de fenêtrage.
vue opposée: sur-obsession de la normalisation.
la plupart des systèmes SQL/RBDBs offrent un grand nombre de fonctionnalités (transactions, réplication) qui sont très utiles, même avec des données non normalisées. L'espace disque est bon marché, et parfois il peut être plus simple (code plus facile, Temps de développement plus rapide) de manipuler / filtrer / rechercher des données récupérées, que de rédiger un schéma 1NF, et de gérer tous les tracas qui s'y trouvent (jointures complexes, sous-selects méchants, etc.).
j'ai constaté que les systèmes sur-normalisés sont souvent une optimisation prématurée, en particulier au cours des premières étapes de développement.
(plus de pensées sur elle... http://writeonly.wordpress.com/2008/12/05/simple-object-db-using-json-and-python-sqlite / )
abus de Table temporaire.
spécifiquement ce genre de chose:
SELECT personid, firstname, lastname, age
INTO #tmpPeople
FROM People
WHERE lastname like 's%'
DELETE FROM #tmpPeople
WHERE firstname = 'John'
DELETE FROM #tmpPeople
WHERE firstname = 'Jon'
DELETE FROM #tmpPeople
WHERE age > 35
UPDATE People
SET firstname = 'Fred'
WHERE personid IN (SELECT personid from #tmpPeople)
ne construisez pas une table temporaire à partir d'une requête, seulement pour supprimer les lignes dont vous n'avez pas besoin.
et oui, j'ai vu des pages de code sous cette forme dans la production DBs.
je viens de mettre celui-ci ensemble, basé sur certaines des réponses SQL ici sur SO.
c'est un antimodèle sérieux de penser que les déclencheurs sont aux bases de données comme les gestionnaires d'événements sont à OOP. Il y a cette perception que n'importe quelle vieille logique peut être mise dans des déclencheurs, pour être mise à feu quand une transaction (événement) se produit sur une table.
faux. Une des grandes différences est que les déclencheurs sont synchrones - avec une vengeance, parce qu'ils sont synchrone sur une opération fixe, pas sur une opération de rang. Du côté de L'OOP, exactement le contraire - les événements sont un moyen efficace pour mettre en œuvre des transactions asynchrones.
1) Je ne sais pas si c'est un anti-modèle" officiel", mais je n'aime pas et j'essaie d'éviter les caractères littéraux comme des valeurs magiques dans une colonne de base de données.
un exemple de la table 'image' de MediaWiki:
img_media_type ENUM("UNKNOWN", "BITMAP", "DRAWING", "AUDIO", "VIDEO",
"MULTIMEDIA", "OFFICE", "TEXT", "EXECUTABLE", "ARCHIVE") default NULL,
img_major_mime ENUM("unknown", "application", "audio", "image", "text",
"video", "message", "model", "multipart") NOT NULL default "unknown",
(je viens de remarquer boîtier, une autre chose à éviter)
je conçois des cas tels que des recherches int dans des tables ImageMediaType et ImageMajorMime avec des clés int primaires.
2) Date / chaîne conversion qui repose sur des paramètres NLS spécifiques
CONVERT(NVARCHAR, GETDATE())
sans Identificateur de format
-
l'Altération de La Vue, point de vue qui est modifié, trop souvent, et sans préavis ni raison. Le changement sera soit remarqué au moment le plus inapproprié ou pire être faux et jamais remarqué. Peut-être que votre candidature sera cassée parce que quelqu'un a pensé à un meilleur nom pour cette colonne. En règle générale, les vues devraient étendre l'utilité des tables de base tout en maintenant un contrat avec les consommateurs. Corriger les problèmes, mais ne pas ajouter de fonctionnalités ou pire modifier le comportement, pour cela créer une nouvelle vue. Pour atténuer ne pas partager les vues avec d'autres projets et, utiliser CTEs lorsque les plates-formes le permettent. Si votre boutique a un DBA, vous ne pouvez probablement pas changer de point de vue, mais tous vos points de vue seront dépassés et / ou inutiles dans ce cas.
-
The !Paramed - une requête peut-elle avoir plus d'un but? Probablement, mais la prochaine personne qui le Lira ne le saura pas avant une méditation profonde. Même si tu n'en as pas besoin maintenant, il y a des chances que will, même si c'est juste pour déboguer. L'ajout de paramètres réduit le temps de maintenance et maintient les choses au sec. Si vous avez une clause où vous devriez avoir des paramètres.
-
Le cas pour aucun CAS
SELECT CASE @problem WHEN 'Need to replace column A with this medium to large collection of strings hanging out in my code.' THEN 'Create a table for lookup and add to your from clause.' WHEN 'Scrubbing values in the result set based on some business rules.' THEN 'Fix the data in the database' WHEN 'Formating dates or numbers.' THEN 'Apply formating in the presentation layer.' WHEN 'Createing a cross tab' THEN 'Good, but in reporting you should probably be using cross tab, matrix or pivot templates' ELSE 'You probably found another case for no CASE but now I have to edit my code instead of enriching the data...' END
Mettre des trucs dans des tables temporaires, en particulier les personnes qui passent à partir de SQL Server vers Oracle ont l'habitude d'abuser des tables temporaires. Il suffit d'utiliser des instructions de sélection imbriquées.
les deux que je trouve le plus, et peut avoir un coût important en termes de performance sont:
-
utilisant des curseurs au lieu d'un ensemble basé expression. Je suppose que celui-ci se produit souvent quand le programmeur pense procédurément.
-
utilisant des sous-requêtes corrélées, lorsqu'un joindre à une table dérivée peut faire le emploi.
développeurs qui écrivent des requêtes sans avoir une bonne idée de ce qui rend les applications SQL (requêtes individuelles et systèmes multi-utilisateurs) rapides ou lentes. Cela inclut l'ignorance au sujet de:
- stratégies de minimisation des e/s physiques, étant donné que le goulot d'étranglement de la plupart des requêtes n'est pas CPU
- perf impact de différents types de stockage physique de l'accès (par exemple, beaucoup d'e/S séquentielles sera plus rapide que beaucoup de petites aléatoire I/O, bien que moins si votre stockage physique est un SSD!)
- comment la main-paramétrer une requête si le SGBD produit un mauvais plan de requête
- comment diagnostiquer une mauvaise performance de la base de données, comment "déboguer" une requête lente, et comment lire un plan d'interrogation (ou expliquer, selon votre SGBD de choix)
- stratégies de verrouillage pour optimiser le débit et éviter les blocages dans les applications multi-utilisateurs
- importance des coups et autres astuces pour gérer le traitement des ensembles de données
- conception de table et d'index pour mieux équilibrer l'espace et la performance (par exemple, couvrir les index, garder les index petits dans la mesure du possible, réduire les types de données à la taille minimale nécessaire, etc.)
utilisant SQL comme paquet ISAM (Indexed Sequential Access Method). En particulier, les curseurs de nidification au lieu de combiner les énoncés SQL en un seul énoncé, bien que plus grand. Ce compte également comme un "abus de l'optimiseur", car en fait il n'ya pas beaucoup que l'optimiseur peut faire. Cela peut être combiné avec des déclarations non préparées pour un maximum d'inefficacité:
DECLARE c1 CURSOR FOR SELECT Col1, Col2, Col3 FROM Table1
FOREACH c1 INTO a.col1, a.col2, a.col3
DECLARE c2 CURSOR FOR
SELECT Item1, Item2, Item3
FROM Table2
WHERE Table2.Item1 = a.col2
FOREACH c2 INTO b.item1, b.item2, b.item3
...process data from records a and b...
END FOREACH
END FOREACH
la solution correcte (presque toujours) est de combiner les deux énoncés SELECT en l'un:
DECLARE c1 CURSOR FOR
SELECT Col1, Col2, Col3, Item1, Item2, Item3
FROM Table1, Table2
WHERE Table2.Item1 = Table1.Col2
-- ORDER BY Table1.Col1, Table2.Item1
FOREACH c1 INTO a.col1, a.col2, a.col3, b.item1, b.item2, b.item3
...process data from records a and b...
END FOREACH
le seul avantage de la version à double boucle est que vous pouvez facilement repérer les écarts entre les valeurs dans le Tableau1 parce que la boucle interne se termine. Cela peut être un facteur de contrôle d'interruption des rapports.
en outre, le tri dans la demande est généralement un non-non.
utilisant des clés primaires comme substituts pour les adresses d'enregistrement et utilisant des clés étrangères comme substituts pour les pointeurs intégrés dans les enregistrements.