python - 选择两个日期之间的日期,同时考虑单独的时间字段

标签 python postgresql

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

步骤基本上是这样的:

  1. Select * from x where date > monthdayyear
  2. 在那个子集中,只选择那些 > 给定日期的时间
  3. 日期 2 必须 < 月日年 2 并且时间 2 必须小于该日期给出的时间 2

我知道肯定有一些 python 方法可以通过遍历结果等来做到这一点。我想知道是否有比暴力破解更好的方法?如果可能的话,我宁愿不运行多个查询或者不得不在 fetchall() 中整理出很多额外的结果。

最佳答案

如果我理解您的设计,这确实是一个架构设计问题。而不是:

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运算符(operator)。而是写:

SELECT ...
FROM sometable
WHERE '2012-01-01 11:15 +0800' > (date1 + time1)
  AND '2012-01-01 11:15 +0800' < (date2 + time2);

自动化架构更改

可以自动更改架构。

您想查询INFORMATION_SCHEMApg_catalog.pg_classpg_catalog.pg_attribute对于具有 date 对的表和 time列,然后生成 ALTER TABLE 的集合命令来统一它们。

确定什么是“对”是非常具体的应用程序;如果您使用了一致的命名方案,那么使用 LIKE 应该很容易。或 ~运营商和/或 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,您可以使用 the format function 生成 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');

这是一个生成输入数据集的查询。请注意,它依赖于匹配列对总是有一个公共(public)名称的命名约定,一旦有 datetime单词从列中删除。您可以通过测试 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;

您可以读取结果并在第二个 session 中将它们作为 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 ZONEWITHOUT TIME ZONE .您很可能只是对其进行硬编码,在这种情况下,您只需删除该列即可。我把它放在那里,以防在你的应用程序中有一个很好的方法来解决它。

如果您遇到时间可以为 null 但日期不为 null 或反之亦然的情况,您需要将日期和时间包装在一个表达式中,该表达式决定在 null 时返回什么结果。 nullifcoalesce功能对此很有用,因为 CASE .请记住,将 null 值和非 null 值相加会产生 null 结果,因此您可能不需要执行任何特殊操作。

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

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

关于python - 选择两个日期之间的日期,同时考虑单独的时间字段,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/14855014/

相关文章:

python - 如何避免 ProgrammingError : can't adapt type 'DateTimeRangeField' when saving a Django model instance to a remote database?

python - 更好的 python 逻辑可以防止在嵌套循环中比较数组时超时

python - 对使用 Celery 感兴趣的新程序员,这是正确的方法吗

python - 使用 selenium 和 python 在 Chromedriver 中保存扩展设置

postgresql - 将 bigint 转换为 bytea,但交换字节顺序

string - Postgresql COPY 空字符串为 NULL 不起作用

python - 空值和排序

python - 将 __builtins__ 恢复为默认值

ruby-on-rails - ActiveRecord、Postgres 和分区表

postgresql - SQL 工作台 + PostgreSQL : how to show execution plans?