2010-12-07 7 views
4

En utilisant les tableaux ci-dessous comme exemple et la requête listée comme requête de base, je souhaite ajouter un moyen de sélectionner uniquement les lignes avec un identifiant maximum! Sans avoir à faire une seconde requête!MySQL sélectionnant des lignes avec un identifiant maximum et correspondant à d'autres conditions

TABLE VEHICLES 

id  vehicleName 
----- -------- 
1  cool car 
2  cool car 
3  cool bus 
4  cool bus 
5  cool bus 
6  car 
7  truck 
8  motorcycle 
9  scooter 
10  scooter 
11  bus 

TABLE VEHICLE NAMES 

nameId vehicleName 
------ ------- 
1  cool car 
2  cool bus 
3  car 
4  truck 
5  motorcycle 
6  scooter 
7  bus 

TABLE VEHICLE ATTRIBUTES 

nameId attribute 
------ --------- 
1  FAST 
1  SMALL 
1  SHINY 
2  BIG 
2  SLOW 
3  EXPENSIVE 
4  SHINY 
5  FAST 
5  SMALL 
6  SHINY 
6  SMALL 
7  SMALL 

Et la requête de base:

select a.* 
    from vehicle   a 
    join vehicle_names b using(vehicleName) 
    join vehicle_attribs c using(nameId) 
where c.attribute in('SMALL', 'SHINY') 
and a.vehicleName like '%coo%' 
group 
    by a.id 
having count(distinct c.attribute) = 2; 

donc ce que je veux atteindre est de sélectionner des lignes avec certains attributs, qui correspondent à un nom mais une seule entrée pour chaque nom qui correspond où l'identification est le plus haut!

Ainsi, une solution de travail dans cet exemple renverrait les lignes ci-dessous:

id  vehicleName 
----- -------- 
2  cool car 
10  scooter 

si elle utilisait une sorte de max sur l'ID

au moment où je reçois toutes les entrées pour voiture cool et scooter.

Ma base de données du monde réel suit une structure similaire et a des dizaines de milliers d'entrées, de sorte qu'une requête comme ci-dessus pourrait facilement renvoyer plus de 3000 résultats. Je limite les résultats à 100 lignes pour maintenir le temps d'exécution bas car les résultats sont utilisés dans une recherche sur mon site. La raison pour laquelle je répète des "véhicules" avec le même nom mais seulement un ID différent est que de nouveaux modèles sont constamment ajoutés mais je garde le plus vieux pour ceux qui veulent les déterrer! Mais sur une recherche par nom de voiture, je ne veux pas retourner les cartes les plus anciennes juste la plus récente qui est celle avec le plus haut ID!

La réponse correcte permettrait d'adapter la requête que j'ai fournie ci-dessus que je suis en train d'utiliser et de ne lui renvoyer que des lignes où le nom correspond mais a l'ID le plus élevé!

Si cela n'est pas possible, des suggestions sur la façon dont je peux réaliser ce que je veux sans augmenter massivement le temps d'exécution d'une recherche seraient appréciées!

+0

Avez-vous des index sur '' vehicle_attribs' sur (nameId, attribut) '? Et la recherche sur vehicle_names en utilisant 'LIKE '% cool%'' N'utilise PAS d'index – ajreal

+0

@ajreal il y a une PRIMARY KEY sur (nameId, attribut) dans le vehicle_attribs. – Tristan

Répondre

4

Si vous voulez garder votre logique, voici ce que je ferais:

select a.* 
from vehicle a 
    left join vehicle a2 on (a.vehicleName = a2.vehicleName and a.id < a2.id) 
    join vehicle_names b on (a.vehicleName = b.vehicleName) 
    join vehicle_attribs c using(nameId) 
where c.attribute in('SMALL', 'SHINY') 
    and a.vehicleName like '%coo%' 
    and a2.id is null 
group by a.id 
having count(distinct c.attribute) = 2;

Quel rendement:

+----+-------------+ 
| id | vehicleName | 
+----+-------------+ 
| 2 | cool car | 
| 10 | scooter  | 
+----+-------------+ 
2 rows in set (0.00 sec) 

Comme autre dit, pourrait être fait normalisation sur quelques niveaux:

En conservant votre table actuelle vehicle_names comme table de recherche principale, je voudrais modifier:

update vehicle a 
    inner join vehicle_names b using (vehicleName) 
set a.vehicleName = b.nameId; 
alter table vehicle change column vehicleName nameId int; 

create table attribs (
    attribId int auto_increment primary key, 
    attribute varchar(20), 
    unique key attribute (attribute) 
); 
insert into attribs (attribute) 
    select distinct attribute from vehicle_attribs; 
update vehicle_attribs a 
    inner join attribs b using (attribute) 
set a.attribute=b.attribId; 
alter table vehicle_attribs change column attribute attribId int; 

qui a conduit à la requête suivante:

select a.id, b.vehicleName 
from vehicle a 
    left join vehicle a2 on (a.nameId = a2.nameId and a.id < a2.id) 
    join vehicle_names b on (a.nameId = b.nameId) 
    join vehicle_attribs c on (a.nameId=c.nameId) 
    inner join attribs d using (attribId) 
where d.attribute in ('SMALL', 'SHINY') 
    and b.vehicleName like '%coo%' 
    and a2.id is null 
group by a.id 
having count(distinct d.attribute) = 2;
+0

Belle manière de classer quand vous avez besoin d'un seul enregistrement de chaque groupe, je n'y ai pas pensé. Je suggérerais une solution avec des variables, mais celle-ci est beaucoup mieux, puisque les résultats de la requête pourraient être mis en cache dans le cache de requête. – newtover

3

Le tableau ne semble normalisée, mais cela vous faciliter de le faire:

select max(id), vehicleName 
from VEHICLES 
group by vehicleName 
having count(*)>=2; 
+0

la table est juste un échantillon assez différent de la vraie chose! Le groupe est-il important au maximum? Mais ce que je suis vraiment après dans une réponse est ce que j'ajouterais à la requête que j'ai fournie ci-dessus pour sélectionner uniquement les lignes max! Merci – Tristan

1

Je ne suis pas sûr que je comprends parfaitement votre modèle, mais la requête suivante répond à vos exigences telles qu'elles sont. La première sous-requête trouve la dernière version du véhicule. La deuxième requête satisfait votre "et" condition. Ensuite, je viens de rejoindre les requêtes sur vehiclename (qui est la clé?).

select a.id 
     ,a.vehiclename 
    from (select a.vehicleName, max(id) as id 
      from vehicle a 
     where vehicleName like '%coo%' 
     group by vehicleName 
     ) as a 
    join (select b.vehiclename 
      from vehicle_names b 
      join vehicle_attribs c using(nameId) 
     where c.attribute in('SMALL', 'SHINY') 
     group by b.vehiclename 
     having count(distinct c.attribute) = 2 
     ) as b on (a.vehicleName = b.vehicleName); 

Si cette logique « dernier véhicule » est quelque chose que vous aurez besoin de faire beaucoup, une petite suggestion serait de créer une vue (voir ci-dessous) qui retourne la dernière version de chaque véhicule. Vous pouvez ensuite utiliser la vue à la place de la requête find-max. Notez que ceci est purement pour la facilité d'utilisation, il n'offre aucun avantage de performance.

select * 
    from vehicle a 
where id = (select max(b.id) 
       from vehicle b 
       where a.vehiclename = b.vehiclename); 
+0

VIEWS ne sont pas indexés, donc il ne peut pas accélérer les résultats de la requête avec la quantité d'enregistrements de l'OP. – Danosaure

+0

@Danosaure: Je vais modifier ma réponse pour clarifier que ma suggestion était destinée à améliorer la facilité d'utilisation. – Ronnis

0

Sans entrer dans la refonte correcte de vous le modèle que vous pourriez

1) Ajouter une colonne IsLatest que votre application peut gérer.

Ce n'est pas parfait, mais vous satisfaire la question (jusqu'au prochain problème, ne pas voir à la fin) Tout ce que vous avez besoin est lorsque vous ajoutez une nouvelle entrée pour émettre des requêtes telles que

UPDATE a 
SET IsLatest = 0 
WHERE IsLatest = 1 

INSERT new a 

UPDATE a 
SET IsLatest = 1 
WHERE nameId = @last_inserted_id 

dans une transaction ou un déclencheur

2) Sinon, vous trouverez la max_id avant de lancer votre requête

SELECT MAX(nameId) 
FROM a 
WHERE vehicleName = @name 

3) vous pouvez le faire en simple S QL et fourniture d 'index sur (vehicleName, nameId), il devrait effectivement avoir une vitesse décente avec

select a.* 
    from vehicle   a 
    join vehicle_names b ON a.vehicleName = b.vehicleName 
    join vehicle_attribs c ON b.nameId = c.nameId AND c.attribute = 'SMALL' 
    join vehicle_attribs d ON b.nameId = c.nameId AND d.attribute = 'SHINY' 
    join vehicle   notmax ON a.vehicleName = b.vehicleName AND a.nameid < notmax.nameid 
where a.vehicleName like '%coo%' 
     AND notmax.id IS NULL 

J'ai enlevé votre GROUP BY et remplacée par une autre jointure (en supposant que seul attribut unique par nameId est possible) . J'ai également utilisé l'une des méthodes pour trouver max par groupe, c'est-à-dire joindre une table sur elle-même et filtrer une ligne pour laquelle il n'y a pas d'enregistrements ayant un plus grand identifiant pour un même nom.

Il y a d'autres façons, cherchez donc 'max per group sql'. Voir aussi here, mais pas complète.

+0

Sympa, je n'ai jamais pensé à implémenter "small AND shiny" en tant que jointure. – Ronnis

Questions connexes