web-dev-qa-db-ja.com

グループの最初と最後の値を選択する

毎日の株価情報(始値、高値、安値、終値、出来高)で構成されるMySqlテーブルがあり、その場で毎週のデータに変換しようとしています。これまでのところ、次の機能があります。これは、高音、低音、および音量に対して機能します。

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

上記のクエリで_openの最初のインスタンスを選択する必要があります。たとえば、月曜日(特定の週)に休日があり、火曜日に株式市場が開いた場合、_open値はその週にグループ化された火曜日から選択する必要があります。同様に、終値はその週の最後の_closeである必要があります。

MySqlでFIRST()やLAST()のようなものを選択して、上記をネストされた選択クエリを使用するのではなく、単一のSELECT内でラップできるようにすることは可能ですか?

スキーマを理解するためのテーブルのcreateステートメントは次のとおりです。

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$$

更新:nullはありません。休日/週末がある場合は、テーブルにはその日付のレコードは含まれません。

22
Zishan

MySQL 8を使用している場合、望ましいソリューションはウィンドウ関数 FIRST_VALUE() および/または を使用しますLAST_VALUE() 、現在利用可能です。 ルーカス・エダーの答え をご覧ください。

ただし、古いバージョンのMySQLを使用している場合、これらの機能はサポートされていません。何らかの回避策を使用してそれらをシミュレートする必要があります。たとえば、すべての_openのセットを作成する集約文字列関数 GROUP_CONCAT() を使用できます。 _closeの場合は_date_openの場合は_date descで順序付けされた週の_close値、およびセットの最初の要素の抽出:

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
;

別のソリューションでは、SELECT句でLIMIT 1を使用してサブクエリを使用します。

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
;

週番号を常に2桁にするために、 LPAD() 文字列関数をmyweekに追加したことに注意してください。正しく注文してください。

また、group_concat()とともにsubstring_indexを使用する場合は注意してください。グループ化された文字列の1つにカンマが含まれていると、関数は期待される結果を返さない場合があります。

34
fthiella

MySQL 8以降では、タスクに理想的には window functions を使用します。

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

最初の値を取得するには、おそらくCOALESCE関数が必要になります。ただし、データのない日(週末と祝日)には、データのない日に_openのnull値が設定されていることを確認する必要があります。

使用法は次のとおりです。

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

Last()値については、GROUP_CONCATを使用してから文字列操作を使用してリストから最後の値を取得するという、かなりハッキングされたソリューションしか考えられません。おそらくこのようなもの:

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;

一貫した外観のクエリが必要な場合は、合体する代わりに最初のアイテムにGROUP_CONCATアプローチを使用することもできます。

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;

GROUP_CONCATが適切に機能するためには、_openおよび_closeフィールドの値のない日付にnullが含まれていることも確認する必要があります。

1
Mike Brant