PostgreSQLには、日付と時刻に関連するデータ型が多数組み込まれています。文字列や整数に対してそれらを使用する必要があるのはなぜですか?それらを使用している間、あなたは何に気をつけるべきですか? Postgresでこれらのデータ型を効果的に操作する方法の詳細をご覧ください。
SQL標準、ISO 8601標準、PostgreSQLの組み込みカタログ、および下位互換性により、重複するカスタマイズ可能な日付/時刻関連のデータ型と規則が多数定義され、せいぜい混乱を招きます。この混乱は通常、データベースドライバコード、アプリケーションコード、SQLルーチンに波及し、デバッグが困難な微妙なバグが発生します。
一方、ネイティブの組み込み型を使用すると、SQLステートメントが簡素化され、読み取りと書き込みがはるかに簡単になり、その結果、エラーが発生しにくくなります。たとえば、整数(エポックからの秒数)を使用して時間を表すと、扱いにくいSQL式になります。およびその他のアプリケーションコード。
ネイティブタイプの利点は、それほど苦痛ではないルールのセットを定義し、アプリケーションとopsコードベース全体にそれらを適用することを価値のあるものにします。これがそのようなセットの1つであり、必要に応じてさらにカスタマイズするための適切なデフォルトと適切な開始点を提供する必要があります。
次の3つのタイプのみを使用してください(多くは利用可能ですが):
- 日付 -特定の日付、時間なし
- タイムスタンプ -マイクロ秒の解像度の特定の日時
- 間隔 -マイクロ秒の解像度の時間間隔
これらの3つのタイプを合わせて、ほとんどのアプリケーションのユースケースをサポートする必要があります。特定のニーズ(ストレージの節約など)がない場合は、これらのタイプだけに固執することを強くお勧めします。
日付 時間のない日付を表し、実際には非常に便利です(以下の例を参照)。タイムスタンプタイプは、タイムゾーン情報を含むバリアントです。タイムゾーン情報がないと、値の解釈と抽出に影響を与える可能性のある変数が単純に存在します。最後に、間隔 マイクロ秒から数百万年までの時間間隔を表します。
次のリテラル表現のみを使用し、キャスト演算子を使用して、読みやすさを犠牲にすることなく冗長性を減らします。
-
'2012-12-25'::date
-ISO 8601 -
'2012-12-25 13:04:05.123-08:00'::timestamptz
-ISO 8601 '1 month 3 days'::interval
-インターバル入力用のPostgresの従来の形式
タイムゾーンを省略すると、Postgresサーバーのタイムゾーン設定、データベースレベル、セッションレベル、ロールレベル、または接続文字列で設定できるTimeZone構成、クライアントマシンのタイムゾーン設定、およびより多くのそのような要因。
アプリケーションコードからクエリを実行するときに、extract
を使用して間隔タイプを適切な単位(日や秒など)に変換します 関数を作成し、整数または実数値として値を読み込みます。
- GUC構成のデフォルト設定を変更しないでください
DateStyle
、TimeZone
およびlc_time
。 - 環境変数
PGDATESTYLE
を設定または使用しないでください およびPGTZ
。 -
SET [SESSION|LOCAL] TIME ZONE ...
を使用しないでください 。 - 可能であれば、Postgresサーバーを実行するマシン、およびそれに接続するアプリケーションコードを実行するすべてのマシンで、システムタイムゾーンをUTCに設定します。
- クライアントが1つのタイムゾーンで実行され、サーバーが別のタイムゾーンで実行されているときに、データベースドライバー(JDBCコネクターやGodatabase / sqlドライバーなど)が適切に動作することを検証します。有効な非UTC
TimeZone
で正しく機能することを確認します パラメータは接続文字列に含まれています。
最後に、これらはすべてガイドラインにすぎず、ニーズに合わせて調整できることに注意してください。ただし、最初にそうすることの影響を必ず調査してください。
では、ネイティブ型を使用すると、SQLコードを単純化するのにどの程度役立ちますか?ここにいくつかの例があります。
日付の値 タイプを減算して、間隔を得ることができます。 それらの間の。粒子状の日付に整数の日数を追加したり、日付に間隔を追加してタイムスタンプを指定したりすることもできます。 :
-- 10 days from now (outputs 2020-07-26)
SELECT now()::date + 10;
-- 10 days from now (outputs 2020-07-26 04:44:30.568847+00)
SELECT now() + '10 days'::interval;
-- days till christmas (outputs 161 days 14:06:26.759466)
SELECT '2020-12-25'::date - now();
-- the 10 longest courses
SELECT name, end_date - start_date AS duration
FROM courses
ORDER BY end_date - start_date DESC
LIMIT 10;
これらのタイプの値は同等です。そのため、lastqueryをend_date - start_date
で並べ替えることができます。 、タイプは間隔です 。別の例を次に示します:
-- certificates expiring within the next 7 days
SELECT name
FROM certificates
WHERE expiry_date BETWEEN now() AND now() + '7 days'::interval;
タイプtimestamptzの値 減算することもできます(間隔を与えるため) )、追加(間隔に) 別のタイムスタンプを与える )と比較しました。
-- difference of timestamps gives an interval
SELECT password_last_modified - created_at AS password_age
FROM users;
-- can also use the age() function
SELECT age(password_last_modified, created_at) AS password_age
FROM users;
このトピックについては、さまざまな「現在のタイムスタンプ」値を返す3つの異なる組み込み関数があることに注意してください。彼らは実際に異なるものを返します:
-- transaction_timestamp() returns the timestampsz of the start of current transaction
-- outputs 2020-07-16 05:09:32.677409+00
SELECT transaction_timestamp();
-- statement_timestamp() returns the timestamptz of the start of the current statement
SELECT statement_timestamp();
-- clock_timestamp() returns the timestamptz of the system clock
SELECT clock_timestamp();
これらの関数のエイリアスもあります:
-- now() actually returns the start of the current transaction, which means it
-- does not change during the transaction
SELECT now(), transaction_timestamp();
-- transaction timestamp is also returned by these keyword-style constructs
SELECT CURRENT_DATE, CURRENT_TIMESTAMP, transaction_timestamp();
間隔タイプの値は、列のデータ型として使用でき、相互に比較でき、タイムスタンプと日付に加算(および減算)できます。いくつかの例を次に示します。
-- interval-typed values can be stored and compared
SELECT num
FROM passports
WHERE valid_for > '10 years'::interval
ORDER BY valid_for DESC;
-- you can multiply them by numbers (outputs 4 years)
SELECT 4 * '1 year'::interval;
-- you can divide them by numbers (outputs 3 mons)
SELECT '1 year'::interval / 4;
-- you can add and subtract them (outputs 1 year 1 mon 6 days)
SELECT '1 year'::interval + '1.2 months'::interval;
PostgreSQLには、これらのタイプの値を操作するために使用できるいくつかの便利な関数と構造も付属しています。
抽出関数を使用して、日付からの月など、指定された値から指定された部分を取得できます。抽出できるパーツの完全なリストは、ここに文書化されています。ここに、いくつかの有用で非自明な例があります:
-- years from an interval (outputs 2)
SELECT extract(YEARS FROM '1.5 years 6 months'::interval);
-- day of the week (0=Sun .. 6=Sat) from timestamp (outputs 4)
SELECT extract(DOW FROM now());
-- day of the week (1=Mon .. 7=Sun) from timestamp (outputs 4)
SELECT extract(ISODOW FROM now());
-- convert interval to seconds (outputs 86400)
SELECT extract(EPOCH FROM '1 day'::interval);
最後の例は、アプリケーションが間隔を秒/分/日などの浮動小数点値として処理しやすいため、アプリケーションによって実行されるクエリで特に役立ちます。
タイムスタンプを表現するための便利な関数もあります 通常、これはアプリケーションコードで実行されます。その方法でテストする方が簡単で、Postgresserverが参照するタイムゾーンデータベースへの依存度が低くなります。それでも、それは時々役立つことがあります:
-- convert timestamps to a different time zone
SELECT timezone('Europe/Helsinki', now());
-- same as before, but this one is a SQL standard
SELECT now() AT TIME ZONE 'Europe/Helsinki';
関数to_char
(docs)日付、タイムスタンプ、間隔をフォーマット文字列に基づいてテキストに変換できます。これは、従来のC関数strftime
に相当するPostgresです。 。
-- outputs Thu, 16th July
SELECT to_char(now(), 'Dy, DDth Month');
-- outputs 01 06 00 12 00 00
SELECT to_char('1.5 years'::interval, 'YY MM DD HH MI SS');
テキストから日付に変換するには、to_date
を使用します 、およびテキストをタイムスタンプに変換するには、to_timestamp
を使用します 。この投稿の冒頭に記載されているフォームを使用する場合は、代わりにキャスト演算子を使用できることに注意してください。
-- outputs 2000-12-25 15:42:50+00
SELECT to_timestamp('2000.12.25.15.42.50', 'YYYY.MM.DD.HH24.MI.SS');
-- outputs 2000-12-25
SELECT to_date('2000.12.25.15.42.50', 'YYYY.MM.DD');
フォーマット文字列パターンの完全なリストについては、ドキュメントを参照してください。
単純な場合には、これらの関数を使用するのが最適です。より複雑な解析またはフォーマットについては、アプリケーションコードに依存することをお勧めします。これにより、(おそらく)単体テストを行うことができます。
特にバインドされたパラメータが使用されている場合、アプリケーションコードとの間で日付/タイムスタンプ/間隔の値を渡すことが不便な場合があります。たとえば、通常は、文字列形式ではなく、整数の日数(または時間、分)として間隔を渡す方が便利です。また、整数/浮動小数点の日数(または時間、分など)として間隔を空けて読み取る方が簡単です。
make_interval
関数を使用して、整数個のコンポーネント値から間隔値を作成できます(ここのドキュメントを参照してください)。to_timestamp
前に見た関数には、Unixエポック時間からタイムスタンプ値を作成できる別の形式があります。
-- pass the interval as number of days from the application code
SELECT name FROM courses WHERE duration <= make_interval(days => $1);
-- pass timestamptz as unix epoch (number of seconds from 1-Jan-1970)
SELECT id FROM events WHERE logged_at >= to_timestamp($1);
-- return interval as number of days (with a fractional part)
SELECT extract(EPOCH FROM duration) / 60 / 60 / 24;