PostgreSQL現在日時完全ガイド!NOW()から日時計算まで実践的な使い方を徹底解説

データベース・SQL

「今の時刻をデータベースに記録したい」
「作成日時や更新日時を自動で設定したい」
「NOW()とCURRENT_TIMESTAMPって何が違うの?」
「タイムゾーンの扱いがよく分からない…」

こんな疑問を持っていませんか?

日時の扱いは、どんなアプリケーションでも必要になる基本機能です。でも、PostgreSQLには現在日時を取得する関数がたくさんあって、どれを使えばいいか迷いますよね。

この記事では、PostgreSQLでの現在日時の取得方法から、日時の計算、フォーマット、タイムゾーンの扱いまで、実例を交えて分かりやすく解説していきます。

スポンサーリンク

現在日時を取得する基本的な関数

NOW()関数 – 最もよく使う関数

現在の日時をタイムスタンプで取得する最も一般的な関数です。

基本的な使い方:

-- 現在の日時を取得
SELECT NOW();
-- 結果: 2024-01-20 14:30:45.123456+09

-- テーブルに挿入
INSERT INTO logs (message, created_at)
VALUES ('ログメッセージ', NOW());

-- 条件での使用
SELECT * FROM events
WHERE start_date <= NOW();

NOW()の特徴:

  • タイムゾーン付きのタイムスタンプを返す
  • ミリ秒単位まで取得
  • トランザクション開始時の時刻を返す(重要!)

CURRENT_TIMESTAMP – SQL標準

NOW()とほぼ同じ機能ですが、SQL標準の書き方です。

-- CURRENT_TIMESTAMPの使用
SELECT CURRENT_TIMESTAMP;
-- 結果: 2024-01-20 14:30:45.123456+09

-- 精度を指定(0〜6)
SELECT CURRENT_TIMESTAMP(0);  -- 秒まで
-- 結果: 2024-01-20 14:30:45+09

SELECT CURRENT_TIMESTAMP(3);  -- ミリ秒まで
-- 結果: 2024-01-20 14:30:45.123+09

-- NOW()と同じ結果
SELECT NOW() = CURRENT_TIMESTAMP;
-- 結果: true

CURRENT_DATE – 日付のみ

時刻を除いた日付だけが必要な場合に使います。

-- 今日の日付を取得
SELECT CURRENT_DATE;
-- 結果: 2024-01-20

-- 今日のデータを検索
SELECT * FROM orders
WHERE order_date = CURRENT_DATE;

-- 年齢計算
SELECT 
  name,
  birth_date,
  EXTRACT(YEAR FROM AGE(CURRENT_DATE, birth_date)) AS age
FROM users;

CURRENT_TIME – 時刻のみ

現在の時刻だけを取得します。

-- 現在時刻を取得
SELECT CURRENT_TIME;
-- 結果: 14:30:45.123456+09

-- 精度指定
SELECT CURRENT_TIME(0);
-- 結果: 14:30:45+09

-- 営業時間内かチェック
SELECT 
  CASE 
    WHEN CURRENT_TIME BETWEEN '09:00:00' AND '18:00:00' 
    THEN '営業中'
    ELSE '営業時間外'
  END AS status;

タイムゾーンを考慮した日時取得

タイムゾーン付きとなしの違い

PostgreSQLには2種類のタイムスタンプ型があります。

-- タイムゾーン付き(TIMESTAMP WITH TIME ZONE)
SELECT NOW();
-- 結果: 2024-01-20 14:30:45.123456+09

-- タイムゾーンなし(TIMESTAMP WITHOUT TIME ZONE)
SELECT NOW()::TIMESTAMP;
-- 結果: 2024-01-20 14:30:45.123456

-- ローカルタイムスタンプ
SELECT LOCALTIMESTAMP;
-- 結果: 2024-01-20 14:30:45.123456

特定のタイムゾーンでの現在時刻

-- UTCでの現在時刻
SELECT NOW() AT TIME ZONE 'UTC';
-- 結果: 2024-01-20 05:30:45.123456

-- 東京時間
SELECT NOW() AT TIME ZONE 'Asia/Tokyo';

-- ニューヨーク時間
SELECT NOW() AT TIME ZONE 'America/New_York';

-- 現在のタイムゾーン設定を確認
SHOW TIMEZONE;
-- 結果: Asia/Tokyo

-- タイムゾーンの一時変更
SET TIMEZONE = 'UTC';
SELECT NOW();
SET TIMEZONE = 'Asia/Tokyo';  -- 元に戻す

トランザクションと時刻の関係

NOW()とCLOCK_TIMESTAMP()の違い

これは重要な違いです!

BEGIN;

-- トランザクション開始時の時刻
SELECT NOW();
-- 結果: 2024-01-20 14:30:45.123456+09

-- 1秒待つ
SELECT pg_sleep(1);

-- NOW()は同じ値を返す(トランザクション開始時)
SELECT NOW();
-- 結果: 2024-01-20 14:30:45.123456+09(同じ!)

-- CLOCK_TIMESTAMP()は現在の実時刻
SELECT CLOCK_TIMESTAMP();
-- 結果: 2024-01-20 14:30:46.234567+09(1秒後)

COMMIT;

使い分け:

-- ログの一貫性が必要な場合:NOW()
INSERT INTO audit_log (action, timestamp) 
VALUES ('開始', NOW());
-- 処理...
INSERT INTO audit_log (action, timestamp) 
VALUES ('終了', NOW());  -- 同じタイムスタンプ

-- 実際の処理時間を記録:CLOCK_TIMESTAMP()
INSERT INTO performance_log (start_time, end_time)
VALUES (
  CLOCK_TIMESTAMP(),
  CLOCK_TIMESTAMP() + INTERVAL '5 seconds'
);

STATEMENT_TIMESTAMP()

SQL文の実行開始時刻を返します。

-- 各タイムスタンプ関数の違い
SELECT 
  NOW() AS "トランザクション開始",
  STATEMENT_TIMESTAMP() AS "SQL文開始",
  CLOCK_TIMESTAMP() AS "現在時刻";

-- 大量データ処理での使用例
INSERT INTO process_log (batch_id, row_id, processed_at)
SELECT 
  STATEMENT_TIMESTAMP(),  -- バッチ単位で同じ
  id,
  CLOCK_TIMESTAMP()       -- 各行で異なる
FROM large_table;

デフォルト値として現在日時を設定

テーブル作成時のデフォルト設定

-- 作成日時と更新日時を自動設定
CREATE TABLE articles (
  id SERIAL PRIMARY KEY,
  title VARCHAR(200),
  content TEXT,
  created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
  updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

-- データ挿入(日時は自動設定)
INSERT INTO articles (title, content)
VALUES ('PostgreSQL入門', '内容...');

-- 確認
SELECT * FROM articles;

更新時に自動で日時を更新

トリガーを使って更新日時を自動更新します。

-- 更新日時を自動更新する関数
CREATE OR REPLACE FUNCTION update_updated_at()
RETURNS TRIGGER AS $$
BEGIN
  NEW.updated_at = NOW();
  RETURN NEW;
END;
$$ LANGUAGE plpgsql;

-- トリガーの作成
CREATE TRIGGER update_articles_updated_at
  BEFORE UPDATE ON articles
  FOR EACH ROW
  EXECUTE FUNCTION update_updated_at();

-- テスト
UPDATE articles SET title = '新しいタイトル' WHERE id = 1;
SELECT * FROM articles;  -- updated_atが更新される

日時の計算と操作

日時の加算・減算

-- 現在から30日後
SELECT NOW() + INTERVAL '30 days';

-- 現在から1時間前
SELECT NOW() - INTERVAL '1 hour';

-- 複雑な計算
SELECT NOW() + INTERVAL '1 year 2 months 3 days 4 hours 5 minutes';

-- 変数を使った計算
SELECT NOW() + (7 * INTERVAL '1 day');  -- 7日後

-- 営業日の計算(週末を除く)
SELECT CURRENT_DATE + 
  CASE 
    WHEN EXTRACT(DOW FROM CURRENT_DATE) = 5 THEN INTERVAL '3 days'  -- 金曜
    WHEN EXTRACT(DOW FROM CURRENT_DATE) = 6 THEN INTERVAL '2 days'  -- 土曜
    ELSE INTERVAL '1 day'
  END AS next_business_day;

日時の差分計算

-- 2つの日時の差
SELECT AGE(NOW(), '2020-01-01');
-- 結果: 4 years 19 days 14:30:45.123456

-- 日数の差
SELECT DATE_PART('day', NOW() - '2024-01-01'::TIMESTAMP);

-- 時間単位で差を計算
SELECT EXTRACT(EPOCH FROM (NOW() - '2024-01-20 12:00:00'::TIMESTAMP)) / 3600 AS hours_diff;

-- 稼働時間の計算
SELECT 
  task_name,
  started_at,
  completed_at,
  completed_at - started_at AS duration
FROM tasks
WHERE completed_at IS NOT NULL;

日時の切り捨て・切り上げ

-- 時間単位で切り捨て
SELECT DATE_TRUNC('hour', NOW());
-- 結果: 2024-01-20 14:00:00+09

-- 日単位で切り捨て(その日の00:00:00)
SELECT DATE_TRUNC('day', NOW());
-- 結果: 2024-01-20 00:00:00+09

-- 月初
SELECT DATE_TRUNC('month', NOW());
-- 結果: 2024-01-01 00:00:00+09

-- 週の始まり(月曜日)
SELECT DATE_TRUNC('week', NOW());

-- 四半期の始まり
SELECT DATE_TRUNC('quarter', NOW());

-- 年初
SELECT DATE_TRUNC('year', NOW());

日時のフォーマット

TO_CHAR()関数でフォーマット

-- 基本的なフォーマット
SELECT TO_CHAR(NOW(), 'YYYY-MM-DD HH24:MI:SS');
-- 結果: 2024-01-20 14:30:45

-- 日本語形式
SELECT TO_CHAR(NOW(), 'YYYY年MM月DD日 HH24時MI分SS秒');
-- 結果: 2024年01月20日 14時30分45秒

-- 曜日付き
SELECT TO_CHAR(NOW(), 'YYYY-MM-DD (Day)');
-- 結果: 2024-01-20 (Saturday  )

-- 月名表示
SELECT TO_CHAR(NOW(), 'Month DD, YYYY');
-- 結果: January   20, 2024

-- AM/PM表示
SELECT TO_CHAR(NOW(), 'YYYY-MM-DD HH12:MI:SS AM');
-- 結果: 2024-01-20 02:30:45 PM

よく使うフォーマットパターン

-- ISO 8601形式
SELECT TO_CHAR(NOW(), 'YYYY-MM-DD"T"HH24:MI:SS.MS');

-- ファイル名用(記号なし)
SELECT TO_CHAR(NOW(), 'YYYYMMDD_HH24MISS');
-- 結果: 20240120_143045

-- 短い日付
SELECT TO_CHAR(NOW(), 'YY/MM/DD');
-- 結果: 24/01/20

-- 時刻のみ
SELECT TO_CHAR(NOW(), 'HH24:MI');
-- 結果: 14:30

実践的な使用例

ログテーブルでの活用

-- アクセスログテーブル
CREATE TABLE access_logs (
  id BIGSERIAL PRIMARY KEY,
  user_id INT,
  endpoint VARCHAR(255),
  method VARCHAR(10),
  status_code INT,
  response_time_ms INT,
  accessed_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),

  -- インデックス
  INDEX idx_accessed_at (accessed_at),
  INDEX idx_user_accessed (user_id, accessed_at)
);

-- 直近1時間のアクセス数
SELECT COUNT(*) 
FROM access_logs
WHERE accessed_at >= NOW() - INTERVAL '1 hour';

-- 時間帯別アクセス数
SELECT 
  DATE_TRUNC('hour', accessed_at) AS hour,
  COUNT(*) AS access_count
FROM access_logs
WHERE accessed_at >= CURRENT_DATE
GROUP BY hour
ORDER BY hour;

予約システムでの使用

-- 予約可能な時間枠を生成
WITH RECURSIVE time_slots AS (
  SELECT CURRENT_DATE + TIME '09:00:00' AS slot_time
  UNION ALL
  SELECT slot_time + INTERVAL '30 minutes'
  FROM time_slots
  WHERE slot_time < CURRENT_DATE + TIME '17:30:00'
)
SELECT 
  slot_time,
  CASE 
    WHEN slot_time < NOW() THEN '過去'
    WHEN EXISTS (
      SELECT 1 FROM reservations 
      WHERE reservation_time = slot_time
    ) THEN '予約済み'
    ELSE '空き'
  END AS status
FROM time_slots;

期限管理

-- 期限切れチェック
SELECT 
  id,
  title,
  deadline,
  CASE
    WHEN deadline < NOW() THEN '期限切れ'
    WHEN deadline < NOW() + INTERVAL '1 day' THEN '本日期限'
    WHEN deadline < NOW() + INTERVAL '7 days' THEN '1週間以内'
    ELSE '余裕あり'
  END AS status,
  deadline - NOW() AS remaining_time
FROM tasks
ORDER BY deadline;

よくある質問と回答

Q1:NOW()とCURRENT_TIMESTAMPの違いは?

回答:
機能的には同じです。NOW()はPostgreSQL独自、CURRENT_TIMESTAMPはSQL標準です。どちらを使っても構いませんが、チーム内で統一することをお勧めします。

Q2:日本時間で保存されているか確認したい

回答:

-- タイムゾーン設定確認
SHOW TIMEZONE;

-- データベースのデフォルトタイムゾーン
SELECT current_setting('TIMEZONE');

-- 特定の時刻を日本時間で表示
SELECT created_at AT TIME ZONE 'Asia/Tokyo' FROM logs;

Q3:ミリ秒を切り捨てたい

回答:

-- 秒単位に切り捨て
SELECT DATE_TRUNC('second', NOW());

-- または精度指定
SELECT NOW()::TIMESTAMP(0);

Q4:文字列から日時に変換したい

回答:

-- 標準形式
SELECT '2024-01-20 14:30:45'::TIMESTAMP;

-- TO_TIMESTAMP関数
SELECT TO_TIMESTAMP('20/01/2024 14:30', 'DD/MM/YYYY HH24:MI');

まとめ:PostgreSQLの日時を使いこなそう!

PostgreSQLでの現在日時の扱いについて、基本から応用まで解説しました。

重要ポイントのおさらい:

  • NOW():最も汎用的な現在日時取得
  • トランザクション内では同じ値:一貫性が保たれる
  • CLOCK_TIMESTAMP():実時刻が必要な場合
  • タイムゾーン:WITH TIME ZONEを使う

よく使うパターン:

-- 基本の取得
SELECT NOW();

-- デフォルト値
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()

-- 日時計算
NOW() + INTERVAL '30 days'

-- フォーマット
TO_CHAR(NOW(), 'YYYY-MM-DD HH24:MI:SS')

ベストプラクティス:

  1. タイムスタンプ型はWITH TIME ZONEを使用
  2. 作成日時・更新日時は自動設定
  3. インデックスを適切に設定
  4. タイムゾーンを意識した設計

日時の扱いは、どんなアプリケーションでも重要な要素です。この記事で紹介した技術を活用して、堅牢で使いやすいデータベース設計を実現してください!

コメント

タイトルとURLをコピーしました