web-dev-qa-db-fra.com

Sélection des première et dernière valeurs d'un groupe

J'ai un tableau MySql composé de cotations boursières quotidiennes (ouvertes, élevées, basses, fermées et en volume) que j'essaie de convertir en données hebdomadaires à la volée. Jusqu'à présent, j'ai la fonction suivante, qui fonctionne pour les hauts, les bas et le volume:

SELECT MIN(_low), MAX(_high), AVG(_volume),
CONCAT(YEAR(_date), "-", WEEK(_date)) AS myweek
FROM mystockdata
GROUP BY myweek
ORDER BY _date;

J'ai besoin de sélectionner la première instance de _open dans la requête ci-dessus. Ainsi, par exemple, s'il y a un jour férié le lundi (au cours d'une semaine) et que le marché boursier est ouvert le mardi, la valeur _open doit être sélectionnée à partir du mardi qui est regroupé dans sa semaine. De même, la valeur de fermeture doit être la dernière _close de cette semaine.

Est-il possible de sélectionner quelque chose comme FIRST () et LAST () dans MySql afin que ce qui précède puisse être enveloppé dans un seul SELECT plutôt que d'utiliser des requêtes de sélection imbriquées?

Voici l'instruction create de ma table pour avoir une idée du schéma:

delimiter $$
CREATE TABLE `mystockdata` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `symbol_id` int(11) NOT NULL,
  `_open` decimal(11,2) NOT NULL,
  `_high` decimal(11,2) NOT NULL,
  `_low` decimal(11,2) NOT NULL,
  `_close` decimal(11,2) NOT NULL,
  `_volume` bigint(20) NOT NULL,
  `add_date` date NOT NULL,
  PRIMARY KEY (`id`),
  KEY `Symbol_Id` (`symbol_id`,`add_date`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8$$

Mise à jour: il n'y a pas de null, partout où il y a un jour férié/week-end, la table ne porte aucun enregistrement pour cette date.

22
Zishan

Si vous utilisez MySQL 8, la solution préférable utiliserait les fonctions de fenêtre FIRST_VALUE () et/ou LAST_VALUE () , qui sont maintenant disponibles. Veuillez consulter réponse de Lukas Eder .

Mais si vous utilisez une ancienne version de MySQL, ces fonctions ne sont pas prises en charge. Vous devez les simuler en utilisant une sorte de solution de contournement, par exemple, vous pouvez utiliser la fonction de chaîne agrégée GROUP_CONCAT () qui crée un ensemble de tous les _open et _close valeurs de la semaine ordonnées par _date pour _open et par _date desc pour _close, et extraire le premier élément de l'ensemble:

select
  min(_low),
  max(_high),
  avg(_volume),
  concat(year(_date), "-", lpad(week(_date), 2, '0')) AS myweek,
  substring_index(group_concat(cast(_open as CHAR) order by _date), ',', 1 ) as first_open,
  substring_index(group_concat(cast(_close as CHAR) order by _date desc), ',', 1 ) as last_close
from
  mystockdata
group by
  myweek
order by
  myweek
;

Une autre solution consisterait à utiliser des sous-requêtes avec LIMIT 1 dans la clause SELECT:

select
  min(_low),
  max(_high),
  avg(_volume),
  concat(year(_date), "-", lpad(week(_date), 2, '0')) AS myweek,
  (
    select _open
    from mystockdata m
    where concat(year(_date), "-", lpad(week(_date), 2, '0'))=myweek
    order by _date
    LIMIT 1
  ) as first_open,
  (
    select _close
    from mystockdata m
    where concat(year(_date), "-", lpad(week(_date), 2, '0'))=myweek
    order by _date desc
    LIMIT 1
  ) as last_close
from
  mystockdata
group by
  myweek
order by
  myweek
;

Veuillez noter que j'ai ajouté la fonction de chaîne LPAD () à myweek, pour que le numéro de semaine soit toujours composé de deux chiffres, sinon les semaines ne seront pas commandées correctement.

Soyez également prudent lorsque vous utilisez substring_index en conjonction avec group_concat (): si l'une des chaînes groupées contient une virgule, la fonction peut ne pas retourner le résultat attendu.

34
fthiella

À partir de MySQL 8, vous utiliseriez idéalement fonctions de fenêtre pour la tâche:

WITH 
  t1 AS (
    SELECT _low, _high, _volume, CONCAT(YEAR(_date), "-", WEEK(_date)) AS myweek
    FROM mystockdata
  ),
  t2 AS (
    SELECT 
      t1.*, 
      FIRST_VALUE(_open) OVER (PARTITION BY myweek ORDER BY _date) AS first_open,
      FIRST_VALUE(_close) OVER (PARTITION BY myweek ORDER BY _date DESC) AS last_close
    FROM t1
  )
SELECT MIN(_low), MAX(_high), AVG(_volume), myweek, MIN(first_open), MAX(last_close)
FROM t2
GROUP BY myweek
ORDER BY myweek;
2
Lukas Eder

Vous aurez probablement besoin de la fonction COALESCE pour obtenir la première valeur. Cependant, vous devez vous assurer que les jours sans données (week-ends et jours fériés) ont une valeur nulle pour _open ces jours-là sans données.

L'utilisation serait:

SELECT MIN(_low), MAX(_high), AVG(_volume), COALESCE(_open)
CONCAT(YEAR(_date), "-", WEEK(_date)) AS myweek
FROM mystockdata
GROUP BY myweek
ORDER BY _date;

Pour la dernière valeur (), je ne peux penser qu'à une solution assez hacky, qui serait d'utiliser GROUP_CONCAT puis manipulation de chaîne pour obtenir la dernière valeur de la liste. Alors peut-être quelque chose comme ça:

SELECT MIN(_low), MAX(_high), AVG(_volume), COALESCE(_open), SUBSTRING_INDEX(GROUP_CONCAT(_close), ',', -1)
CONCAT(YEAR(_date), "-", WEEK(_date)) AS myweek
FROM mystockdata
GROUP BY myweek
ORDER BY _date;

Notez que vous pouvez également utiliser le GROUP_CONCAT approche pour le premier élément au lieu de fusionner si vous vouliez une recherche cohérente

SELECT MIN(_low), MAX(_high), AVG(_volume), SUBSTRING_INDEX(GROUP_CONCAT(_open), ',', 1), SUBSTRING_INDEX(GROUP_CONCAT(_close), ',', -1)
CONCAT(YEAR(_date), "-", WEEK(_date)) AS myweek
FROM mystockdata
GROUP BY myweek
ORDER BY _date;

Pour GROUP_CONCAT pour fonctionner correctement, vous devez également vous assurer que les dates sans valeur sont nulles dans _open et _close des champs.

1
Mike Brant