损坏的替代方法 PL/ruby:转换仓库日志 table
Alternatives to broken PL/ruby: convert a warehouse journal table
升级 Postgres 8.4 -> 9.3 和 Ruby 1.8 -> 2.1 后,PL/ruby 无法 运行。我在第一次执行任何 PL/ruby 函数时立即得到 Postgres 服务器核心转储。
我正在分析堆栈跟踪,但看起来不太好。还有,PL/ruby的维护状态看起来不太好。
因此,将注意力转移到我使用的实际数据库问题上 PL/ruby 并考虑替代方案。
问题的简化示例:
给定仓库日志作为数据库 table,其中包含以下字段:
- 日期(日期)
- 商品类型(外键)
- 计数(数字)
考虑到仓库 运行s 处于严格的 FIFO 模式,我需要一个列表来显示每批物品在仓库中停留了多长时间(以及仍在仓库中的剩余物品的列表):
- journal_recno_in(外键)
- journal_recno_out(外键)
- 计数(数字)
所有其他信息都可以很容易地加入到这里。
我需要在当前 SQL 查询中动态创建,以便包含最新数据;因此排除了外部程序。
我认为用简单的 SQL 查询语言不可能解决这个问题,因此过程语言似乎是唯一的选择。
我试过PL/pgSQL,这绝对是可以的,但它看起来很粗糙和丑陋。
现在我正在寻找痛苦最小的方法,考虑到未来的扩展。 Ruby 显然是我的最爱,因为这种语言似乎几乎在我的思维中自我编码。但是,如果 PL/ruby 不能形成稳定的行为(目前看来这需要大量额外的工作和学习),那是毫无意义的。
建议?我可能忽略了哪些事情?
附录:堆栈跟踪的结果
第一个问题是 PL/ruby 将 ruby SAFE_LEVEL
设置为 12,而 ruby 2.1 最多接受 3,否则会加注。这很容易纠正,然后就可以执行简单的功能。但是当执行 RETURNS SETOF
函数时,它再次崩溃,这次是在 ruby 库中的 rb_iterate()
附近。我在这里放弃了,结论是 PL/ruby 可能需要从头到尾查看(即 5000+ loc)。
@Erwin:这是您需要的数据:
输入table:
CREATE TABLE events (
id serial PRIMARY KEY,
datum date NOT NULL,
name_id integer,
count numeric(12,4),
created_at timestamp without time zone,
updated_at timestamp without time zone,
);
输出格式:
SELECT * FROM ev_fifo() AS (id_in int, id_out int,
datum_in date, datum_out date,
name_id int,
count numeric)
输入示例:
id | datum | name_id | count | created_at | updated_at
------+------------+---------+------------+---------------------+---------------------
1 | 23.04.2008 | 1 | 1.0000 | 23.04.2008 02:11:45 | 06.06.2008 02:11:45
2 | 28.04.2008 | 2 | 50.0000 | 29.04.2008 07:17:24 | 16.12.2008 04:32:43
3 | 03.07.2008 | 2 | 250.0000 | 21.07.2008 01:26:15 | 16.12.2008 04:36:20
4 | 03.07.2008 | 2 | -1.0000 | 21.07.2008 01:31:00 | 16.12.2008 04:37:22
5 | 03.07.2008 | 1 | -1.0000 | 21.07.2008 01:28:19 | 16.12.2008 04:36:50
6 | 04.07.2008 | 2 | -60.0000 | 21.07.2008 01:32:26 | 16.12.2008 04:37:50
期望的输出:
id_in | id_out | datum_in | datum_out | name_id | count
---------+----------+------------+-------------+----------+-----------
2 | 4 | 28.04.2008 | 03.07.2008 | 2 | 1.0000
1 | 5 | 23.04.2009 | 03.07.2008 | 1 | 1.0000
2 | 6 | 28.04.2008 | 04.07.2008 | 2 | 49.0000
3 | 6 | 03.07.2008 | 04.07.2008 | 2 | 11.0000
3 | NULL | 03.07.2008 | NULL | 2 | 239.0000
让我们从您的选择开始:
- pl/pgsql 和 sql
- pl/perl、pl/pythonu 和 pl/tcl
- 其他请
这些大类各有优缺点。他们在处理事情的方式上也有差异。 pl/ruby 等外部 pls 的一大弱点是,如果维护失败,您以后可能会遇到问题。
PL/PGSQL 和 SQL
在这些情况下,您可以将您的更改表达为具有递归通用 table 表达式的 SQL 查询。然后你可以使用 sql 或者,如果你需要一些轻微的程序支持,添加它并使用 pl/pgsql。这通常是我处理这个问题的方式。
PL/Perl、PL/TCL 和 PL/PythonU
您还可以将 Ruby 代码移植到 Python 或 Perl,并使用这些语言的 PL 变体。这些 PL 作为 PostgreSQL 核心发行版的一部分被广泛使用和维护。他们不会消失。这将使您更好地了解逻辑如何移动。
PL/Python 的一个重要限制是它没有可信模式,而 运行 使用 pl/perl 的一个问题是可信模式意味着无法访问外部模块。
纯SQL,单查询
作为概念证明,因为我有点挑战你,这个 SQL 查询就完成了所有工作:
WITH i AS ( -- input summed up
SELECT id_in, datum_in, name_id, count, numrange(sum - count, sum) AS rng
FROM (
SELECT id AS id_in, datum AS datum_in, name_id, count
, sum(count) OVER (PARTITION BY name_id ORDER BY datum, id) AS sum
FROM events
WHERE count > 0
) sub
)
, o AS ( -- output summed up
SELECT id_out, datum_out, name_id, count, numrange(sum + count, sum) AS rng
FROM (
SELECT id AS id_out, datum AS datum_out, name_id, count
, sum(count) OVER (PARTITION BY name_id ORDER BY datum, id) * -1 AS sum
FROM events
WHERE count < 0
) sub
UNION ALL -- add ghost range for items still in store
SELECT NULL AS id_out, NULL AS datum_out, name_id, sum_in - sum_out AS count
, numrange(sum_out, sum_in) AS rng
FROM (
SELECT name_id, sum(CASE WHEN count > 0 THEN count END) AS sum_in
, COALESCE(sum(CASE WHEN count < 0 THEN count END) * -1, 0) AS sum_out
FROM events
GROUP BY 1
) sub
WHERE sum_in > sum_out -- only where items are left
)
SELECT i.id_in, o.id_out, i.datum_in::text, datum_out::text, i.name_id
, upper(i.rng * o.rng) - lower(i.rng * o.rng) AS count -- range intersect operator *
FROM i
JOIN o USING (name_id)
WHERE i.rng && o.rng -- range overlaps operator &&
ORDER BY datum_out, id_out, datum_in, id_in;
假设基础table是一致的:扣除的项目不能超过之前添加的项目。即,输出总和 <= 每个 name_id
.
的输入总和
使用 Postgres 9.3 测试。准确地产生你的结果。并且应该表现得体。
与 range types and range operators 合作以简化任务。
SQL Fiddle 使用扩展数据显示极端情况。
PL/pgSQL 有两个游标的函数
我希望这种方法明显更快,但是:运行 两个并行的游标,一个在输入上,另一个在输出列上。所以我们只遍历 table 一次。
此相关回答实现了基本逻辑(章“FNC - 函数”):
- Window Functions or Common Table Expressions: count previous rows within range
升级 Postgres 8.4 -> 9.3 和 Ruby 1.8 -> 2.1 后,PL/ruby 无法 运行。我在第一次执行任何 PL/ruby 函数时立即得到 Postgres 服务器核心转储。 我正在分析堆栈跟踪,但看起来不太好。还有,PL/ruby的维护状态看起来不太好。
因此,将注意力转移到我使用的实际数据库问题上 PL/ruby 并考虑替代方案。
问题的简化示例: 给定仓库日志作为数据库 table,其中包含以下字段:
- 日期(日期)
- 商品类型(外键)
- 计数(数字)
考虑到仓库 运行s 处于严格的 FIFO 模式,我需要一个列表来显示每批物品在仓库中停留了多长时间(以及仍在仓库中的剩余物品的列表):
- journal_recno_in(外键)
- journal_recno_out(外键)
- 计数(数字)
所有其他信息都可以很容易地加入到这里。
我需要在当前 SQL 查询中动态创建,以便包含最新数据;因此排除了外部程序。 我认为用简单的 SQL 查询语言不可能解决这个问题,因此过程语言似乎是唯一的选择。
我试过PL/pgSQL,这绝对是可以的,但它看起来很粗糙和丑陋。
现在我正在寻找痛苦最小的方法,考虑到未来的扩展。 Ruby 显然是我的最爱,因为这种语言似乎几乎在我的思维中自我编码。但是,如果 PL/ruby 不能形成稳定的行为(目前看来这需要大量额外的工作和学习),那是毫无意义的。
建议?我可能忽略了哪些事情?
附录:堆栈跟踪的结果
第一个问题是 PL/ruby 将 ruby SAFE_LEVEL
设置为 12,而 ruby 2.1 最多接受 3,否则会加注。这很容易纠正,然后就可以执行简单的功能。但是当执行 RETURNS SETOF
函数时,它再次崩溃,这次是在 ruby 库中的 rb_iterate()
附近。我在这里放弃了,结论是 PL/ruby 可能需要从头到尾查看(即 5000+ loc)。
@Erwin:这是您需要的数据:
输入table:
CREATE TABLE events (
id serial PRIMARY KEY,
datum date NOT NULL,
name_id integer,
count numeric(12,4),
created_at timestamp without time zone,
updated_at timestamp without time zone,
);
输出格式:
SELECT * FROM ev_fifo() AS (id_in int, id_out int,
datum_in date, datum_out date,
name_id int,
count numeric)
输入示例:
id | datum | name_id | count | created_at | updated_at
------+------------+---------+------------+---------------------+---------------------
1 | 23.04.2008 | 1 | 1.0000 | 23.04.2008 02:11:45 | 06.06.2008 02:11:45
2 | 28.04.2008 | 2 | 50.0000 | 29.04.2008 07:17:24 | 16.12.2008 04:32:43
3 | 03.07.2008 | 2 | 250.0000 | 21.07.2008 01:26:15 | 16.12.2008 04:36:20
4 | 03.07.2008 | 2 | -1.0000 | 21.07.2008 01:31:00 | 16.12.2008 04:37:22
5 | 03.07.2008 | 1 | -1.0000 | 21.07.2008 01:28:19 | 16.12.2008 04:36:50
6 | 04.07.2008 | 2 | -60.0000 | 21.07.2008 01:32:26 | 16.12.2008 04:37:50
期望的输出:
id_in | id_out | datum_in | datum_out | name_id | count
---------+----------+------------+-------------+----------+-----------
2 | 4 | 28.04.2008 | 03.07.2008 | 2 | 1.0000
1 | 5 | 23.04.2009 | 03.07.2008 | 1 | 1.0000
2 | 6 | 28.04.2008 | 04.07.2008 | 2 | 49.0000
3 | 6 | 03.07.2008 | 04.07.2008 | 2 | 11.0000
3 | NULL | 03.07.2008 | NULL | 2 | 239.0000
让我们从您的选择开始:
- pl/pgsql 和 sql
- pl/perl、pl/pythonu 和 pl/tcl
- 其他请
这些大类各有优缺点。他们在处理事情的方式上也有差异。 pl/ruby 等外部 pls 的一大弱点是,如果维护失败,您以后可能会遇到问题。
PL/PGSQL 和 SQL
在这些情况下,您可以将您的更改表达为具有递归通用 table 表达式的 SQL 查询。然后你可以使用 sql 或者,如果你需要一些轻微的程序支持,添加它并使用 pl/pgsql。这通常是我处理这个问题的方式。
PL/Perl、PL/TCL 和 PL/PythonU
您还可以将 Ruby 代码移植到 Python 或 Perl,并使用这些语言的 PL 变体。这些 PL 作为 PostgreSQL 核心发行版的一部分被广泛使用和维护。他们不会消失。这将使您更好地了解逻辑如何移动。
PL/Python 的一个重要限制是它没有可信模式,而 运行 使用 pl/perl 的一个问题是可信模式意味着无法访问外部模块。
纯SQL,单查询
作为概念证明,因为我有点挑战你,这个 SQL 查询就完成了所有工作:
WITH i AS ( -- input summed up
SELECT id_in, datum_in, name_id, count, numrange(sum - count, sum) AS rng
FROM (
SELECT id AS id_in, datum AS datum_in, name_id, count
, sum(count) OVER (PARTITION BY name_id ORDER BY datum, id) AS sum
FROM events
WHERE count > 0
) sub
)
, o AS ( -- output summed up
SELECT id_out, datum_out, name_id, count, numrange(sum + count, sum) AS rng
FROM (
SELECT id AS id_out, datum AS datum_out, name_id, count
, sum(count) OVER (PARTITION BY name_id ORDER BY datum, id) * -1 AS sum
FROM events
WHERE count < 0
) sub
UNION ALL -- add ghost range for items still in store
SELECT NULL AS id_out, NULL AS datum_out, name_id, sum_in - sum_out AS count
, numrange(sum_out, sum_in) AS rng
FROM (
SELECT name_id, sum(CASE WHEN count > 0 THEN count END) AS sum_in
, COALESCE(sum(CASE WHEN count < 0 THEN count END) * -1, 0) AS sum_out
FROM events
GROUP BY 1
) sub
WHERE sum_in > sum_out -- only where items are left
)
SELECT i.id_in, o.id_out, i.datum_in::text, datum_out::text, i.name_id
, upper(i.rng * o.rng) - lower(i.rng * o.rng) AS count -- range intersect operator *
FROM i
JOIN o USING (name_id)
WHERE i.rng && o.rng -- range overlaps operator &&
ORDER BY datum_out, id_out, datum_in, id_in;
假设基础table是一致的:扣除的项目不能超过之前添加的项目。即,输出总和 <= 每个 name_id
.
使用 Postgres 9.3 测试。准确地产生你的结果。并且应该表现得体。
与 range types and range operators 合作以简化任务。
SQL Fiddle 使用扩展数据显示极端情况。
PL/pgSQL 有两个游标的函数
我希望这种方法明显更快,但是:运行 两个并行的游标,一个在输入上,另一个在输出列上。所以我们只遍历 table 一次。
此相关回答实现了基本逻辑(章“FNC - 函数”):
- Window Functions or Common Table Expressions: count previous rows within range