无法在 plsql 过程中找出循环游标

Posted

技术标签:

【中文标题】无法在 plsql 过程中找出循环游标【英文标题】:Can't figure out cursor for loop in a plsql procedure 【发布时间】:2017-04-17 05:30:45 【问题描述】:

我正在尝试在 plsql 中编写一个接收两个参数 monthyear 的过程。该过程为一个表——loanreport 生成数据。该过程为贷款类型表中的所有贷款类型生成。当程序运行时,它应该在表格中填充:

    年份

    已关闭的贷款金额(状态为 6 的贷款的贷款金额之和)如果没有状态为 6 的贷款,则已关闭的贷款金额为 0。

    4.平均收盘周期

以下是相关表格:

CREATE TABLE LOANDETAILS 
(LOANNO VARCHAR2(11) primary key, 
    PROPERTYID VARCHAR2(10), 
    CUSTID CHAR(8), 
    LOANTYPE VARCHAR2(20), 
    LOANSTATUSCODE NUMBER(3,0), 
    LOANAMOUNT NUMBER(10,2), 
    RATE NUMBER(5,2), 
    LOANCREATIONDATE DATE, 
    LOANSTATUSDATE DATE,
    constraint loandet_prop_fk foreign key(PROPERTYID) references PROPERTIES(propertyid),
    constraint loandet_cust_fk foreign key(CUSTID) references customers(custid),
    constraint loandet_lt_fk foreign key (LOANTYPE) references loantypes(loantype)
   );
   --insert
Insert into LOANDETAILS values ('L1000000001','P1000001','C1000001','Conventional',1,87975,9,to_date('26-JUL-2016','DD-MON-YY'),to_date('02-AUG-2016','DD-MON-YYYY'));
Insert into LOANDETAILS values ('L1000000009','P1000009','C1000009','FHA',6,160055,4.5,to_date('30-NOV-2016','DD-MON-YYYY'),to_date('07-DEC-2016','DD-MON-YYYY'));
Insert into LOANDETAILS values ('L1000000010','P1000010','C1000010','VA',2,217600,7.5,to_date('30-NOV-2016','DD-MON-YYYY'),to_date('07-DEC-2016','DD-MON-YYYY'));


CREATE TABLE LOANTYPES
   (ltID char(5) constraint loantypes_pk primary key,
    loantype VARCHAR2(20) constraint loantypes_lt_unique UNIQUE, 
    description VARCHAR2(100),
    active char(1) constraint loantypes_active CHECK (active IN ('Y','N')) -- if loan type is currently being offered
    );
Insert into loantypes values ('LT001', 'VA', 'Service members, veterans or eligible family','Y');
Insert into loantypes values ('LT002', 'FHA', 'Federal Housing Administration eligible loans', 'Y');
Insert into loantypes values ('LT003', 'Conventional', 'Standard loan','Y');
Insert into loantypes values ('LT004', 'Employee', 'Eligible employees of the organization','Y');
Insert into loantypes values ('LT005', 'Reconstruct', 'Relief work reconstruction','N');

CREATE TABLE LOANTYPEREPORT 
   (LOANTYPE VARCHAR2(20), 
    MONTH number(2,0), 
    YEAR NUMBER(4,0),  
    CLOSEDLOANSAMOUNT NUMBER(15,2), 
    AVERAGECLOSINGPERIOD NUMBER(5,2),
    constraint loantr_pk PRIMARY KEY (LOANTYPE, RMONTH, RYEAR)
   );

我是 sql 新手,我显然有一些知识空白。我正在尝试创建一个过程,然后想创建一个游标并使用 for 循环遍历游标以创建所需的报告。这是我不完整的代码:

CREATE OR REPLACE PROCEDURE loan_type_report_procedure (Month loantypereport.month%type, Year loantypereport.year%type) AS

CURSOR C1 IS
SELECT l.loantype, 
       loanamount, 
       loancreationdate,  
       loanstatusdate,
       loanstatuscode,
       to_char(LOANCREATIONDATE, 'mm') AS rMonth, 
       to_char(LOANCREATIONDATE, 'YYYY') AS rYEAR
FROM LOANTYPES l
JOIN LOANDETAILS d
ON l.loantype = d.loantype
WHERE Month = to_char(LOANCREATIONDATE, 'mm') 
      AND Year = to_char(LOANCREATIONDATE, 'YYYY')

BEGIN
FOR loan_rec in C1 LOOP

据我了解,for 循环在游标中逐行运行。如果我想要一个包含贷款类型、月份、年份、已结贷款金额和平均结案期限的决赛桌贷款类型报告 - 我该如何进行?已结贷款金额和平均结账期限均在贷款类型分组中汇总。我会在游标选择语句中使用 group by 和 have 吗?

感谢您的见解

【问题讨论】:

你可以写一些类似下面的东西:INSERT INTO table_name SELECT l.loantype, SUM (CASE WHEN loanstatuscode = 6 THEN loanamount ELSE 0 END) loanamount, loancreationdate, loanstatusdate, loanstatuscode, to_char(LOANCREATIONDATE, 'mm') AS rMonth, to_char(LOANCREATIONDATE, 'YYYY') AS rYEAR FROM LOANTYPES l JOIN LOANDETAILS d ON l.loantype = d.loantype WHERE Month = to_char(LOANCREATIONDATE, 'mm') AND Year = to_char(LOANCREATIONDATE, ' YYYY') Mike 我不完全确定我是否理解您对持续时间的所有标准,但我举了几个例子。如果一个月内没有贷款结清,我留下了平均NULL。这是您对平均持续时间的想法吗? 【参考方案1】:

如果我正确理解您的问题,您想通过他们的LOANTYPE总结每个月的贷款数量,对于已关闭的贷款 (LOANSTATUSCODE = 6),您想@987654323 @他们的金额并记录他们的贷款时间跨度的AVERAGE。 从LOANTYPEREPORT 的外观来看,您似乎计划将这个平均跨度在几天内。

要做到这一点,您不需要使用 PL/SQL。这可以使用传统的 SQL 来完成。我只是猜测决定贷款期限的标准是什么,所以我将在下面概述一个示例,其中有几个变化。

在此示例中,我需要稍微修改您的表格,因为它们引用了您帖子中未包含的其他表格(我删除了 LOANDET_PROP_FKLOANDET_CUST_FK)。

创建示例表后,创建LOANTYPEREPORT 表(根据您的示例稍作修改以进行编译):

CREATE TABLE LOANTYPEREPORT
(LOANTYPE VARCHAR2(20),
 MONTH NUMBER(2,0),
 YEAR NUMBER(4,0),
 CLOSEDLOANSAMOUNT NUMBER(15,2),
 AVERAGECLOSINGPERIOD NUMBER(5,2),
  CONSTRAINT LOANTR_PK PRIMARY KEY (LOANTYPE, MONTH, YEAR)
);

此外,我为一些 FHA 和常规贷款添加了一些额外数据,以帮助区分下面示例中的 SUMs 和持续时间。

Insert into LOANDETAILS values ('L1000000001','P1000001','C1000001','Conventional',1,87975,9,to_date('26-JUL-2016','DD-MON-YY'),to_date('02-AUG-2016','DD-MON-YYYY'));
Insert into LOANDETAILS values ('L1000000009','P1000009','C1000009','FHA',6,160055,4.5,to_date('30-NOV-2016','DD-MON-YYYY'),to_date('07-DEC-2016','DD-MON-YYYY'));
Insert into LOANDETAILS values ('L1000000010','P1000010','C1000010','VA',2,217600,7.5,to_date('30-NOV-2016','DD-MON-YYYY'),to_date('07-DEC-2016','DD-MON-YYYY'));
Insert into LOANDETAILS values ('L1000000011','P1000010','C1000010','VA',6,217600,7.5,to_date('30-NOV-2016','DD-MON-YYYY'),to_date('07-DEC-2016','DD-MON-YYYY'));
Insert into LOANDETAILS values ('L1000000012','P1000010','C1000010','VA',6,111111,7.5,to_date('30-NOV-2016','DD-MON-YYYY'),to_date('17-DEC-2016','DD-MON-YYYY'));
Insert into LOANDETAILS values ('L1000000013','P1000010','C1000010','VA',2,222222,7.5,to_date('30-NOV-2016','DD-MON-YYYY'),to_date('27-DEC-2016','DD-MON-YYYY'));
Insert into LOANDETAILS values ('L1000000014','P1000010','C1000010','Conventional',6,333333,7.5,to_date('30-NOV-2016','DD-MON-YYYY'),to_date('27-JAN-2017','DD-MON-YYYY'));
Insert into LOANDETAILS values ('L1000000015','P1000010','C1000010','Conventional',5,333333,7.5,to_date('30-NOV-2016','DD-MON-YYYY'),to_date('27-FEB-2017','DD-MON-YYYY'));
Insert into LOANDETAILS values ('L1000000016','P1000010','C1000010','Conventional',4,333333,7.5,to_date('30-NOV-2016','DD-MON-YYYY'),to_date('27-MAR-2017','DD-MON-YYYY'));
Insert into LOANDETAILS values ('L1000000017','P1000010','C1000010','FHA',4,444444,7.5,to_date('30-NOV-2016','DD-MON-YYYY'),to_date('27-APR-2017','DD-MON-YYYY'));
Insert into LOANDETAILS values ('L1000000018','P1000010','C1000010','FHA',6,200000,7.5,to_date('30-NOV-2016','DD-MON-YYYY'),to_date('27-APR-2017','DD-MON-YYYY'));
Insert into LOANDETAILS values ('L1000000019','P1000010','C1000010','FHA',6,300000,7.5,to_date('30-NOV-2016','DD-MON-YYYY'),to_date('27-MAY-2017','DD-MON-YYYY'));
Insert into LOANDETAILS values ('L1000000020','P1000010','C1000010','FHA',6,300000,7.5,to_date('30-NOV-2016','DD-MON-YYYY'),to_date('22-MAY-2017','DD-MON-YYYY'));

然后,您可以加载报告表。示例 1 这假设只有已关闭的贷款应该包含在贷款期限平均值中,贷款期限在 LOANCREATIONDATE 之间和LOANSTATUSDATE,并且您只需要实际关闭贷款的月份和贷款类型的数据。这意味着 2016 年 7 月将完全不包括在内,因为该月没有关闭任何贷款。

INSERT INTO LOANTYPEREPORT
  SELECT
    LOANDETAILS.LOANTYPE,
    EXTRACT(MONTH FROM LOANDETAILS.LOANSTATUSDATE) AS MONTH,
    EXTRACT(YEAR FROM LOANDETAILS.LOANSTATUSDATE) AS YEAR,
    SUM(LOANDETAILS.LOANAMOUNT) AS CLOSED_LOAN_AMOUNT,
    AVG(LOANDETAILS.LOANSTATUSDATE - LOANDETAILS.LOANCREATIONDATE) AS AVERAGE_LOAN_DURATION
  FROM LOANDETAILS
    WHERE LOANDETAILS.LOANSTATUSCODE = 6
  GROUP BY
    LOANDETAILS.LOANTYPE,
    EXTRACT(YEAR FROM LOANDETAILS.LOANSTATUSDATE),
    EXTRACT(MONTH FROM LOANDETAILS.LOANSTATUSDATE);

然后看看它做了什么:

SELECT YEAR,MONTH,LOANTYPE,CLOSEDLOANSAMOUNT,AVERAGECLOSINGPERIOD
FROM LOANTYPEREPORT
ORDER BY YEAR, MONTH, LOANTYPE;


YEAR  MONTH  LOANTYPE      CLOSEDLOANSAMOUNT  AVERAGECLOSINGPERIOD  
2016  12     FHA           160055             7                     
2016  12     VA            328711             12                    
2017  1      Conventional  333333             58                    
2017  4      FHA           200000             148                   
2017  5      FHA           600000             175.5                 

示例 2: 但是,如果您想包含给定贷款类型的未关闭贷款的月份的数据(我不确定,但这可能是您帖子中的第三项) ,那么您需要列举月份。 您可以通过多种方式执行此操作,但我将在此示例中包含一个额外的查询,为 2016 - 2018 设置边界。

运行插入:

INSERT INTO LOANTYPEREPORT
  WITH YEAR_MONTH AS(
     SELECT THE_MONTH.MONTH_NUMBER,
       THE_YEAR.YEAR_NUMBER
     FROM
       (SELECT LEVEL AS MONTH_NUMBER FROM DUAL CONNECT BY LEVEL < 13) THE_MONTH
       CROSS JOIN
       (SELECT YEAR_NUMBER FROM
         (SELECT LEVEL AS YEAR_NUMBER FROM DUAL CONNECT BY LEVEL < 2019)
       WHERE YEAR_NUMBER BETWEEN 2016 AND 2018) THE_YEAR
  )
  SELECT
    LOANTYPES.LOANTYPE,
    YEAR_MONTH.MONTH_NUMBER,
    YEAR_MONTH.YEAR_NUMBER,
    SUM(COALESCE(CLOSED_LOAN.LOANAMOUNT,0)) AS CLOSED_LOAN_AMOUNT,
    AVG(CLOSED_LOAN.LOAN_DURATION) AS AVERAGE_LOAN_DURATION
  FROM
    YEAR_MONTH
    CROSS JOIN LOANTYPES
      LEFT OUTER JOIN (SELECT EXTRACT(MONTH FROM LOANDETAILS.LOANSTATUSDATE) AS MONTH,
                              EXTRACT(YEAR FROM LOANDETAILS.LOANSTATUSDATE) AS YEAR,
                         LOANDETAILS.LOANTYPE,
                         LOANDETAILS.LOANAMOUNT,
                         LOANDETAILS.LOANSTATUSDATE - LOANDETAILS.LOANCREATIONDATE AS LOAN_DURATION
                       FROM LOANDETAILS
  WHERE LOANDETAILS.LOANSTATUSCODE = 6) CLOSED_LOAN
    ON YEAR_MONTH.YEAR_NUMBER = CLOSED_LOAN.YEAR
AND YEAR_MONTH.MONTH_NUMBER = CLOSED_LOAN.MONTH
    AND LOANTYPES.LOANTYPE = CLOSED_LOAN.LOANTYPE
    GROUP BY YEAR_NUMBER, MONTH_NUMBER, LOANTYPES.LOANTYPE
ORDER BY YEAR_NUMBER, MONTH_NUMBER, LOANTYPES.LOANTYPE;

并查看生成了什么数据:

YEAR  MONTH  LOANTYPE      CLOSEDLOANSAMOUNT  AVERAGECLOSINGPERIOD  
2016  1      Conventional  0                                        
2016  1      Employee      0                                        
2016  1      FHA           0                                        
2016  1      Reconstruct   0                                        
2016  1      VA            0                                        
2016  2      Conventional  0                                        
...
...
...
2016  12     Conventional  0                                        
2016  12     Employee      0                                        
2016  12     FHA           160055             7                     
2016  12     Reconstruct   0                                        
2016  12     VA            328711             12                    
2017  1      Conventional  333333             58                    
2017  1      Employee      0                                        
2017  1      FHA           0                      
2017  4      FHA           200000             148                   
2017  4      Reconstruct   0                                        
2017  4      VA            0                                  
2017  5      Conventional  0                                        
2017  5      Employee      0                                        
2017  5      FHA           600000             175.5                 
2017  5      Reconstruct   0                                        
2017  5      VA            0                                        
2017  6      Conventional  0                                        
2017  6      Employee      0                                        
2017  6      FHA           0                               

通过这种方式,您可以获得每个月关闭的每种类型贷款的关闭金额总和,如果没有关闭,则为零。

使用从函数返回的示例进行编辑。

重申一下,您不需要使用函数来执行此类报告。 但是如果你有使用函数的需求,这里有一个例子:

首先,创建你的返回类型:

CREATE TYPE LOAN_TYPE_MONTH_REPORT IS OBJECT (
  LOANTYPE VARCHAR2(20),
  MONTH number(2,0),
  YEAR NUMBER(4,0),
  CLOSEDLOANSAMOUNT NUMBER(15,2),
  AVERAGECLOSINGPERIOD NUMBER(5,2)
);
/

CREATE TYPE LOAN_TYPE_MONTH_REPORT_LIST IS TABLE OF LOAN_TYPE_MONTH_REPORT;
/

然后创建你的函数:

CREATE FUNCTION GET_LOAN_TYPE_REPORT_FOR_MONTH(P_YEAR IN NUMBER, P_MONTH IN NUMBER)
  RETURN LOAN_TYPE_MONTH_REPORT_LIST
  IS
  V_MONTH_REPORT LOAN_TYPE_MONTH_REPORT_LIST;
  BEGIN
    SELECT
      LOAN_TYPE_MONTH_REPORT(
         THE_MONTH_YEAR.LOANTYPE,
         THE_MONTH_YEAR.THE_MONTH,
         THE_MONTH_YEAR.THE_YEAR,
         COALESCE(CLOSED_LOAN_SUMMARY.CLOSED_LOAN_AMOUNT,0),
         CLOSED_LOAN_SUMMARY.AVERAGE_LOAN_DURATION)
    BULK COLLECT INTO V_MONTH_REPORT
    FROM
      (SELECT P_YEAR AS THE_YEAR, P_MONTH AS THE_MONTH, LOANTYPES.LOANTYPE FROM LOANTYPES) THE_MONTH_YEAR
      LEFT OUTER JOIN
      (SELECT
         LOANDETAILS.LOANTYPE,
         EXTRACT(MONTH FROM LOANDETAILS.LOANSTATUSDATE) AS THE_MONTH,
         EXTRACT(YEAR FROM LOANDETAILS.LOANSTATUSDATE) AS THE_YEAR,
         SUM(LOANDETAILS.LOANAMOUNT) AS CLOSED_LOAN_AMOUNT,
         AVG(LOANDETAILS.LOANSTATUSDATE - LOANDETAILS.LOANCREATIONDATE) AS AVERAGE_LOAN_DURATION
       FROM LOANDETAILS
       WHERE LOANDETAILS.LOANSTATUSCODE = 6
       GROUP BY
         LOANDETAILS.LOANTYPE,
         EXTRACT(YEAR FROM LOANDETAILS.LOANSTATUSDATE),
         EXTRACT(MONTH FROM LOANDETAILS.LOANSTATUSDATE)) CLOSED_LOAN_SUMMARY
        ON THE_MONTH_YEAR.THE_YEAR = CLOSED_LOAN_SUMMARY.THE_YEAR
           AND THE_MONTH_YEAR.THE_MONTH = CLOSED_LOAN_SUMMARY.THE_MONTH
           AND THE_MONTH_YEAR.LOANTYPE = CLOSED_LOAN_SUMMARY.LOANTYPE;
    RETURN V_MONTH_REPORT;
  END;
  /

然后测试它: 这是一个月有两种贷款类型的关闭:

SELECT * FROM TABLE(GET_LOAN_TYPE_REPORT_FOR_MONTH(2016,12));
LOANTYPE      MONTH  YEAR  CLOSEDLOANSAMOUNT  AVERAGECLOSINGPERIOD  
Employee      12     2016  0                                        
VA            12     2016  328711             12                    
Reconstruct   12     2016  0                                        
FHA           12     2016  160055             7                     
Conventional  12     2016  0                                      

或者一个月只有一种贷款类型并结束:

SELECT * FROM TABLE(GET_LOAN_TYPE_REPORT_FOR_MONTH(2017,01));
LOANTYPE      MONTH  YEAR  CLOSEDLOANSAMOUNT  AVERAGECLOSINGPERIOD  
Employee      1      2017  0                                        
Reconstruct   1      2017  0                                        
FHA           1      2017  0                                        
VA            1      2017  0                                        
Conventional  1      2017  333333             58                    

【讨论】:

嗨,亚历克斯,比你更详细的答案。感谢您抽出宝贵的时间,并且可以保证这有助于我了解 SQL。如果我想把它变成一个带有月份和年份输入的程序,可以输出报告,你对此有什么见解吗? 谢谢@Mike 当然我可以看看。关于目标的更多细节和遇到的任何问题都会有所帮助。您能告诉我,您希望该程序如何处理输入吗?您是否正在寻找仅加载该月-年的报告数据或该月-年的所有历史记录的过程?是否要求单个过程加载表并返回数据,或者加载和查询应该是两个单独的过程?谢谢 嗨@Alex,这将是一个以月份和年份作为输入的过程。它只返回该月和该年的报告。返回的表将包含月份、年份、已结清贷款金额和平均结清期。关闭的贷款金额和平均关闭期限仅适用于状态 = 6 的贷款。如果没有状态 = 6 的贷款,则关闭的贷款金额和平均关闭期限应为 0。我在上面的解决方案中遇到的问题是我不知道如何循环游标然后按贷款类型聚合数据 好的@Mike我会用一个例子更新我的帖子,让你知道。 @Mike 我添加了一个从函数返回单月报告的示例。正如我在原始帖子中指出的那样,您不需要 pl/sql 来进行这种报告,但希望这可以为您提供一个示例,如果您需要在您的情况下使用 pl/sql。这能回答你的问题吗?

以上是关于无法在 plsql 过程中找出循环游标的主要内容,如果未能解决你的问题,请参考以下文章

循环遍历带有更新字段条件的游标 [PLSQL]

Oracle-4 - :超级适合初学者的入门级笔记:plsql,基本语法,记录类型,循环,游标,异常处理,存储过程,存储函数,触发器

Oracle存储过程游标for循环怎么写

oracle 执行存储过程 无法中断 但是是循环执行 怎么办

oracle(sql)基础篇系列——PLSQL游标存储过程触发器

Oracle存储过程游标for循环怎么写