PLSQL - 一次外连接翻车的教训

近期写视图时用外连接翻了车,正好还原了曾经犯过的但没有及时记录的错误,现总结本文。

  • 需求简述:
  1. 写一个采购订单行信息的视图,其中包含一部分说明性弹性域字段
  2. 这里我们简化为两个弹性域字段,均为非必需字段,关联独立值集
  3. 为了适应更换值集的可能,应用弹性域段分配表灵活取值集
  4. 备注:OEBS采购订单行表PO_LINES_ALL,弹性域段信息表FND_DESCR_FLEX_COLUMN_USAGES,独立值集值视图FND_FLEX_VALUES_VL
  • 翻车经过:

由于弹性域字段是非必需的,POL表的ATTRIBUTE字段关联FFV时必然要用外连接,FFV的值集ID要从FCU表里取,所以我自然而然的把FCU表也外连接了进来,结果就翻车了

初版代码就像这样,先拿引入一个弹性域字段举例

SELECT pol.po_line_id, ffv2.flex_value_meaning project_no
  FROM po_lines_all                 pol
      ,fnd_descr_flex_column_usages fcu2
      ,fnd_flex_values_vl           ffv2
 WHERE pol.attribute2 = ffv2.flex_value(+)
   AND ffv2.flex_value_set_id = fcu2.flex_value_set_id(+)
   AND fcu2.descriptive_flexfield_name(+) = 'PO_LINES'
   AND fcu2.descriptive_flex_context_code(+) = 'Global Data Elements'
   AND fcu2.application_column_name(+) = 'ATTRIBUTE2';
  • 反思:

上面SQL翻车的现象就是,POL.ATTRIBUTE2有值的行大批量的重复了,说实话一开始我都看不出来到底是发生肾么事了

后来从外连接的定义入手,想到了翻车的原因

例如A表和B表外连接,其中A做“主表”,外连接起到的作用是:1、A表所有的行都要被选择出来;2、B表能关联到A表的行,就关联进结果集;3、对于关联不到B表的A表的行,也要被选到,只不过该行B表的字段用NULL填充

在这里,pol.attribute2 = ffv2.flex_value(+)这个外连接条件尚且问题不大,但是FCU表再外连接到FFV表就不妙了:宏观上看,连接的顺序变成了POL与FFV外连接后,又与FCU进行外连接

从微观上,我们举个简单的例子,POL某行ATTRIBUTE2值为60,FFV中有两个值集含有60这个独立值,第一步上POL这一行要和FFV中的这两行都关联到。

再假设这两个值集分别为M和N,M是弹性域引用的,而N不是。我们为了限制到M值集引入了FCU表,但是FCU表是被外连接进来的,所以说不论是M值集还是N值集都将被保留,FCU表起不到任何限制作用

  • 改正:

FFV表和FCU表在本例中不是相互独立的关系,事实上我们是想用FCU表的唯一行去限制FFV表的记录范围,所以两表密不可分,应视同一体。我想的办法是两表先写作内嵌视图,再与POL表外连接

SELECT pol.po_line_id
      ,ffv2.flex_value_meaning  project_no
      ,ffv10.flex_value_meaning product_line
  FROM po_lines_all pol
      ,(SELECT ffv.flex_value, ffv.flex_value_meaning
          FROM fnd_descr_flex_column_usages fcu, fnd_flex_values_vl ffv
         WHERE fcu.descriptive_flexfield_name = 'PO_LINES'
           AND fcu.descriptive_flex_context_code = 'Global Data Elements'
           AND fcu.application_column_name = 'ATTRIBUTE2'
           AND fcu.flex_value_set_id = ffv.flex_value_set_id) ffv2
      ,(SELECT ffv.flex_value, ffv.flex_value_meaning
          FROM fnd_descr_flex_column_usages fcu, fnd_flex_values_vl ffv
         WHERE fcu.descriptive_flexfield_name = 'PO_LINES'
           AND fcu.descriptive_flex_context_code = 'Global Data Elements'
           AND fcu.application_column_name = 'ATTRIBUTE10'
           AND fcu.flex_value_set_id = ffv.flex_value_set_id) ffv10
 WHERE pol.attribute2 = ffv2.flex_value(+)
   AND pol.attribute10 = ffv10.flex_value(+);

ANSI版本看起来可能更直观一点

原来翻车的SQL

SELECT pol.po_line_id, ffv2.flex_value_meaning project_no
  FROM po_lines_all pol
  LEFT JOIN fnd_flex_values_vl ffv2
    ON pol.attribute2 = ffv2.flex_value
  LEFT JOIN fnd_descr_flex_column_usages fcu2
    ON ffv2.flex_value_set_id = fcu2.flex_value_set_id
   AND fcu2.descriptive_flexfield_name = 'PO_LINES'
   AND fcu2.descriptive_flex_context_code = 'Global Data Elements'
   AND fcu2.application_column_name = 'ATTRIBUTE2';

更正后的SQL

SELECT pol.po_line_id, ffv2.flex_value_meaning project_no
  FROM po_lines_all pol
  LEFT JOIN (SELECT ffv.flex_value, ffv.flex_value_meaning
               FROM fnd_descr_flex_column_usages fcu
               JOIN fnd_flex_values_vl ffv
                 ON fcu.descriptive_flexfield_name = 'PO_LINES'
                AND fcu.descriptive_flex_context_code =
                    'Global Data Elements'
                AND fcu.application_column_name = 'ATTRIBUTE2'
                AND fcu.flex_value_set_id = ffv.flex_value_set_id) ffv2
    ON pol.attribute2 = ffv2.flex_value
  LEFT JOIN (SELECT ffv.flex_value, ffv.flex_value_meaning
               FROM fnd_descr_flex_column_usages fcu
               JOIN fnd_flex_values_vl ffv
                 ON fcu.descriptive_flexfield_name = 'PO_LINES'
                AND fcu.descriptive_flex_context_code =
                    'Global Data Elements'
                AND fcu.application_column_name = 'ATTRIBUTE10'
                AND fcu.flex_value_set_id = ffv.flex_value_set_id) ffv10
    ON pol.attribute10 = ffv10.flex_value;
  • 注意:

原版翻车SQL不能改写成形如下面的版本,因为ORA-01799: 列不能外部联接到子查询

SELECT pol.po_line_id, ffv2.flex_value_meaning project_no
  FROM po_lines_all pol, fnd_flex_values_vl ffv2
 WHERE pol.attribute2 = ffv2.flex_value(+)
   AND ffv2.flex_value_set_id(+) =
       (SELECT fcu2.flex_value_set_id
          FROM fnd_descr_flex_column_usages fcu2
         WHERE fcu2.descriptive_flexfield_name = 'PO_LINES'
           AND fcu2.descriptive_flex_context_code = 'Global Data Elements'
           AND fcu2.application_column_name = 'ATTRIBUTE2');
  • 拓展:

相比全局上下文的情况,根据数据表行的上下文动态关联独立值集似乎更值得探讨

沿用之前反思的套路,此时变成POL表与FCU表视同一体了,因为FCU表的“值集ID”列更像是对POL的一个增补,所以FFV应该是与前两者的结合体进行外连接的

ANSI版本

SELECT pol.po_line_id, ffv15.flex_value_meaning project_no
  FROM po_lines_all pol
  JOIN fnd_descr_flex_column_usages fcu15
    ON fcu15.descriptive_flexfield_name = 'PO_LINES'
   AND fcu15.descriptive_flex_context_code = pol.attribute_category
   AND fcu15.application_column_name = 'ATTRIBUTE15'
  LEFT JOIN fnd_flex_values_vl ffv15
    ON ffv15.flex_value = pol.attribute15
   AND ffv15.flex_value_set_id = fcu15.flex_value_set_id;

Oracle SQL版本的写法要注意了,虽然POL表与FCU表绑定一起了,但与FFV进行外连接时分别要用到来自POL的ATTRIBUTE字段和来自FCU的值集ID字段,在12版本以前的数据库中,尚不允许一张表外连接到多张表(ORA-01417),解决方案是先写内嵌式图

-- 12以下版本
SELECT pol.po_line_id, ffv15.flex_value_meaning equi_spec
  FROM (SELECT l.po_line_id, l.attribute15, u.flex_value_set_id
          FROM po_lines_all l, fnd_descr_flex_column_usages u
         WHERE u.descriptive_flexfield_name = 'PO_LINES'
           AND u.descriptive_flex_context_code = l.attribute_category
           AND u.application_column_name = 'ATTRIBUTE15') pol
      ,fnd_flex_values_vl ffv15
 WHERE pol.flex_value_set_id = ffv15.flex_value_set_id(+)
   AND pol.attribute15 = ffv15.flex_value(+);

-- 12及更高版本
SELECT pol.po_line_id, ffv15.flex_value_meaning equi_spec
  FROM po_lines_all                 pol
      ,fnd_descr_flex_column_usages fcu15
      ,fnd_flex_values_vl           ffv15
 WHERE fcu15.descriptive_flexfield_name = 'PO_LINES'
   AND fcu15.descriptive_flex_context_code = pol.attribute_category
   AND fcu15.application_column_name = 'ATTRIBUTE15'
   AND fcu15.flex_value_set_id = ffv15.flex_value_set_id(+)
   AND pol.attribute15 = ffv15.flex_value(+);

SELECT pol.po_line_id, ffv2.flex_value_meaning project_no
  FROM po_lines_all                 pol
      ,fnd_descr_flex_column_usages fcu2
      ,fnd_flex_values_vl           ffv2
 WHERE fcu2.descriptive_flexfield_name = 'PO_LINES'
   AND fcu2.descriptive_flex_context_code = 'Global Data Elements'
   AND fcu2.application_column_name = 'ATTRIBUTE2'
   AND fcu2.flex_value_set_id = ffv2.flex_value_set_id(+)
   AND pol.attribute2 = ffv2.flex_value(+);

微信搜一搜 SQL干货分享

 

银行管理系统是一个非常复杂的系统,由于篇幅有限,我只能提供一个简单的实现,基于Oracle-PLSQL语言。我们将实现以下功能: 1. 用户账户管理:包括开户、销户、查询、存款、取款等操作。 2. 账户转账:允许用户在不同账户之间进行转账操作。 3. 查询交易记录:用户可以查询历史交易记录。 下面是实现步骤: 1. 创建表格: 我们需要创建以下表格: - 用户信息表(包括账户信息) - 交易记录表 2. 编写存储过程: 我们需要编写一系列存储过程来实现用户账户管理、账户转账和查询交易记录。以下是一些基本存储过程的示例: - 开户: ``` CREATE PROCEDURE create_account( p_name VARCHAR2, p_balance NUMBER, p_branch VARCHAR2 ) AS BEGIN INSERT INTO account_info(name, balance, branch) VALUES(p_name, p_balance, p_branch); END; ``` - 销户: ``` CREATE PROCEDURE delete_account( p_account_no VARCHAR2 ) AS BEGIN DELETE FROM account_info WHERE account_no = p_account_no; DELETE FROM transaction_history WHERE account_no = p_account_no; END; ``` - 查询余额: ``` CREATE PROCEDURE check_balance( p_account_no VARCHAR2, p_balance OUT NUMBER ) AS BEGIN SELECT balance INTO p_balance FROM account_info WHERE account_no = p_account_no; END; ``` - 存款: ``` CREATE PROCEDURE deposit( p_account_no VARCHAR2, p_amount NUMBER ) AS BEGIN UPDATE account_info SET balance = balance + p_amount WHERE account_no = p_account_no; INSERT INTO transaction_history(account_no, transaction_type, amount) VALUES(p_account_no, 'DEPOSIT', p_amount); END; ``` - 取款: ``` CREATE PROCEDURE withdraw( p_account_no VARCHAR2, p_amount NUMBER ) AS BEGIN UPDATE account_info SET balance = balance - p_amount WHERE account_no = p_account_no; INSERT INTO transaction_history(account_no, transaction_type, amount) VALUES(p_account_no, 'WITHDRAW', p_amount); END; ``` - 转账: ``` CREATE PROCEDURE transfer( p_sender_account_no VARCHAR2, p_receiver_account_no VARCHAR2, p_amount NUMBER ) AS BEGIN UPDATE account_info SET balance = balance - p_amount WHERE account_no = p_sender_account_no; UPDATE account_info SET balance = balance + p_amount WHERE account_no = p_receiver_account_no; INSERT INTO transaction_history(account_no, transaction_type, amount) VALUES(p_sender_account_no, 'TRANSFER', -p_amount); INSERT INTO transaction_history(account_no, transaction_type, amount) VALUES(p_receiver_account_no, 'TRANSFER', p_amount); END; ``` - 查询交易记录: ``` CREATE PROCEDURE check_transaction_history( p_account_no VARCHAR2 ) AS BEGIN SELECT * FROM transaction_history WHERE account_no = p_account_no; END; ``` 3. 测试: 我们可以使用以下代码来测试我们的系统: ``` DECLARE v_balance NUMBER; BEGIN -- 创建账户 create_account('Alice', 1000, 'Beijing'); create_account('Bob', 2000, 'Shanghai'); -- 查询余额 check_balance('1001', v_balance); DBMS_OUTPUT.PUT_LINE('Alice balance: ' || v_balance); check_balance('1002', v_balance); DBMS_OUTPUT.PUT_LINE('Bob balance: ' || v_balance); -- 存款 deposit('1001', 500); -- 取款 withdraw('1002', 1000); -- 转账 transfer('1001', '1002', 300); -- 查询交易记录 check_transaction_history('1001'); check_transaction_history('1002'); -- 销户 delete_account('1001'); delete_account('1002'); END; ``` 这只是一个简单的实现,真正的银行管理系统要比这个复杂得多。但这个例子应该可以帮助你了解如何使用Oracle-PLSQL来实现银行管理系统。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值