如何在 PL/pgSQL 中的动态选择查询中使用迭代器变量?

Posted

技术标签:

【中文标题】如何在 PL/pgSQL 中的动态选择查询中使用迭代器变量?【英文标题】:How to work with a iterator variable on a dynamic select query in PL/pgSQL? 【发布时间】:2014-11-18 11:10:36 【问题描述】:

我需要在 PostgreSQL 9.1 中提供现金流量报告。

在余额行 (BalanceLine) 中,一个函数 (getBalanceLine) 负责获取银行账户的总和及其贷方或借方头寸。

数字行 (num_row) 需要减少到零,以向函数 getBalanceLine 指示临时表必须是 DROPed。该临时表将平衡线结果保存为计算的“工作内存”。

问题是函数getBalanceLine 上的num_row 变量没有按照我的LOOP 子句中的说明递减。

以下是PL/pgSQL函数:

DROP FUNCTION IF EXISTS report_cash_flow();

CREATE FUNCTION report_cash_flow() RETURNS TABLE(Date date, Company varchar(128), Bank varchar(64), Partner varchar(128), Document varchar(64), Credit numeric, Debit numeric, Line integer, BalanceLine numeric) AS 
$BODY$

DECLARE
    num_row int := 0;

BEGIN

    num_row = ( SELECT
                COUNT(*)
            FROM
                account_move_line aml
                INNER JOIN  res_company rc ON rc.id = aml.company_id
                INNER JOIN  res_partner rp ON rp.id = aml.partner_id
                INNER JOIN  account_journal aj ON aj.id = aml.journal_id
                INNER JOIN  account_account aa ON aa.id = aml.account_id
            WHERE
                aml.state = 'valid'
                AND aml.reconcile_id IS NULL );


    FOR Date, Company, Bank, Partner, Document, Credit, Debit, Line, BalanceLine IN

        SELECT
            aml.date_maturity AS Date,
            rc.name AS Company,
            aj.name AS Bank,
            rp.name AS Partner,
            aml.name AS Document,
            aml.credit AS Credit,
            aml.debit AS Debit,
            num_row AS Line,
            getBalanceLine(aml.credit, aml.debit, num_row) AS BalanceLine
        FROM
            account_move_line aml
            INNER JOIN  res_company rc ON rc.id = aml.company_id
            INNER JOIN  res_partner rp ON rp.id = aml.partner_id
            INNER JOIN  account_journal aj ON aj.id = aml.journal_id
            INNER JOIN  account_account aa ON aa.id = aml.account_id
        WHERE
            aml.state = 'valid'
            AND aml.reconcile_id IS NULL
        ORDER BY
            Document

        LOOP
            num_row := num_row - 1;
            RAISE NOTICE '%', num_row;
            RETURN NEXT;

        END LOOP;

    RETURN;

END;
$BODY$
LANGUAGE 'plpgsql';

SELECT * FROM report_cash_flow();

查询结果如下:

date        company     bank    partner     document    credit  debit     line  balanceline
01/10/2013  Company 1   Bank 1  Partner 1   00003621/1  0.00    520.56    4       1.024,00
01/10/2013  Company 1   Bank 2  Partner 2   00003622/1  32.00   0.00      4         922,00
09/10/2014  Company 1   Bank 1  Partner 3   00003623/1  0.00    18009.65  4     -17.087,65
10/10/2014  Company 1   Bank 2  Partner 4   00003624/1  6126.95 0.00      4     -10.960,70

以下是RAISE NOTICE '%', num_row的结果:

NOTICE:  4
NOTICE:  3
NOTICE:  2
NOTICE:  1

【问题讨论】:

【参考方案1】:

为您的FOR 循环提供行的查询在循环的第一次迭代之前执行一次。您必须在循环中调用函数 getBalanceLine(),而不是在基本查询中。

但是,您的整个方法是不必要的冗长和昂贵的。

简化,步骤 1

CREATE FUNCTION report_cash_flow()
  RETURNS TABLE(Date date, Company text, Bank text, Partner text, Document text, Credit numeric, Debit numeric, Line integer, BalanceLine numeric) AS 
$func$
DECLARE
    _iterator int := 0;
BEGIN
    FOR Date, Company, Bank, Partner, Document, Credit, Debit, Line IN
        SELECT  aml.date_maturity AS Date -- alias useless
                rc.name AS Company,
                aj.name AS Bank,
                rp.name AS Partner,
                aml.name AS Document,
                aml.credit AS Credit,
                aml.debit AS Debit,
                count(*) OVER () AS Line  -- = initial num_row
        FROM    account_move_line aml
        JOIN    res_company       rc ON rc.id = aml.company_id
        JOIN    res_partner       rp ON rp.id = aml.partner_id
        JOIN    account_journal   aj ON aj.id = aml.journal_id
        JOIN    account_account   aa ON aa.id = aml.account_id
        WHERE   aml.state = 'valid'
        AND     aml.reconcile_id IS NULL
        ORDER   BY Document
    LOOP
        BalanceLine := getBalanceLine(Credit, Debit, Line - _iterator);
        RETURN NEXT;
        _iterator := _iterator + 1;
        RAISE NOTICE '%', Line - _iterator;         
    END LOOP;
    RETURN;
END
$func$ LANGUAGE plpgsql;
循环执行函数。 无需为计数运行第二个查询。您可以使用窗口函数在单个 SELECT 中执行此操作。 引入一个额外的变量来计数循环,在我的例子中是_iterator。 我在变量前面加上 _ 以避免命名冲突。 不要引用语言名称plpgsql,它是一个标识符。

简化,第 2 步

您或许可以将这个简单的查询与window functions 一起使用:

SELECT aml.date_maturity AS Date
     , rc.name AS Company
     , aj.name AS Bank
     , rp.name AS Partner
     , aml.name AS Document
     , aml.credit
     , aml.debit
     , count(*) OVER () AS Line  -- or the decreasing number?
     , getBalanceLine(aml.credit
                    , aml.debit
                    , count(*) OVER () + 1 -
                      row_number() OVER (ORDER  BY Document)) AS BalanceLine 
FROM   account_move_line aml
JOIN   res_company       rc ON rc.id = aml.company_id
JOIN   res_partner       rp ON rp.id = aml.partner_id
JOIN   account_journal   aj ON aj.id = aml.journal_id
JOIN   account_account   aa ON aa.id = aml.account_id
WHERE  aml.state = 'valid'
AND    aml.reconcile_id IS NULL
ORDER  BY Document;

或者使用更复杂的:

count(*) OVER (ORDER BY Document
               ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)

更多解释:

How to use a ring data structure in window functions

【讨论】:

Erwin 我非常感谢您的帮助。诀窍是(在步骤 1 中)在 LOOP 子句上操作 BalanceLine。谢谢大家的解释。

以上是关于如何在 PL/pgSQL 中的动态选择查询中使用迭代器变量?的主要内容,如果未能解决你的问题,请参考以下文章

使用 PL/pgSQL 将查询结果存储在变量中

如何使用 pl/pgsql 将具有动态元素名称的 JSON 数据转换为行?

如何在 PL/pgSQL 中获取动态生成的字段名称的值

在 PL/PGSQL 动态 SQL 内部函数中引用局部变量

PL/pgSQL 从表中动态复制数据

在 PL/pgSQL 函数中使用变量