0

我在 Postgresql 中有一个日期和时间字段。我正在用 python 阅读它,需要在特定时间过去的特定日子里整理东西。

步骤基本上是这样的:

  1. 选择 * from x where date > monthdayyear
  2. 在该子集中,仅选择那些 > 为该日期给出的时间
  3. AND date2 必须 < monthdayyear2 AND 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操作员。

如果您需要在一侧和/或另一侧进行排他性测试,则不能使用,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_attributedatetimeALTER TABLE

确定什么是“对”是特定于应用程序的;如果您使用了一致的命名方案,则应该很容易使用LIKEor~运算符和/或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因此在您COMMITROLLBACK.

如果您使用的是现代 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;

您可以在第二个会话中读取结果并将它们作为 SQL 命令发送,或者如果您想变得花哨,您可以编写一个相当简单的 PL/PgSQL 函数,该函数LOOP覆盖结果并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. 很可能你只是硬编码就完成了,在这种情况下,你可以删除该列。我把它放在那里,以防在你的应用程序中有一个很好的方法来解决它。

如果您遇到时间可以为空但日期不为空或反之亦然的情况,您需要将日期和时间包装在一个表达式中,以决定在为空时返回什么结果。nullif和函数对此coalesce很有用,就像CASE. 请记住,添加 null 和非 null 值会产生 null 结果,因此您可能不需要做任何特殊的事情。

如果您使用模式,您可能需要进一步细化查询以使用模式名称前缀的 %I 替换来消除歧义。如果您不使用模式(如果您不知道模式是什么,则不使用),那么这无关紧要。

完成此操作后,请考虑添加小于或等于在应用程序中有意义的位置CHECK强制执行的约束。另请查看文档中的排除约束。time1time2

于 2013-02-13T14:10:00.373 回答