4

我有一个包含 2002 年每一天的数据的表格,但它有一些缺失的日期。即,2002 年有 354 条记录(而不是 365 条)。对于我的计算,我需要在表中包含 Null 值的缺失数据

+-----+------------+------------+
| ID  |  rainfall  | date       |
+-----+------------+------------+
| 100 |  110.2     | 2002-05-06 |
| 101 |  56.6      | 2002-05-07 |
| 102 |  65.6      | 2002-05-09 |
| 103 |  75.9      | 2002-05-10 |
+-----+------------+------------+

你看到 2002-05-08 不见了。我希望我的决赛桌是这样的:

+-----+------------+------------+
| ID  |  rainfall  | date       |
+-----+------------+------------+
| 100 |  110.2     | 2002-05-06 |
| 101 |  56.6      | 2002-05-07 |
| 102 |            | 2002-05-08 |
| 103 |  65.6      | 2002-05-09 |
| 104 |  75.9      | 2002-05-10 |
+-----+------------+------------+

有没有办法在 PostgreSQL 中做到这一点?

我是否将结果作为查询结果(不一定是更新的表)并不重要

4

4 回答 4

9

date是标准 SQL 中的保留字,也是 PostgreSQL 中数据类型的名称。PostgreSQL 允许它作为标识符,但这并不是一个好主意。我thedate改为用作列名。

不要依赖代理 ID 中没有空白。这几乎总是一个坏主意。将这样的 ID 视为没有意义的唯一编号,即使它在大多数情况下似乎带有某些其他属性。

在这种特殊情况下,正如@Clodoaldo 所评论的那样,它thedate似乎是一个完美的主键,并且该列id只是杂乱无章 - 我将其删除:

CREATE TEMP TABLE tbl (thedate date PRIMARY KEY, rainfall numeric);
INSERT INTO tbl(thedate, rainfall) VALUES
  ('2002-05-06', 110.2)
, ('2002-05-07', 56.6)
, ('2002-05-09', 65.6)
, ('2002-05-10', 75.9);

询问

查询全表:

SELECT x.thedate, t.rainfall  -- rainfall automatically NULL for missing rows
FROM (
   SELECT generate_series(min(thedate), max(thedate), '1d')::date AS thedate
   FROM   tbl
   ) x
LEFT   JOIN tbl t USING (thedate)
ORDER  BY x.thedate

类似于@a_horse_with_no_name发布的内容,但简化并忽略了pruned id

填补表格中第一个和最后一个日期之间的空白。如果可能存在领先/落后差距,请相应扩展。您可以date_trunc()@Clodoaldo演示的那样使用 - 但他的查询存在语法错误并且可以更简单。

插入缺失的行

最快和最易读的方法是NOT EXISTS反半连接。

INSERT INTO tbl (thedate, rainfall)
SELECT x.thedate, NULL
FROM (
   SELECT generate_series(min(thedate), max(thedate), '1d')::date AS thedate
   FROM   tbl
   ) x
WHERE NOT EXISTS (SELECT 1 FROM tbl t WHERE t.thedate = x.thedate)
于 2012-10-28T05:16:11.163 回答
8

只需对返回 2002 年所有日期的查询进行外连接:

with all_dates as (
  select date '2002-01-01' + i as date_col
  from generate_series(0, extract(doy from date '2002-12-31')::int - 1) as i
)
select row_number() over (order by ad.date_col) as id, 
       t.rainfall,
       ad.date_col as date
from all_dates ad
  left join your_table t on ad.date_col = t.date
order by ad.date_col;

这不会改变您的表格,它只会产生所需的结果。

请注意,生成的 id 列不会包含与表中的 ID 列相同的值,因为它只是结果集中的一个计数器。

您也可以将row_number()功能替换为extract(doy from ad.date_col)

于 2012-10-27T13:48:30.490 回答
4

填补空白。这不会重新排序 ID:

insert into t (rainfall, "date") values
select null, "date"
from (
    select d::date as "date"
    from (
        t
        right join
        generate_series(
            (select date_trunc('year', min("date")) from t)::timestamp,
            (select max("date") from t),
            '1 day'
        ) s(d) on t."date" = s.d::date
    where t."date" is null
    ) q
) s
于 2012-10-27T13:47:30.323 回答
1

您必须完全重新创建表,因为索引必须更改。

更好的方法是使用您喜欢的 dbi 语言,创建一个忽略 ID 的循环并将值放入具有新序列化 ID 的新表中。

for day in (whole needed calendar)
    value = select rainfall from oldbrokentable where date = day
    insert into newcleanedtable date=day, rainfall=value, id=serialized

(这不是真正的代码!只是概念性的,以适应您喜欢的脚本语言)

于 2012-10-27T13:24:21.583 回答