0

Postgresqlに日付と時刻のフィールドがあります。私はPythonでそれを読んでいて、特定の時刻を過ぎた特定の日に物事を整理する必要があります。

手順は基本的に次のようになります。

  1. xから*を選択します。ここで、日付>月日年
  2. そのサブセットで、その日付に指定された時間以上のもののみを選択します
  3. AND date2は<monthdayyear2である必要があり、time2はその日付で指定されたtime2よりも小さい必要があります

結果などを繰り返すことで、これを実行できるPythonの方法が確かにいくつかあることを私は知っています。これをブルートフォースするよりも良い方法があるかどうか疑問に思っていますか?可能であれば、複数のクエリを実行したり、fetchall()で多くの余分な結果を整理したりする必要はありません。

4

1 に答える 1

4

私があなたの設計を理解しているなら、これは本当にスキーマ設計の問題です。それ以外の:

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オペレーターを見る必要があるかもしれません。

片側または反対側で専用テストが必要な場合はBETWEENa <= 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_attributedatetimeALTER 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 ZONEWITHOUT TIME ZONE。ハードコーディングするだけで着地する可能性があります。その場合は、その列を削除するだけです。あなたのアプリケーションでそれを理解する良い方法がある場合に備えて、私はそこにそれを入れました。

時刻がnullであるが、日付がnullでない場合、またはその逆の場合は、nullの場合に返される結果を決定する式で日付と時刻をラップする必要があります。nullifandcoalesce関数は、と同様にこれに役立ちますCASE。nullとnull以外の値を追加すると、nullの結果が生成されるため、特別なことをする必要がない場合があることに注意してください。

スキーマを使用する場合は、スキーマ名プレフィックスの%I置換を使用して曖昧さを解消するために、クエリをさらに絞り込む必要がある場合があります。スキーマを使用しない場合(スキーマが何であるかわからない場合は、使用しません)、これは問題ではありません。

これを行ったら、アプリケーションで意味のある場所以下のCHECK制約を追加することを検討してください。ドキュメントの除外制約も確認してください。time1time2

于 2013-02-13T14:10:00.373 に答える