Supposons que j'ai une table de clients et une table d'achats. Chaque achat appartient à un client. Je souhaite obtenir une liste de tous les clients ainsi que leur dernier achat dans une seule instruction SELECT. Quelle est la meilleure pratique? Des conseils sur la construction d'index?
Veuillez utiliser les noms de table / colonne dans votre réponse:
- client: id, nom
- achat: id, customer_id, item_id, date
Et dans des situations plus compliquées, serait-il (en termes de performances) bénéfique de dénormaliser la base de données en plaçant le dernier achat dans la table client?
S'il est garanti que l'ID (d'achat) est trié par date, les déclarations peuvent-elles être simplifiées en utilisant quelque chose comme LIMIT 1
?
Réponses:
Ceci est un exemple du
greatest-n-per-group
problème qui est apparu régulièrement sur StackOverflow.Voici comment je recommande généralement de le résoudre:
Explication: étant donné une ligne
p1
, il ne devrait pas y avoir de lignep2
avec le même client et une date ultérieure (ou en cas d'égalité, une date ultérieureid
). Lorsque nous constatons que cela est vrai, ilp1
s'agit de l'achat le plus récent pour ce client.En ce qui concerne les indices, je crée un indice composé
purchase
sur les colonnes (customer_id
,date
,id
). Cela peut permettre à la jointure externe d'être effectuée à l'aide d'un index de recouvrement. Assurez-vous de tester sur votre plate-forme, car l'optimisation dépend de l'implémentation. Utilisez les fonctionnalités de votre SGBDR pour analyser le plan d'optimisation. Par exempleEXPLAIN
sur MySQL.Certaines personnes utilisent des sous-requêtes au lieu de la solution que je montre ci-dessus, mais je trouve que ma solution facilite la résolution des liens.
la source
Vous pouvez également essayer de le faire en utilisant une sous-sélection
La sélection doit rejoindre tous les clients et leur dernière date d'achat.
la source
INNER JOIN
par aLEFT OUTER JOIN
.purchase
table sont la date et le client_id, mais la requête demande tous les champs de la table.Vous n'avez pas spécifié la base de données. Si elle permet des fonctions analytiques, il peut être plus rapide d'utiliser cette approche que celle de GROUP BY (certainement plus rapide dans Oracle, probablement plus rapide dans les dernières éditions de SQL Server, je ne sais pas pour les autres).
La syntaxe dans SQL Server serait:
la source
Une autre approche consiste à utiliser une
NOT EXISTS
condition dans votre condition de jointure pour tester les achats ultérieurs:la source
AND NOT EXISTS
partie en termes simples?J'ai trouvé ce fil comme solution à mon problème.
Mais quand je les ai essayés, les performances étaient faibles. Ci-dessous est ma suggestion pour de meilleures performances.
J'espère que cela vous sera utile.
la source
top 1
etordered it by
MaxDatedesc
Si vous utilisez PostgreSQL, vous pouvez utiliser
DISTINCT ON
pour trouver la première ligne d'un groupe.Documents PostgreSQL - Distinct On
Notez que le ou les
DISTINCT ON
champs - icicustomer_id
- doivent correspondre au (x) champ (s) le plus à gauche de laORDER BY
clause.Mise en garde: Il s'agit d'une clause non standard.
la source
Essayez ceci, cela vous aidera.
Je l'ai utilisé dans mon projet.
la source
Testé sur SQLite:
La
max()
fonction d'agrégation s'assurera que le dernier achat est sélectionné dans chaque groupe (mais suppose que la colonne de date est dans un format où max () donne le dernier - ce qui est normalement le cas). Si vous souhaitez gérer les achats avec la même date, vous pouvez utilisermax(p.date, p.id)
.En termes d'index, j'utiliserais un index lors de l'achat avec (customer_id, date, [toute autre colonne d'achat que vous souhaitez retourner dans votre sélection]).
Le
LEFT OUTER JOIN
(par opposition àINNER JOIN
) s'assurera que les clients qui n'ont jamais effectué d'achat sont également inclus.la source
Veuillez essayer ceci,
la source