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

时间:2013-02-13 13:46:15

标签: python postgresql

我在Postgresql中有一个日期和时间字段。我正在python中阅读它,并且需要在特定时间过去的某些时间解决问题。

步骤基本上是这样的:

  1. 从x中选择*,其中日期> monthdayyear
  2. 在该子集中,仅选择>该日期的时间
  3. AND date2必须是< monthdayyear2 AND time2必须小于该日期的time2
  4. 我知道通过迭代结果等等,我肯定有一些python方法可以做到这一点。我想知道是否有更好的方式比野蛮强迫这个?如果可能的话,我宁愿不运行多个查询或者必须在fetchall()中排序很多额外的结果。

1 个答案:

答案 0 :(得分: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_SCHEMApg_catalog.pg_classpg_catalog.pg_attribute以查找包含datetime列对的表,然后生成{{1命令统一它们。

确定“对”是什么是非常特定于应用程序;如果您使用了一致的命名方案,那么使用ALTER TABLELIKE运算符和/或~应该很容易。您想要生成一组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开始对桌面进行独占锁定,因此在您ALTERCOMMIT之前,其他任何内容都无法使用该表。

如果您使用的是模糊的现代PostgreSQL,则可以使用the format function生成SQL;在旧版本上,您可以使用字符串连接(ROLLBACK)和||功能。例如:

给出样本数据:

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

你可以在第二个会话中读取结果并将它们作为SQL命令发送,或者如果你想得到想象,你可以写一个相当简单的PL / PgSQL函数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; 对结果和{{1每一个。查询产生如下输出:

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; 。你很可能只是硬编码,在这种情况下你可以删除该列。我把它放在那里,以防有一个很好的方法来解决你的应用程序。

如果您的时间可以为null但日期为非null,反之亦然,则需要将日期和时间包装在表达式中,以确定null时返回的结果。 WITH TIME ZONEWITHOUT TIME ZONE函数对此非常有用,nullif也是如此。请记住,添加null和非null值会产生null结果,因此您可能不需要执行任何特殊操作。

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

考虑添加coalesce约束,强制CASE小于或等于CHECK,一旦您完成此操作,它就会在您的应用程序中有意义。另请参阅文档中的排除约束。