损坏的 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 的替代方案:转换仓库日志表的主要内容,如果未能解决你的问题,请参考以下文章

使用损坏的外部参照表修复 pdf

怎样修复损坏了的innodb 表

一次服务器断电,造成innodb引擎表(日志表)损坏的解决办法

工作日志当前库面临的问题

如何阻止谷歌云 sql 损坏数据库表

数据仓库中事实表的复合索引 - 数据集市