Disons que j'ai une table "valeurs" qui contient les champs id (entier) nom (varchar) valeur (flottant) horodatage (int)
Maintenant, je veux calculer la valeur la plus basse et la première valeur la plus élevée (basée sur l'horodatage) pour chaque nom sur l'ensemble de la table des valeurs.
Est-ce possible d'atteindre dans une seule requête performante ? Je suis tombé sur la fonction 'first_value', mais celle-ci ne semble pas fonctionner. J'ai essayé la requête suivante, en utilisant des jointures, mais aussi sans succès.
name open low high USD 3 3 7 EUR 5 2 8 GBP 4 4 8
N'y a-t-il pas une sorte de fonction qui rendrait quelque chose de similaire possible?
id name value timestamp 1 USD 3 16540 2 EUR 5 16540 3 GBP 4 16540 4 EUR 2 16600 5 USD 4 16600 6 GBP 5 16600 7 USD 6 16660 8 EUR 7 16660 9 GBP 6 16660 10 USD 5 16720 11 EUR 5 16720 12 GBP 7 16720 13 EUR 8 16780 14 USD 7 16780 15 GBP 8 16780
Exemple de données
SELECT name, FIRST_VALUE(value) as open, MIN(value) as low, MAX(value) as high FROM values GROUP BY name ORDER BY timestamp ASC;
Exemple de sortie
SELECT a.name, b.value as open, MIN(a.value) as low, MAX(a.value) as high FROM values a LEFT JOIN values b ON a.name = b.name AND b.id = MIN(a.id) GROUP BY a.name;
J'utilise la version du client MySQL: 5.6.39 Une égalité ne devrait pas être possible, si c'est le cas, je me fiche de la valeur choisie.
3 Réponses :
Si vous utilisez MySQL 8.0, cela peut être assez facilement résolu avec les fonctions de fenêtre:
select t.name, t.value open, (select min(value) from mytable t1 where t1.name = t.name) low, (select max(value) from mytable t1 where t1.name = t.name) high from mytable t where timestamp = ( select min(t1.timestamp) from mytable t1 where t1.name = t.name )
select t.name, t.value open, t0.low, t0.high from mytable t inner join ( select name, min(value) low, max(value) high from mytable group by name ) t0 on t0.name = t.name where t.timestamp = ( select min(t1.timestamp) from mytable t1 where t1.name = t.name );
Dans les versions antérieures, vous pourrait:
Query:
| name | open | low | high | | ---- | ---- | --- | ---- | | EUR | 5 | 2 | 8 | | GBP | 4 | 4 | 8 | | USD | 3 | 3 | 7 |
Démo sur MySQL 5.6 DB Fiddle : mêmes résultats que ci-dessus
Ceci pourrait également être réalisé en utilisant des sous-requêtes en ligne (qui peuvent en fait mieux fonctionner): / p>
select name, value open, low, high from ( select name, value, min(value) over(partition by name) low, max(value) over(partition by name) high, row_number() over(partition by name order by timestamp) rn from mytable ) x where rn = 1
J'utilise la version 5.6.39. Je devrais peut-être envisager de simplement mettre à jour mon mysql.
@GillesLesire: oui, vous devriez envisager une mise à jour: MySQL 5.6 est sorti en 2013 et est hors support depuis 2018 ... Quoi qu'il en soit, j'ai mis à jour ma réponse avec des solutions pour cette ancienne version.
@GillesLesire: ma réponse (mise à jour) répond-elle correctement à votre question?
Oui, mais j'ai trouvé une solution plus performante pour le moment. Je cherche actuellement à mettre à niveau ma version mysql vers la version 8 et voir si votre solution offre de meilleurs résultats.
dans une seule requête performante
Faites-le de manière logique et laissez le SGBD se soucier des performances. Si ce n'est pas assez rapide, vérifiez vos index.
La valeur associée au premier horodatage nécessite une jointure. Vous pouvez trouver le premier horodatage assez facilement. Obtenir une valeur à partir d'une ligne associée à une ligne donnée: c'est à cela que servent les jointures.
Donc, nous avons:
SELECT name, value as open, v1.low v1.high FROM values as v join ( select name, min(timestamp) as timestamp, min(value) as low, max(value) as high FROM values GROUP BY name ) as v1 on v.name = v1.name and v.timestamp = v1.timestamp
Je suppose que "vi" est une faute de frappe et est censé être "v1"?
De plus, la sous-requête avait besoin d'une instruction FROM pour qu'elle fonctionne.
Cette solution semble offrir les meilleures performances.
SELECT name, CAST(SUBSTRING_INDEX(GROUP_CONCAT(CAST(value AS CHAR) ORDER BY TIMESTAMP ASC), ',', 1) AS DECIMAL(10, 6)) AS open, MIN(value) AS low, MAX(value) AS high FROM mytable GROUP BY name ORDER BY name ASC
Quelle version de MySQL utilisez-vous?
Ce que vous cherchez à faire n'est pas très clair. Pourriez-vous fournir des exemples de données et des résultats attendus pour clarifier votre question?
Définissez «premier» même lorsque vous utilisez ORDER BY, le tri n'est pas corrigé dans les cas où les valeurs sont égales. De plus, cet ORDER BY n'est pas valide avec les règles ANSI / ISO SQL GROUP BY .. comme @GMB a suggéré que nous devions voir des exemples de données et résultats attendus. Voir Pourquoi devrais-je fournir un exemple minimal reproductible pour une requête SQL très simple?
avez-vous essayé la commande UNION?
J'ai édité l'article avec des exemples de données