损坏的替代方法 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 模式,我需要一个列表来显示每批物品在仓库中停留了多长时间(以及仍在仓库中的剩余物品的列表):

所有其他信息都可以很容易地加入到这里。

我需要在当前 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