Redshift

Snippets

動作確認用のデータを用意する

1 列の場合は以下などの方法がある。

WITH test_data AS (
  SELECT TO_TIMESTAMP(v::TEXT, 'YYYYMMDD HH24:MI:SS.MS') AS ts
  FROM (
    SELECT ARRAY(
      '20240325 00:00:00.000',
      '20240326 00:00:00.000') AS v
  ) AS g, g.v AS v
)
SELECT ts AT TIME ZONE 'Asia/Tokyo' AS ts FROM test_data

ts
2024-03-25 09:00:00
2024-03-26 09:00:00
2 列以上の場合は以下などの方法がある。

WITH test_data AS (
  SELECT
    v[0]::TEXT AS fruit,
    v[1] AS price
  FROM (
    SELECT ARRAY(
      ARRAY('apple', 200),
      ARRAY('banana', 100)) AS v
  ) AS g, g.v AS v
)
SELECT * FROM test_data

fruit,  price
apple,  200
banana, 100

月曜日に丸める

Redshift の DOW (曜日番号) は日曜日が 0, ... 土曜日が 6 なので、日曜日を除いては「DOW - 1」日前の日付をとればよい。日曜日については 6 日前の日付をとればよい。
  • ISODOW がとれるなら月曜日が 1, ..., 日曜日が 7 になるので CASE 文が要らないが、少なくとも手元の Redshift は対応していない。
  • なお、「(DOW + 6) % 7」日前をとることでも CASE 文は要らなくなる。

WITH test_data AS (
  SELECT TO_TIMESTAMP(v::TEXT, 'YYYYMMDD HH24:MI:SS.MS') AS ts
  FROM (
    SELECT ARRAY(
      '20240325 00:00:00.000',
      '20240326 00:00:00.000',
      '20240331 00:00:00.000',
      '20240401 00:00:00.000') AS v
  ) AS g, g.v AS v
)
SELECT
  ts AT TIME ZONE 'Asia/Tokyo' AS ts_jst,
  CASE
    WHEN EXTRACT(DOW FROM ts_jst) = 0 THEN TO_CHAR(ts_jst - INTERVAL '6 day', 'YYYY-MM-DD')
    ELSE TO_CHAR(ts_jst - INTERVAL '1 day' * (EXTRACT(DOW FROM ts_jst) - 1), 'YYYY-MM-DD')
  END AS week_0,
  TO_CHAR(ts_jst - INTERVAL '1 day' * ((EXTRACT(DOW FROM ts_jst) + 6) % 7), 'YYYY-MM-DD') AS week_1
FROM test_data

ts_jst,              week_0,     week_1
2024-03-25 09:00:00, 2024-03-25, 2024-03-25
2024-03-26 09:00:00, 2024-03-25, 2024-03-25
2024-03-31 09:00:00, 2024-03-25, 2024-03-25
2024-04-01 09:00:00, 2024-04-01, 2024-04-01

基準日から N×X 日に丸める

基準日があり、そこから 7 日間や 28 日間などの区間で区切っていきたいときは以下の方法がある。なお、DATEDIFF で日数の差をとるときは、日付部分の差が取られるので、先に目的のタイムゾーンにしなければ意図通りにならない (i.e. 20240325 00:00+09 と 20240325 09:00+09 は同じ日なので日数の差は 0 になってほしいが、これらを協定世界時 20240324 15:00+00 と 20240325 00:00+00 として扱うと別々の日になり日数の差が 1 となってしまう)。

WITH test_data AS (
  SELECT TO_TIMESTAMP(v::TEXT, 'YYYYMMDD HH24:MI:SS.MS') AS ts
  FROM (
    SELECT ARRAY(
      '20240325 00:00:00.000',
      '20240325 14:59:59.000',
      '20240325 15:00:00.000',
      '20240331 00:00:00.000',
      '20240421 00:00:00.000',
      '20240422 00:00:00.000',
      '20240519 00:00:00.000',
      '20240520 00:00:00.000') AS v
  ) AS g, g.v AS v
),
configs AS (
  SELECT
    'YYYYMMDD HH24:MI:SS.MS' AS tf,  -- 時刻フォーマット
    -(INTERVAL '9 hour') AS td,  -- 日本標準時 - 世界標準時
    TO_TIMESTAMP('20240325 00:00:00.000'::TEXT, tf) + td AS bd,  -- 基準日
    bd AT TIME ZONE 'Asia/Tokyo' AS bd_jst
)
SELECT
  ts AT TIME ZONE 'Asia/Tokyo' AS ts_jst,
  DATEDIFF(days, (SELECT bd_jst FROM configs)::TIMESTAMP, ts_jst::TIMESTAMP) AS diff,
  TO_CHAR(ts_jst - INTERVAL '1 day' * (diff % 7), 'YYYY-MM-DD') AS week,
  TO_CHAR(ts_jst - INTERVAL '1 day' * (diff % 28), 'YYYY-MM-DD') AS week4
FROM test_data

ts_jst,              diff, week,       week4
2024-03-25 09:00:00, 0,    2024-03-25, 2024-03-25
2024-03-25 23:59:59, 0,    2024-03-25, 2024-03-25
2024-03-26 00:00:00, 1,    2024-03-25, 2024-03-25
2024-03-31 09:00:00, 6,    2024-03-25, 2024-03-25
2024-04-21 09:00:00, 27,   2024-04-15, 2024-03-25
2024-04-22 09:00:00, 28,   2024-04-22, 2024-04-22
2024-05-19 09:00:00, 55,   2024-05-13, 2024-04-22
2024-05-20 09:00:00, 56,   2024-05-20, 2024-05-20

プレフィクスを除去する

REPLACE('hoge_xxx', 'hoge_', '') でもよいがプレフィクスであれば以下の方が速い。

SELECT SUBSTRING('hoge_xxx', LENGTH('hoge_') + 1)

グループごとの値の登場数ランキングを集計する

各人の好きな果物テーブルからクラスごとの好きな果物ランキングを集計する。登場数が同じなら果物名で順位を付ける場合は ORDER BY COUNT(*) DESC, fruit とする。

WITH sample_table AS (
  SELECT '1年1組' AS class_, 'りんご' AS fruit UNION ALL
  SELECT '1年1組' AS class_, 'りんご' AS fruit UNION ALL
  SELECT '1年1組' AS class_, 'バナナ' AS fruit UNION ALL
  SELECT '1年1組' AS class_, 'バナナ' AS fruit UNION ALL
  SELECT '1年1組' AS class_, 'バナナ' AS fruit UNION ALL
  SELECT '1年2組' AS class_, 'バナナ' AS fruit UNION ALL
  SELECT '1年2組' AS class_, 'みかん' AS fruit UNION ALL
  SELECT '1年2組' AS class_, 'みかん' AS fruit UNION ALL
  SELECT '1年2組' AS class_, 'みかん' AS fruit UNION ALL
  SELECT '1年2組' AS class_, 'みかん' AS fruit UNION ALL
  SELECT '1年2組' AS class_, 'キウイ' AS fruit
),
tmp_table_0 AS (
  SELECT
    class_, fruit, COUNT(*) AS n_student,
    RANK() OVER (PARTITION BY class_ ORDER BY COUNT(*) DESC, fruit) AS rank
  FROM sample_table GROUP BY class_, fruit
)
SELECT
  class_, SUM(n_student) AS n_student,
  MAX(CASE WHEN rank = 1 THEN fruit || '(' || n_student::VARCHAR || ')' ELSE NULL END) AS fruit_1,
  MAX(CASE WHEN rank = 2 THEN fruit || '(' || n_student::VARCHAR || ')' ELSE NULL END) AS fruit_2,
  MAX(CASE WHEN rank = 3 THEN fruit || '(' || n_student::VARCHAR || ')' ELSE NULL END) AS fruit_3
FROM tmp_table_0
GROUP BY class_
ORDER BY class_

class_, n_student, fruit_1,  fruit_2,  fruit_3
1年1組, 5,         バナナ(3), りんご(2), 
1年2組, 6,         みかん(4), キウイ(1), バナナ(1)