损坏的 PL/ruby 的替代方案:转换仓库日志表
Posted
技术标签:
【中文标题】损坏的 PL/ruby 的替代方案:转换仓库日志表【英文标题】:Alternatives to broken PL/ruby: convert a warehouse journal table 【发布时间】:2016-04-14 01:12:03 【问题描述】:升级 Postgres 8.4 -> 9.3 和 Ruby 1.8 -> 2.1 后,PL/ruby 无法运行。在第一次执行任何 PL/ruby 函数时,我会立即获得 Postgres 服务器核心转储。 我正在分析堆栈跟踪,但看起来不太好。还有,PL/ruby的维护状态也不好。
因此,将焦点转移到我使用 PL/ruby 解决的实际数据库问题并考虑替代方案。
问题的简化示例: 给定一个仓库日志作为具有以下字段的数据库表:
日期(日期) 商品类型(外键) 计数(数字)考虑到仓库在严格的 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:这是您需要的数据:
输入表:
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
【问题讨论】:
plv8 或 plcoffee 对您来说可能更容易使用(或者至少更熟悉),它们甚至似乎得到了维护:pgxn.org/dist/plv8 Postgres 的 V8 javascript 引擎可能是您最好的计划。 PL/SQL 粗糙且丑陋,但得到很好的支持。您也可以通过 Postgres 数据库连接在外部的 Ruby 中执行此操作。I consider it impossible to solve this with plain SQL query language
。我不。现代 SQL 可能可以做到这一点。提供您的实际表定义(显示数据类型和约束的完整 CREATE TABLE
语句)、一小组示例数据、所需结果和您的实际 Postgres 版本。 (为什么是 Postgres 9.3?如果你升级,升级到 Postgres 9.5!)如果你真的需要过程元素,切换到 PL/pgSQL。
2009
在你的结果是一个错字,我猜?另外,我只有在您@-notify me in a comment时才会收到通知。
【参考方案1】:
让我们从您的选择开始:
pl/pgsql 和 sql pl/perl、pl/pythonu 和 pl/tcl 其他请这些主要类别各有优劣。他们在你做事的方式上也有不同。像 pl/ruby 这样的外部 pls 的一大弱点是,如果它们无法维护,您以后可能会遇到问题。
PL/PGSQL 和 SQL
在这些情况下,您可能可以将您的更改表示为带有递归公用表表达式的 SQL 查询。然后你可以使用 sql,或者,如果你需要一些轻微的程序支持,添加它并使用 pl/pgsql。我通常是这样处理的。
PL/Perl、PL/TCL 和 PL/PythonU
您还可以将 Ruby 代码移植到 Python 或 Perl 并使用这些语言的 PL 变体。这些 PL 作为 PostgreSQL 核心发行版的一部分被广泛使用和维护。他们不会消失。这将使您更好地了解逻辑如何移动。
PL/Python 的一个重要限制是它没有受信任模式,而使用 pl/perl 会遇到的一个问题是受信任模式意味着无法访问外部模块。
【讨论】:
【参考方案2】:纯 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;
假设基础表是一致的:不能扣除比以前添加的更多的项目。即,输出总和 name_id 的输入总和。
使用 Postgres 9.3 测试。准确地产生你的结果。并且应该表现得体。
使用range types 和range operators 来简化任务。
SQL Fiddle 带有扩展数据以显示极端情况。
带有两个游标的 PL/pgSQL 函数
我希望这种方法明显更快,但是:并行运行两个游标,一个在输入列上,另一个在输出列上。所以我们只走过桌子一次。
这个相关答案实现了基本逻辑(“FNC - Function”章节):
Window Functions or Common Table Expressions: count previous rows within range【讨论】:
天哪!这太棒了!我还不明白,但我在完整数据上运行它,它似乎提供了正确的结果。从来没有想过这是可能的。而且您正在使用我不知道的构造。 (可能是在 2008 年,当我编写 ruby 代码时,它们还没有出现?) @PMc: 2008,那将是 Postgres 8.3。还没有 CTE,没有窗口函数,没有范围类型/运算符。该查询本来是可能的,但非常冗长、笨拙且缓慢。与您之前的解决方案相比,查询的执行情况如何? (如果性能很重要,请查看我的第二个建议。) 性能 - 我只能说 ruby 代码似乎更慢。这运行在大约 1000 条记录上,所以我看到的是一些 ms,它可能更能反映机器的情绪。此外,这只是例程的逻辑核心——需要更多时间的是外围任务,比如加入报价(这篇文章确实计算了一些公司股票投资的税收和业绩——还有处理股票分割事件的任务——有时,并使用您向我展示的工具,我会考虑一下。) @PMc:只要一千行,别无所求。这应该足够好了。玩你的新玩具! :)以上是关于损坏的 PL/ruby 的替代方案:转换仓库日志表的主要内容,如果未能解决你的问题,请参考以下文章