Quelle est la raison de ne pas utiliser select *?

136

J'ai vu un certain nombre de personnes affirmer que vous devriez nommer spécifiquement chaque colonne souhaitée dans votre requête de sélection.

En supposant que je vais utiliser toutes les colonnes de toute façon, pourquoi ne les utiliserais-je pas SELECT *?

Même en considérant la question * Requête SQL - Sélectionnez * à partir de la vue ou Sélectionnez col1, col2,… colN à partir de la vue *, je ne pense pas que ce soit un doublon exact car j'aborde le problème dans une perspective légèrement différente.

L'un de nos principes est de ne pas optimiser avant l'heure. Dans cet esprit, il semble que l'utilisation SELECT *devrait être la méthode préférée jusqu'à ce qu'il soit prouvé qu'il s'agit d'un problème de ressources ou que le schéma soit à peu près gravé dans la pierre. Ce qui, comme nous le savons, ne se produira que lorsque le développement sera complètement terminé.

Cela dit, y a-t-il un problème primordial à ne pas utiliser SELECT *?

Pas moi
la source

Réponses:

168

L'essence de la citation de ne pas optimiser prématurément est d'opter pour un code simple et direct, puis d' utiliser un profileur pour signaler les points chauds, que vous pouvez ensuite optimiser pour être efficace.

Lorsque vous utilisez select *, il est impossible de profiler, par conséquent, vous n'écrivez pas de code clair et direct et vous allez à l'encontre de l'esprit de la citation. select *est un anti-pattern.


La sélection des colonnes n'est donc pas une optimisation prématurée. Quelques petites choses qui me viennent à l'esprit ...

  1. Si vous spécifiez des colonnes dans une instruction SQL, le moteur d'exécution SQL génère une erreur si cette colonne est supprimée de la table et que la requête est exécutée.
  2. Vous pouvez scanner plus facilement le code là où cette colonne est utilisée.
  3. Vous devez toujours écrire des requêtes pour ramener le moins d'informations possible.
  4. Comme d'autres le mentionnent, si vous utilisez l'accès à la colonne ordinale, vous ne devez jamais utiliser select *
  5. Si votre instruction SQL joint des tables, sélectionnez * vous donne toutes les colonnes de toutes les tables de la jointure

Le corollaire est que l'utilisation de select *...

  1. Les colonnes utilisées par l'application sont opaques
  2. Les administrateurs de bases de données et leurs profileurs de requêtes ne sont pas en mesure d'aider les mauvaises performances de votre application
  3. Le code est plus fragile lorsque des changements se produisent
  4. Votre base de données et votre réseau souffrent car ils rapportent trop de données (E / S)
  5. Les optimisations du moteur de base de données sont minimes car vous ramenez toutes les données indépendamment (logique).

Ecrire du SQL correct est aussi simple que l'écriture Select *. Donc, le vrai paresseux écrit du SQL approprié parce qu'il ne veut pas revoir le code et essayer de se souvenir de ce qu'il faisait quand il l'a fait. Ils ne veulent pas expliquer aux DBA à propos de chaque bit de code. Ils ne veulent pas expliquer à leurs clients pourquoi l'application fonctionne comme un chien.

Robert Paulson
la source
2
Dans votre première section, le point n ° 5 doit se lire "select * vous donne toutes les colonnes de toutes les tables de la jointure". Dans votre deuxième section, les points 2 et 5 ne sont pas nécessairement vrais et ne devraient pas être mentionnés comme des raisons de ne pas utiliser «select *».
jimmyorr
1
@uglysmurf - merci pour la correction, mais en ce qui concerne 2 et 5 - bien qu'elles ne soient pas nécessairement vraies pour toutes les bases de données / dba dans tous les cas, je pense qu'elles sont importantes et valables pour la majorité des cas et les laisseront. L'utilisation de 'select *' n'a jamais facilité le travail d'un dba.
Robert Paulson
11
Je dirais que le n ° 3 (code fragile) n'est pas vraiment vrai. Selon l'implémentation, Select * peut le rendre MOINS fragile, mais je ne vois pas comment il pourrait l'être davantage.
JohnFx
2
@JohnFx, je suppose que vous définissez cassant différemment. La fragilité est normalement définie comme «se casse facilement». Avoir des dépendances inconnues ou difficiles à trouver parce que chaque morceau de code utilisera des colonnes différentes signifie que je ne peux rien changer facilement au niveau des données sans régression complète .. ce qui semble fragile.
Robert Paulson
9
@mavnn, par rapport à la fragilité, je crains que ce soit un problème de sémantique sur mon choix du mot fragile. Mon dernier mot est de dire que cela fait peu de différence de toute façon. Le seul scénario est celui des colonnes renommées / supprimées. Vous déplacez simplement la pause entre le moment où le SQL est exécuté (explicite) et la rupture lorsque les résultats sont consommés. La manière dont le résultat de la requête est consommé peut varier, et le code peut échouer ou non en silence, mais le moteur d'exécution SQL échouera définitivement avec SQL non valide. Alors, est-ce que select * vous a aidé? Un échec explicite IMO plus proche de la base de données pour un problème de base de données est préférable. Thx
Robert Paulson
42

Si votre code dépend du fait que les colonnes sont dans un ordre spécifique, votre code sera interrompu lors de modifications de la table. De plus, vous en récupérez peut-être trop dans la table lorsque vous sélectionnez *, surtout s'il y a un champ binaire dans la table.

Ce n'est pas parce que vous utilisez toutes les colonnes maintenant que quelqu'un d'autre ne va pas ajouter une colonne supplémentaire à la table.

Il ajoute également une surcharge à la mise en cache d'exécution du plan car il doit récupérer les métadonnées sur la table pour savoir quelles colonnes se trouvent dans *.

Bob
la source
4
Bonne réponse, mais je changerais le "code va casser" en "code PEUT casser." C'est le vrai problème ici, l'utilisation de "select *" ne produit pas TOUJOURS un changement de rupture. Et lorsque la rupture se produit, elle est généralement fortement découplée de l'utilisation qui finit par être interrompue.
BQ.
4
Si quelqu'un fait référence à des colonnes ordinairement dans son code, il est en difficulté, qu'il utilise SELECT * ou non. La surcharge d'exécution du plan est insignifiante et n'aurait pas d'importance de toute façon une fois le plan mis en cache.
MusiGenesis
1
Ensuite, l'erreur du programmeur réside dans l'écriture de code qui dépend de la séquence des colonnes. Vous n'avez jamais besoin de faire ça.
dkretz
1
@doofledorfer - ne dites jamais jamais. Il est plus rapide d'accéder aux colonnes ordinales, et c'est parfois pratique. C'est une plus grande erreur d'utiliser select * que d'utiliser un accès ordinal.
Robert Paulson
23

L'une des raisons principales est que si jamais vous ajoutez / supprimez des colonnes de votre table, toute requête / procédure qui effectue un appel SELECT * recevra désormais plus ou moins de colonnes de données que prévu.

Ahockley
la source
3
Vous ne devez jamais écrire de code qui dépend du nombre de colonnes renvoyées de toute façon.
dkretz
4
Mais tout le monde écrit du code qui exige que les programmeurs sachent quelles données reviennent. Vous ne pouvez pas Ctrl + F le nom de votre colonne s'il est caché dans un SELECT *.
Lotus Notes
17
  1. D'une manière détournée, vous enfreignez la règle de modularité concernant l'utilisation d'un typage strict dans la mesure du possible. Explicite est presque universellement meilleur.

  2. Même si vous avez maintenant besoin de chaque colonne de la table, d'autres pourraient être ajoutées plus tard, ce qui sera réduit à chaque fois que vous exécutez la requête et pourrait nuire aux performances. Cela nuit aux performances car

    • Vous tirez plus de données sur le fil; et
    • Parce que vous pourriez annuler la capacité de l'optimiseur à extraire les données directement de l'index (pour les requêtes sur des colonnes qui font toutes partie d'un index.) Plutôt que d'effectuer une recherche dans la table elle-même

Quand utiliser sélectionnez *

Lorsque vous avez explicitement BESOIN de chaque colonne de la table, au lieu d'avoir besoin de chaque colonne de la table QUI EXISTE AU MOMENT QUE VOUS ÉCRIVEZ LA DEMANDE. Par exemple, si vous écriviez une application de gestion de base de données qui devait afficher l'intégralité du contenu de la table (quel qu'il soit), vous pouvez utiliser cette approche.

JohnFx
la source
1
Un autre moment à utiliser SELECT *serait lorsque vous effectuez des requêtes de test à l'aide du client db.
cdmckay
Cela semble être une exception étrange étant donné le contexte de la question. En dehors de la sauvegarde de la saisie, quel est l'avantage de faire cela pour les requêtes de test?
JohnFx
SELECT * FROM (SELECT a, b, c FROM table) est également OK.
kmkaplan
12

Il y a quelques raisons:

  1. Si le nombre de colonnes d'une base de données change et que votre application s'attend à ce qu'il y en ait un certain nombre ...
  2. Si l'ordre des colonnes dans une base de données change et que votre application s'attend à ce qu'elles soient dans un certain ordre ...
  3. Surcharge de la mémoire. 8 colonnes INTEGER inutiles ajouteraient 32 octets de mémoire gaspillée. Cela ne semble pas beaucoup, mais c'est pour chaque requête et INTEGER est l'un des petits types de colonnes ... les colonnes supplémentaires sont plus susceptibles d'être des colonnes VARCHAR ou TEXT, ce qui s'additionne plus rapidement.
  4. Frais généraux du réseau. Lié à la surcharge de mémoire: si j'émets 30 000 requêtes et que j'ai 8 colonnes INTEGER inutiles, j'ai gaspillé 960 Ko de bande passante. Les colonnes VARCHAR et TEXT sont probablement beaucoup plus grandes.

Remarque: j'ai choisi INTEGER dans l'exemple ci-dessus car ils ont une taille fixe de 4 octets.

Powerlord
la source
1 et 2 seraient une odeur de code et 3 et 4 sonneraient comme une optimisation prématurée
NikkyD
7

Si votre application obtient des données avec SELECT * et que la structure de la table dans la base de données est modifiée (par exemple, une colonne est supprimée), votre application échouera à chaque endroit où vous référencez le champ manquant. Si vous incluez à la place toutes les colonnes dans votre requête, votre application se cassera dans le (espérons-le) un endroit où vous obtenez initialement les données, ce qui facilitera le correctif.

Cela étant dit, il existe un certain nombre de situations dans lesquelles SELECT * est souhaitable. L'une est une situation que je rencontre tout le temps, où je dois répliquer une table entière dans une autre base de données (comme SQL Server vers DB2, par exemple). Une autre est une application écrite pour afficher les tables de manière générique (c'est-à-dire sans aucune connaissance d'une table particulière).

MusiGenesis
la source
La question n'est pas «est sélectionné * toujours souhaitable», donc la deuxième partie de votre réponse n'est pas pertinente. La question stipule que l'utilisation de 'select *' devrait être préférable, ce qui est bien sûr complet.
Robert Paulson
Oui, ma 2ème partie n'est pas pertinente. OQ a changé la question en indiquant que SELECT * est préférable, et oui, c'est un peu bizarre.
MusiGenesis
Ah ouais désolé - la question a changé sa direction après votre réponse.
Robert Paulson
C'est bien. Même Mozart était un éditeur ( stackoverflow.com/questions/292682/… ). Mon article original suggérait que l'utilisation de SELECT * menait au cannibalisme. :)
MusiGenesis
3

En fait, j'ai remarqué un comportement étrange lorsque j'utilisais des select *vues dans SQL Server 2005.

Exécutez la requête suivante et vous verrez ce que je veux dire.

IF  EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[starTest]') AND type in (N'U'))
DROP TABLE [dbo].[starTest]
CREATE TABLE [dbo].[starTest](
    [id] [int] IDENTITY(1,1) NOT NULL,
    [A] [varchar](50) NULL,
    [B] [varchar](50) NULL,
    [C] [varchar](50) NULL
) ON [PRIMARY]

GO

insert into dbo.starTest
select 'a1','b1','c1'
union all select 'a2','b2','c2'
union all select 'a3','b3','c3'

go
IF  EXISTS (SELECT * FROM sys.views WHERE object_id = OBJECT_ID(N'[dbo].[vStartest]'))
DROP VIEW [dbo].[vStartest]
go
create view dbo.vStartest as
select * from dbo.starTest
go

go
IF  EXISTS (SELECT * FROM sys.views WHERE object_id = OBJECT_ID(N'[dbo].[vExplicittest]'))
DROP VIEW [dbo].[vExplicittest]
go
create view dbo.[vExplicittest] as
select a,b,c from dbo.starTest
go


select a,b,c from dbo.vStartest
select a,b,c from dbo.vExplicitTest

IF  EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[starTest]') AND type in (N'U'))
DROP TABLE [dbo].[starTest]
CREATE TABLE [dbo].[starTest](
    [id] [int] IDENTITY(1,1) NOT NULL,
    [A] [varchar](50) NULL,
    [B] [varchar](50) NULL,
    [D] [varchar](50) NULL,
    [C] [varchar](50) NULL
) ON [PRIMARY]

GO

insert into dbo.starTest
select 'a1','b1','d1','c1'
union all select 'a2','b2','d2','c2'
union all select 'a3','b3','d3','c3'

select a,b,c from dbo.vStartest
select a,b,c from dbo.vExplicittest

Comparez les résultats des 2 dernières instructions de sélection. Je crois que ce que vous verrez est le résultat de Select * référençant les colonnes par index au lieu de nom.

Si vous reconstruisez la vue, cela fonctionnera à nouveau correctement.

ÉDITER

J'ai ajouté une question distincte, * «sélectionner * à partir de la table» vs «sélectionner colA, colB, etc. à partir de la table» comportement intéressant dans SQL Server 2005 * pour examiner ce comportement plus en détail.

Kristof
la source
2

Vous pouvez joindre deux tables et utiliser la colonne A de la deuxième table. Si vous ajoutez plus tard la colonne A à la première table (avec le même nom mais peut-être une signification différente), vous obtiendrez très probablement les valeurs de la première table et non de la seconde comme précédemment. Cela ne se produira pas si vous spécifiez explicitement les colonnes que vous souhaitez sélectionner.

Bien sûr, la spécification des colonnes provoque également parfois des bogues si vous oubliez d'ajouter les nouvelles colonnes à chaque clause de sélection. Si la nouvelle colonne n'est pas nécessaire à chaque exécution de la requête, cela peut prendre un certain temps avant que le bogue ne soit remarqué.

Kaniu
la source
2

Je comprends où vous allez en ce qui concerne l'optimisation prématurée, mais cela ne va vraiment que jusqu'à un certain point. L'intention est d'éviter une optimisation inutile au début. Vos tables sont-elles non indexées? Utiliseriez-vous nvarchar (4000) pour stocker un code postal?

Comme d'autres l'ont souligné, il existe d'autres points positifs à la spécification de chaque colonne que vous prévoyez d'utiliser dans la requête (comme la maintenabilité).

Jim BG
la source
2

Lorsque vous spécifiez des colonnes, vous vous attachez également à un ensemble spécifique de colonnes et vous vous rendez moins flexible, ce qui fait que Feuerstein se déplace, eh bien, où qu'il se trouve. Juste une pensée.

poisson-globe
la source
1
Je n'ai absolument aucune idée de qui est Feuerstein. J'ai essayé de googler et j'ai trouvé un psychologue, un personnage de télévision et un blogueur, donc le mieux que j'ai pu trouver était une blague.
NotMe
Auteur des livres O'Reilly sur PL / SQL. Essayez de googler "feuerstein sql" au lieu de simplement "feuerstein".
orbfish
2

SELECT * n'est pas toujours mauvais. À mon avis, du moins. Je l'utilise assez souvent pour des requêtes dynamiques renvoyant une table entière, ainsi que quelques champs calculés.

Par exemple, je veux calculer des géométries géographiques à partir d'une table "normale", c'est-à-dire une table sans aucun champ de géométrie, mais avec des champs contenant des coordonnées. J'utilise postgresql, et son extension spatiale postgis. Mais le principe s'applique dans de nombreux autres cas.

Un exemple:

  • une table de lieux, avec des coordonnées stockées dans des champs étiquetés x, y, z:

    CREATE TABLE places (place_id integer, x numeric (10, 3), y numeric (10, 3), z numeric (10, 3), description varchar);

  • nourrissons-le avec quelques exemples de valeurs:

    INSERT INTO places (place_id, x, y, z, description) VALUES
    (1, 2.295, 48.863, 64, 'Paris, Place de l \' Étoile '),
    (2, 2.945, 48.858, 40,' Paris, Tour Eiffel '),
    (3, 0,373, 43,958, 90, «Condom, Cathédrale St-Pierre»);

  • Je veux pouvoir mapper le contenu de ce tableau, en utilisant un client SIG. La méthode normale consiste à ajouter un champ de géométrie à la table et à créer la géométrie en fonction des coordonnées. Mais je préférerais avoir une requête dynamique: de cette façon, lorsque je change de coordonnées (corrections, plus de précision, etc.), les objets mappés se déplacent réellement, dynamiquement. Voici donc la requête avec le SELECT * :

    CRÉER OU REMPLACER VUE lieux_points AS
    SELECT *,
    GeomFromewkt ('SRID = 4326; POINT (' || x || '' || y || '' || z || ')')
    FROM lieux;

    Reportez-vous à postgis, pour l'utilisation de la fonction GeomFromewkt ().

  • Voici le résultat:

    SELECT * FROM lieux_points;

place_id | x | y | z | description | geomfromewkt                            
---------- + ------- + -------- + -------- + ------------- ----------------- + -------------------------------- ------------------------------------  
        1 | 2,295 | 48,863 | 64.000 | Paris, place de l'Étoile | 01010000A0E61000005C8FC2F5285C02405839B4C8766E48400000000000005040  
        2 | 2,945 | 48,858 | 40.000 | Paris, Tour Eiffel | 01010000A0E61000008FC2F5285C8F0740E7FBA9F1D26D48400000000000004440
        3 | 0,373 | 43,958 | 90.000 | Condom, Cathédrale St-Pierre | 01010000A0E6100000AC1C5A643BDFD73FB4C876BE9FFA45400000000000805640
(3 lignes)

La colonne la plus à droite peut désormais être utilisée par n'importe quel programme SIG pour cartographier correctement les points.

  • Si, dans le futur, certains champs sont ajoutés à la table: pas de soucis, je dois juste relancer la même définition VIEW.

Je souhaite que la définition de la VUE puisse être conservée "telle quelle", avec le *, mais hélas ce n'est pas le cas: c'est ainsi qu'elle est stockée en interne par postgresql:

SELECT places.place_id, places.x, places.y, places.z, places.description, geomfromewkt ((((('SRID = 4326; POINT (' :: text || places.x) || '': : text) || places.y) || '' :: text) || places.z) || ')' :: text) AS geomfromewkt FROM places;

Pierre
la source
1

Même si vous utilisez chaque colonne mais adressez le tableau de lignes par index numérique, vous aurez des problèmes si vous ajoutez une autre ligne plus tard.

C'est donc essentiellement une question de maintenabilité! Si vous n'utilisez pas le sélecteur *, vous n'aurez pas à vous soucier de vos requêtes.

Markus
la source
1

En sélectionnant uniquement les colonnes dont vous avez besoin, le jeu de données en mémoire est plus petit et donc votre application plus rapide.

De plus, de nombreux outils (par exemple les procédures stockées) mettent également en cache les plans d'exécution des requêtes. Si vous ajoutez ou supprimez ultérieurement une colonne (particulièrement facile si vous désactivez une vue), l'outil fera souvent une erreur lorsqu'il ne récupère pas les résultats qu'il attend.

Soldarnal
la source
1

Cela rend votre code plus ambigu et plus difficile à maintenir; parce que vous ajoutez des données inutilisées supplémentaires au domaine, et que vous ne savez pas ce que vous avez voulu et lequel non. (Cela suggère également que vous pourriez ne pas savoir ou ne pas vous en soucier.)

dkretz
la source
1

Pour répondre directement à votre question: N'utilisez pas "SELECT *" lorsque cela rend votre code plus fragile aux modifications des tables sous-jacentes. Votre code ne doit être interrompu que lorsqu'une modification est apportée à la table qui affecte directement les exigences de votre programme.

Votre application doit tirer parti de la couche d'abstraction fournie par l'accès relationnel.

Métro
la source
1

Je n'utilise pas SELECT * simplement parce qu'il est agréable de voir et de savoir quels champs je récupère.

Lkessler
la source
1

Généralement mauvais d'utiliser 'select *' à l'intérieur des vues car vous serez obligé de recompiler la vue en cas de changement de colonne de table. En changeant les colonnes de table sous-jacentes d'une vue, vous obtiendrez une erreur pour les colonnes inexistantes jusqu'à ce que vous reveniez et recompiliez.

Christopher Klein
la source
1

Ce n'est pas grave quand vous le faites exists(select * ...)car il n'est jamais étendu. Sinon, ce n'est vraiment utile que lorsque vous explorez des tables avec des instructions de sélection temporaires ou si vous avez défini un CTE ci-dessus et que vous voulez chaque colonne sans les taper toutes à nouveau.

dotjoe
la source
1

Juste pour ajouter une chose que personne d'autre n'a mentionnée. Select *renvoie toutes les colonnes, quelqu'un peut ajouter une colonne plus tard que vous ne voulez pas nécessairement que les utilisateurs puissent voir, par exemple qui a mis à jour les données pour la dernière fois ou un horodatage ou des notes que seuls les gestionnaires ne devraient pas voir tous les utilisateurs, etc.

En outre, lors de l'ajout d'une colonne, l'impact sur le code existant doit être examiné et pris en compte pour voir si des modifications sont nécessaires en fonction des informations stockées dans la colonne. En utilisantselect * , cet examen sera souvent ignoré car le développeur supposera que rien ne cassera. Et en fait, rien ne peut apparaître explicitement, mais les requêtes peuvent maintenant commencer à renvoyer la mauvaise chose. Le simple fait que rien ne casse explicitement ne signifie pas que les requêtes n'auraient pas dû être modifiées.

HLGEM
la source
0

car "select *" gaspillera de la mémoire lorsque vous n'avez pas besoin de tous les champs. Mais pour le serveur SQL, leurs performances sont les mêmes.

FloatFish
la source