私があなたの設計を理解しているなら、これは本当にスキーマ設計の問題です。それ以外の:
CREATE TABLE sometable (
date1 date,
time1 time,
date2 date,
time2 time
);
あなたは一般的に欲しい:
CREATE TABLE sometable (
timestamp1 timestamp with time zone,
timestamp2 timestamp with time zone
);
タイムスタンプを自動的にUTCに変換してクライアントに戻す場合TimeZone
、またはtimestamp without time zone
タイムゾーン変換なしで生のタイムスタンプを保存する場合。
包括的テストで問題がない場合は、次のように記述できます。
SELECT ...
FROM sometable
WHERE '2012-01-01 11:15 +0800' BETWEEN timestamp1 AND timestamp2;
スキーマを修正できない場合、最善の策は次のようなものです。
SELECT ...
FROM sometable
WHERE '2012-01-01 11:15 +0800' BETWEEN (date1 + time1) AND (date2 + time2);
複数のタイムゾーンのクライアントに関しては、これには予期しない癖がある可能性があります。AT TIME ZONE
オペレーターを見る必要があるかもしれません。
片側または反対側で専用テストが必要な場合はBETWEEN
、a <= x <= b
オペレーターであるため使用できません。代わりに次のように記述します。
SELECT ...
FROM sometable
WHERE '2012-01-01 11:15 +0800' > (date1 + time1)
AND '2012-01-01 11:15 +0800' < (date2 + time2);
スキーマ変更の自動化
スキーマ変更の自動化が可能です。
と列のペアを持つテーブルに対してINFORMATION_SCHEMA
またはとをクエリしpg_catalog.pg_class
、それらを統合するためのコマンドのセットを生成する必要があります。pg_catalog.pg_attribute
date
time
ALTER TABLE
「ペア」が何であるかを判断することは、アプリケーション固有です。LIKE
一貫した命名スキームを使用している場合は、 or~
演算子および/またはで簡単に実行できるはずregexp_matches
です。(tablename, datecolumnname, timecolumnname)
タプルのセットを作成したいとします。
それができたら、(tablename, datecolumnname, timecolumnname)
タプルごとに次のALTER TABLE
ステートメントを生成できます。これらのステートメントは、安全のためにトランザクションで実行する必要[brackets]
があり、関心のあるデータで使用する前にテストする必要があります。また、のエントリは置換です。
BEGIN;
ALTER TABLE [tablename] ADD COLUMN [timestampcolumnname] TIMESTAMP WITH TIME ZONE;
--
-- WARNING: This part can lose data; if one of the columns is null and the other one isn't
-- the result is null. You should've had a CHECK constraint preventing that, but probably
-- didn't. You might need to special case that; the `coalesce` and `nullif` functions and
-- the `CASE` clause might be useful if so.
--
UPDATE [tablename] SET [timestampcolumnname] = ([datecolumnname] + [timecolumnname]);
ALTER TABLE [tablename] DROP COLUMN [datecolumnname];
ALTER TABLE [tablename] DROP COLUMN [timecolumnname];
-- Finally, if the originals were NOT NULL:
ALTER TABLE [tablename] ALTER COLUMN [timestampcolumnname] SET NOT NULL;
次に、結果を確認し、COMMIT
満足している場合。最初からテーブルに排他ロックが設定されているALTER
ため、あなたCOMMIT
または。まで他の人がテーブルを使用できないことに注意してくださいROLLBACK
。
漠然と最新のPostgreSQLを使用している場合は、format
関数を使用してSQLを生成できます。古いバージョンでは、文字列連結(||
)とquote_literal
関数を使用できます。例:
サンプルデータが与えられた場合:
CREATE TABLE sometable(date1 date not null, time1 time not null, date2 date not null, time2 time not null);
INSERT INTO sometable(date1,time1,date2,time2) VALUES
('2012-01-01','11:15','2012-02-03','04:00');
CREATE TABLE othertable(somedate date, sometime time);
INSERT INTO othertable(somedate, sometime) VALUES
(NULL, NULL),
(NULL, '11:15'),
('2012-03-08',NULL),
('2014-09-18','23:12');
これは、入力データセットを生成するクエリです。date
いずれかまたはtime
単語が列から削除されると、一致する列のペアには常に共通の名前が付けられるという命名規則に依存していることに注意してください。代わりに、をテストして隣接関係を使用できますc1.attnum + 1 = c2.attnum
。
BEGIN;
WITH
-- Create set of each date/time column along with its table name, oids, and not null flag
cols AS (
select attrelid, relname, attname, typname, atttypid, attnotnull
from pg_attribute
inner join pg_class on pg_attribute.attrelid = pg_class.oid
inner join pg_type on pg_attribute.atttypid = pg_type.oid
where NOT attisdropped AND atttypid IN ('date'::regtype, 'time'::regtype)
),
-- Self join the time and date column set, filtering the left side for only dates and
-- the right side for only times, producing two distinct sets. Then filter for entries
-- where the names are the same after replacing any appearance of the word `date` or
-- `time`.
tableinfo (tablename, datecolumnname, timecolumnname, nonnull, hastimezone) AS (
SELECT
c1.relname, c1.attname, c2.attname,
c1.attnotnull AND c2.attnotnull AS nonnull,
't'::boolean AS withtimezone
FROM cols c1
INNER JOIN cols c2 ON (
c1.atttypid = 'date'::regtype
AND c2.atttypid = 'time'::regtype
AND c1.attrelid = c2.attrelid
-- Match column pairs; I used name matching, you might use adjancency:
AND replace(c1.attname,'date','') = replace(c2.attname,'time','')
)
)
-- Finally, format the results into a series of ALTER TABLE statements.
SELECT format($$
ALTER TABLE %1$I ADD COLUMN %4$I TIMESTAMP %5$s;
UPDATE %1$I SET %4$I = (%2$I + %3$I);
ALTER TABLE %1$I DROP COLUMN %2$I;
ALTER TABLE %1$I DROP COLUMN %3$I;
$$ ||
-- Append a clause to make the column NOT NULL now that it's populated, only
-- if the original date or time were NOT NULL:
CASE
WHEN nonnull
THEN ' ALTER TABLE %1$I ALTER COLUMN %4$I SET NOT NULL;'
ELSE ''
END,
-- Now the format arguments
tablename, -- 1
datecolumnname, -- 2
timecolumnname, -- 3
-- You'd use a better column name generator than this simple example:
datecolumnname||'_'||timecolumnname, -- 4
CASE
WHEN hastimezone THEN 'WITH TIME ZONE'
ELSE 'WITHOUT TIME ZONE'
END -- 5
)
FROM tableinfo;
LOOP
結果を読み取って、2番目のセッションでSQLコマンドとして送信できます。または、結果を確認したい場合は、結果を上書きしてそれぞれを処理する非常に単純なPL/PgSQL関数を作成できますEXECUTE
。クエリは次のような出力を生成します。
ALTER TABLE sometable ADD COLUMN date1_time1 TIMESTAMP WITH TIME ZONE;
UPDATE sometable SET date1_time1 = (date1 + time1);
ALTER TABLE sometable DROP COLUMN date1;
ALTER TABLE sometable DROP COLUMN time1;
ALTER TABLE sometable ALTER COLUMN date1_time1 SET NOT NULL;
ALTER TABLE sometable ADD COLUMN date2_time2 TIMESTAMP WITH TIME ZONE;
UPDATE sometable SET date2_time2 = (date2 + time2);
ALTER TABLE sometable DROP COLUMN date2;
ALTER TABLE sometable DROP COLUMN time2;
ALTER TABLE sometable ALTER COLUMN date2_time2 SET NOT NULL;
ALTER TABLE othertable ADD COLUMN somedate_sometime TIMESTAMP WITHOUT TIME ZONE;
UPDATE othertable SET somedate_sometime = (somedate + sometime);
ALTER TABLE othertable DROP COLUMN somedate;
ALTER TABLE othertable DROP COLUMN sometime;
必要かどうかにかかわらず、列ごとに解決するための便利な方法があるかどうかはわかりませWITH TIME ZONE
んWITHOUT TIME ZONE
。ハードコーディングするだけで着地する可能性があります。その場合は、その列を削除するだけです。あなたのアプリケーションでそれを理解する良い方法がある場合に備えて、私はそこにそれを入れました。
時刻がnullであるが、日付がnullでない場合、またはその逆の場合は、nullの場合に返される結果を決定する式で日付と時刻をラップする必要があります。nullif
andcoalesce
関数は、と同様にこれに役立ちますCASE
。nullとnull以外の値を追加すると、nullの結果が生成されるため、特別なことをする必要がない場合があることに注意してください。
スキーマを使用する場合は、スキーマ名プレフィックスの%I置換を使用して曖昧さを解消するために、クエリをさらに絞り込む必要がある場合があります。スキーマを使用しない場合(スキーマが何であるかわからない場合は、使用しません)、これは問題ではありません。
これを行ったら、アプリケーションで意味のある場所以下のCHECK
制約を追加することを検討してください。ドキュメントの除外制約も確認してください。time1
time2