在锁步中取消嵌套存储为文本的多个数组列
Posted
技术标签:
【中文标题】在锁步中取消嵌套存储为文本的多个数组列【英文标题】:Unnest multiple array columns stored as text in lockstep 【发布时间】:2021-09-29 21:52:26 【问题描述】:我在 Postgres 9.6 中有下表:
CREATE TABLE some_tbl(
target_id integer NOT NULL
, machine_id integer NOT NULL
, dateread timestamp without time zone NOT NULL
, state text
, ftime text
, CONSTRAINT pk_sometable PRIMARY KEY (target_id, machine_id, dateread)
);
使用如下数据:
targetID | MachineID | DateRead | State | FTime |
---|---|---|---|---|
60000 | 30 | '2021-09-29 15:20:00' | '0|1|0' | '850|930|32000' |
60000 | 31 | '2021-09-29 16:35:13' | '0|0|0' | '980|1050|30000' |
重要的部分是state
和ftime
。我需要取消嵌套元素并保持它们的顺序。这会生成步骤。
例如,第一行将是:
targetID | MachineID | DateRead | State | FTime | Step |
---|---|---|---|---|---|
60000 | 30 | '2021-09-29 15:20:00' | '0' | '850' | 0 |
60000 | 30 | '2021-09-29 15:20:00' | '1' | '930' | 1 |
60000 | 30 | '2021-09-29 15:20:00' | '0' | '32000' | 2 |
顺序很重要,因为 FTIME 850 ms 始终是第一个并在 STEP 中获得值 0,然后是 930 ms 是第二个并获得第 1 步,最后 32000 ms 是第三个并获得第 2 步。
目前,我首先使用string_to_array()
将文本转换为数组,然后使用unnnest()
,最后使用row_number()
分配步骤编号来解决此问题。
这项工作非常出色 - 除了有时某些索引出现乱序。第一行是这样的:
targetID | MachineID | DateRead | State | Ftime | Step |
---|---|---|---|---|---|
60000 | 30 | '2021-09-29 15:20:00' | '1' | '930' | 0 |
60000 | 30 | '2021-09-29 15:20:00' | '0' | '32000' | 1 |
60000 | 30 | '2021-09-29 15:20:00' | '0' | '850' | 2 |
我对数千条记录执行此操作,实际上一切正常,但后来我必须进行统计,需要获取最小值、最大值、平均值并得到错误的值,所以我检查并看到索引是否错误(我移动统计有一个庞大的 ETL 过程),但如果我执行选择检查有错误的特定行,它显示完美。所以我假设 row_number 有时会出现索引问题,这是非常随机的。
这是我使用的 SQL:
SELECT foo.target_id,
dateread,
foo.machine_id,
foo.state,
foo.ftime::integer,
(row_number() OVER (PARTITION BY foo.dateread, foo.machine_id, foo.target_id)) - 1 AS step
FROM ( SELECT target_id,
machine_id,
dateread
unnest(string_to_array(state, '|'::text))::integer AS state,
unnest(string_to_array(ftime, '|'::text))::integer AS tiempo
FROM some_table
WHERE target_id IN (6000) AND dateread = '2021-06-09')foo
有更好的方法吗?
【问题讨论】:
请提供精确的表定义(CREATE TABLE
显示数据类型和约束的语句)和 Postgres 版本。
嗨,这是基表:'CREATE TABLE schema.sometable ( target_id integer NOT NULL, machine_id integer NOT NULL, dateread timestamp without time zone NOT NULL, state text, ftime text, CONSTRAINT pk_sometable PRIMARY KEY (target_id, machine_id, dateread)) WITH (OIDS=FALSE); ALTER TABLE schema.sometable OWNER TO postgres;'而且我正在使用 Postgres 9.6。谢谢@ErwinBrandstetter
Edit 将表定义放入问题中,请勿放入评论中。
好的,我做到了。 ty
【参考方案1】:
一种优雅的方法是在LATERAL
子查询中对多个输入数组使用unnest()
的特殊实现并附加WITH ORDINALITY
:
SELECT t.target_id, t.dateread, t.machine_id, u.state, u.tiempo
, ord - 1 AS step
FROM tbl t
LEFT JOIN LATERAL unnest(string_to_array(state, '|')::int[]
, string_to_array(ftime, '|')::int[]) WITH ORDINALITY AS u(state, tiempo, ord) ON true
WHERE target_id = 60000
AND dateread = '2021-09-29 15:20:00' -- adapted
ORDER BY t.target_id, t.dateread, t.machine_id, step;
db小提琴here
由于state
和ftime
可以是NULL
,我使用LEFT JOIN ... ON true
将这些行保留在结果中。
见:
What is the difference between LATERAL JOIN and a subquery in PostgreSQL? Unnest multiple arrays in parallel PostgreSQL unnest() with element number What is the expected behaviour for multiple set-returning functions in SELECT clause?当然,你真正应该做的是这样的:
-
与设计数据库的人解除好友关系。 (PC 版我的真实建议。)
安装当前的 Postgres 版本。见:https://www.postgresql.org/support/versioning/
使用适当的关系设计创建一个新数据库。
迁移您的数据。 (并保留原始文件的备份以确保安全。)
烧掉旧数据库,再也不提了。
现代 Postgres 中适当的(规范化)关系设计可能如下所示:
CREATE TABLE tbl (
tbl_id int GENERATED ALWAYS AS IDENTITY PRIMARY KEY
, target_id integer NOT NULL
, machine_id integer NOT NULL
, read_timestamp timestamp with time zone NOT NULL
, CONSTRAINT tbl_uni UNIQUE (target_id, machine_id, read_timestamp)
);
CREATE TABLE tbl_step (
tbl_id int REFERENCES tbl ON DELETE CASCADE
, step int NOT NULL
, state int NOT NULL
, tiempo int NOT NULL
, CONSTRAINT tbl_step_pkey PRIMARY KEY (tbl_id, step)
);
那么你的查询就是:
SELECT *
FROM tbl
LEFT JOIN tbl_step USING (tbl_id);
【讨论】:
非常感谢 Erwin,我正在检查您的 sql 并且效果很好,甚至比较前后的大量负载并发现超过 30k 的差异。我一直在检查差异,到目前为止,一切都很完美。真的非常感谢大师。周末愉快。以上是关于在锁步中取消嵌套存储为文本的多个数组列的主要内容,如果未能解决你的问题,请参考以下文章
BigQuery - 如何取消嵌套多个数组,并从一列分配值?