我在Postgresql中有一个日期和时间字段。我正在python中阅读它,并且需要在特定时间过去的某些时间解决问题。
步骤基本上是这样的:
我知道通过迭代结果等等,我肯定有一些python方法可以做到这一点。我想知道是否有更好的方式比野蛮强迫这个?如果可能的话,我宁愿不运行多个查询或者必须在fetchall()中排序很多额外的结果。
答案 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_SCHEMA
或pg_catalog.pg_class
和pg_catalog.pg_attribute
以查找包含date
和time
列对的表,然后生成{{1命令统一它们。
确定“对”是什么是非常特定于应用程序;如果您使用了一致的命名方案,那么使用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
之前,其他任何内容都无法使用该表。
如果您使用的是模糊的现代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 ZONE
和WITHOUT TIME ZONE
函数对此非常有用,nullif
也是如此。请记住,添加null和非null值会产生null结果,因此您可能不需要执行任何特殊操作。
如果使用模式,则可能需要进一步优化查询以使用模式名称前缀的%I替换来消除歧义。如果你不使用模式(如果你不知道它是什么,你没有),那么这没关系。
考虑添加coalesce
约束,强制CASE
小于或等于CHECK
,一旦您完成此操作,它就会在您的应用程序中有意义。另请参阅文档中的排除约束。