

PostgreSQLでintervalを「総時間:分:秒」形式にフォーマットする
この記事は、構成と文章の整理にAIを使っています。
あくまで読みやすくするための補助で、内容そのものをAIにすべて任せて書いているわけではありませんが、ところどころ相談しながら整えています。
検証環境はPostgreSQL 15。
1 day 00:15:25 みたいな interval 型の値を、24:15:25 のような「総時間:分:秒」形式で出したかった。日をまたいでも時間側に繰り上げて、24時間を超える表記にしたいというのが要件。
やりたいこと
考え方はシンプルで、EXTRACT(EPOCH FROM interval) で総秒数を取り出し、それを3600で割れば時間、余りを60で割れば分、さらに余りが秒になる。1 day は86400秒として計算されるので、日をまたいだ分もそのまま時間側に足し込まれる。
1 day 00:15:25→ 87325秒 →24:15:252 days 03:45:10→ 186310秒 →51:45:10
計算式自体はこれで合っている。
クエリ
example_table の timestamp_column から経過時間を計算して、category ごとに集計する想定。
WITH duration_calc AS ( SELECT category, MAX(timestamp_column) AS max_timestamp, MIN(timestamp_column) AS min_timestamp, EXTRACT(EPOCH FROM (MAX(timestamp_column) - MIN(timestamp_column)))::numeric AS total_seconds FROM example_table GROUP BY category)SELECT category, max_timestamp, min_timestamp, TO_CHAR(FLOOR(total_seconds / 3600), 'FM00') || ':' || TO_CHAR(FLOOR((total_seconds % 3600) / 60), 'FM00') || ':' || TO_CHAR(total_seconds % 60, 'FM00') AS elapsed_timeFROM duration_calcORDER BY category;ポイントは2つ。
MAX(timestamp_column) - MIN(timestamp_column)はCTEの中で1回だけ計算してtotal_secondsに持たせる。時・分・秒それぞれで同じ集約を書き直す必要がなくなるEXTRACT(EPOCH FROM ...)の結果は::numericにキャストしてから使う。EXTRACT()の戻り値はdouble precisionだが、PostgreSQLの%(剰余)演算子はsmallint/integer/bigint/numericにしか対応しておらず、double precisionのままだと... % 3600や... % 60の箇所でoperator does not exist: double precision % integerになる。numericにキャストしておけばここは問題なく通る
/ や FLOOR()、TO_CHAR() は double precision のままでも使えるので、キャストが必要なのは % を使う箇所だけ。
結果
1 day 00:15:25→24:15:252 days 03:45:10→51:45:10
狙い通りのフォーマットになった。24時間を超えても 24:15:25 51:45:10 のように総時間として繰り上がる。