oracle-databasemacosfunctionnvl

Modify sql macro with nvl()


I have customers. Who can have 1 or customer accounts. In each customer account there can be debits or credits in a transaction table.

I am converting a function to a MACRO in order to do performance testing. As you can see from the output below Melanie Zanzona has no transactions associated with the account and the balance is NULL. I want all NULL balances to show as zero (0). I am aware of the NVL command but I can't seem to integrate this into the SQL MACRO function and was hoping someone could help me out. The end result should be that customer Melanie Zanzona should appear last in the output as I am sorting by balance desc

Below is my test CASE.


ALTER SESSION SET NLS_DATE_FORMAT = 'DD-MON-YYYY HH24:MI:SS';

CREATE TABLE CUSTOMERS (
CUSTOMER_ID, FIRST_NAME, LAST_NAME,IS_ACTIVE) AS  SELECT 'L382059', 'Melanie', 'Zanzona','Y' FROM DUAL UNION ALL
SELECT 'P382319', 'Wayne', 'Zahn','Y' FROM DUAL UNION ALL 
SELECT 'E379466', 'Bonnie', 'Winterbottom','Y' FROM DUAL  UNION ALL 
SELECT 'X060162', 'Lisa','Saladino','Y'  FROM DUAL UNION ALL 
SELECT 'Y331964', 'Bella', 'Oxmix','Y' FROM DUAL  UNION ALL 
SELECT 'Z888555', 'Debra', 'Doss','Y' FROM DUAL;


CREATE TABLE CUSTOMER_ACCOUNTS (
ACCOUNT_NUMBER,
CUSTOMER_ID, VENDOR_ID,
IS_ACTIVE) AS 
SELECT '0HE53T6WP9RV28P', 'Y331964', 1, 'Y' FROM DUAL 
 UNION ALL
SELECT 'Z17ARWYYZRCU2Q2', 'P382319', 1, 'Y' FROM DUAL 
 UNION ALL
 SELECT '0T81Z07CS6LXQ7Z', 'P382319', 3, 'Y' FROM DUAL 
UNION ALL 
SELECT 'YWYXC3Q5N9XZ7S', 'L382059', 1, 'Y' FROM DUAL UNION ALL
SELECT '612ZKAQ66VA3W3', 'Y331964', 3, 'Y' FROM DUAL    UNION ALL 
SELECT 'BCHD9TW78W67S1D', 'Z888555', 3, 'Y' FROM DUAL UNION ALL 
SELECT '0HLS87LDR1TE8WB', 
'X060162', 3, 'Y' FROM DUAL  UNION ALL 
SELECT 'Z69AG7DKS37UYU',
'X060162', 3, 'Y' FROM DUAL  UNION ALL 
SELECT 'B17ARWYYZRCU2Q2', 
'X060162', 3, 'Y' FROM DUAL  UNION ALL 
SELECT 'THVQD6M9LR7AVK', 'E379466', 1, 'Y' FROM DUAL  UNION ALL 
SELECT '0Z76WT5NTLRZPTW',
'E379466', 1, 'Y' FROM DUAL;

create table transactions (
     transaction_id NUMBER GENERATED BY DEFAULT AS IDENTITY,
    account_number VARCHAR2(15),
    transaction_type varchar2(1) DEFAULT 'C',
    transaction_amount NUMBER(10,2),
     transaction_date DATE DEFAULT SYSDATE 
);


insert  into transactions(
account_number, transaction_type, transaction_amount, transaction_date)
SELECT '0HE53T6WP9RV28P', 'D', (LEVEL * 100.05), (DATE '2023-08-23' + 19/24) + (LEVEL-1) * (1/1440 * -5)  from dual  connect by level <= 15 union all
SELECT '0HE53T6WP9RV28P', 'C', (LEVEL * 50.00), (DATE '2023-08-23' + 19/24) + (LEVEL-1) * (1/1440 * -6)  from dual  connect by level <= 14 UNION ALL 
SELECT 'Z17ARWYYZRCU2Q2', 'D', (LEVEL * 1250.50), date '2023-05-14' + level * interval '5 15:13' day to minute from dual
          connect by level <= 7
union all
SELECT 'Z17ARWYYZRCU2Q2', 'C', (LEVEL * 1175.75), date '2023-07-04' + level * interval '1 21:23' day to minute from dual
          connect by level <= 5
union all
SELECT '0T81Z07CS6LXQ7Z', 'D', (LEVEL * 1250.50), date '2023-02-14' + level * interval '3 15:13' day to minute from dual
          connect by level <= 17
union all
SELECT '0T81Z07CS6LXQ7Z', 'C', (LEVEL * 75.75), date '2023-02-04' + level * interval '2 21:23' day to minute from dual
          connect by level <= 11
union all
select '612ZKAQ66VA3W3', 'D', 555.25 * LEVEL, (DATE '2023-07-13' + 13/24) + (level * 2)  from dual 
    connect by level <= 25 
UNION ALL 
select '612ZKAQ66VA3W3', 'C', 555.25 * LEVEL, (DATE '2023-07-23' + 13/24) + (level * 2)  from dual 
    connect by level <= 20 
UNION ALL 
select 'BCHD9TW78W67S1D', 'D', 1125.25 * LEVEL, date '2023-01-23' + level * interval '1 1' day to hour from dual
          connect by level <= 11
union all
select 'BCHD9TW78W67S1D', 'C', 925.00 * LEVEL, date '2023-01-25' + level * interval '1 1' day to hour from dual
          connect by level <= 7
union all
SELECT '0HLS87LDR1TE8WB', 'D', (LEVEL * 1250.50), date '2023-05-04' + level * interval '1 15:13' day to minute from dual
          connect by level <= 7
union all
SELECT '0HLS87LDR1TE8WB', 'C', (LEVEL * 1175.75), date '2023-05-04' + level * interval '1 15:13' day to minute from dual
          connect by level <= 5
union all
SELECT 'Z69AG7DKS37UYU', 'D', ((LEVEL * 5) * 1750), date '2023-06-01' + level * interval '1 18:43:35' day to second from dual
          connect by level <= 15
union all
SELECT 'Z69AG7DKS37UYU', 'C', ((LEVEL * 5) * 1750), date '2023-06-11' + level * interval '1 15:23:49' day to second from dual
          connect by level <= 13
union all
SELECT '0Z76WT5NTLRZPTW', 'D', (LEVEL * 100.57), date '2023-04-02' + level * interval '1 4' day to hour from dual
          connect by level <= 5
union all
SELECT '0Z76WT5NTLRZPTW', 'C', (LEVEL * 25.26), date '2023-04-04' + level * interval '1 4' day to hour from dual
          connect by level <= 5
union all
SELECT 'THVQD6M9LR7AVK', 'D', (LEVEL * 250.10), date '2023-05-10' + level * interval '1 7' day to hour from dual
          connect by level <= 13
union all
SELECT 'THVQD6M9LR7AVK', 'C', (LEVEL * 133.11), (SYSDATE - LEVEL)  from dual
          connect by level <= 9;


create or replace function get_customer_balance (
  i_customer_id in varchar2
) return clob sql_macro 
is
begin
  return q'[
   SELECT 
     SUM 
     (
       CASE  t.transaction_type
       WHEN  'C'
       THEN -t.transaction_amount
       ELSE t.transaction_amount
       END 
      )
   FROM  customer_accounts  ca
   JOIN  transactions       t  ON  t.account_number  = ca.account_number
   WHERE  ca.customer_id  = i_customer_id  -- one customer
   OR   ca.customer_id  IS NULL    ]';
end;
/

SELECT
    c.customer_id,
    c.first_name,
    c.last_name,
       (SELECT * FROM TABLE (get_customer_balance (c.customer_id))) AS balance FROM customers c 
ORDER BY balance DESC;


CUSTOMER_ID FIRST_NAME
LAST_NAME BALANCE
L382059 Melanie Zanzona -
X060162 Lisa Saladino 271127.75
P382319 Wayne Zahn 203704.75
Y331964 Bella Oxmix 70609.75
Z888555 Debra Doss 48366.5
E379466 Bonnie Winterbottom 17898.8



Solution

  • As @JonasMetzler pointed out in the comments, you can simply use COALESCE (SQL standard) or NVL (Oracle specific) to convert the NULL value to 0 before attemping to use it for ordering.

    Given your usage, it would seem preferable to have get_customer_balance return a scalar rather than a table, then you can just call it directly without a subquery:

    create or replace function get_customer_balance (
      i_customer_id in varchar2
    ) return varchar2 sql_macro(scalar)
    is
    begin
      return q'[
       SELECT 
         COALESCE(SUM 
         (
           CASE  t.transaction_type
           WHEN  'C'
           THEN -t.transaction_amount
           ELSE t.transaction_amount
           END 
          )
       , 0)
       FROM  customer_accounts  ca
       JOIN  transactions       t  ON  t.account_number  = ca.account_number
       WHERE  ca.customer_id  = i_customer_id  -- one customer
       OR   ca.customer_id  IS NULL    ]';
    end;
    /
    
    SELECT
        c.customer_id,
        c.first_name,
        c.last_name,
        get_customer_balance(c.customer_id) AS balance
    FROM customers c 
    ORDER BY balance DESC;
    

    Output:

    CUSTOMER_ID FIRST_NAME  LAST_NAME       BALANCE
    X060162     Lisa        Saladino        271127.75
    P382319     Wayne       Zahn            203704.75
    Y331964     Bella       Oxmix           70609.75
    Z888555     Debra       Doss            48366.5
    E379466     Bonnie      Winterbottom    17898.8
    L382059     Melanie     Zanzona         0
    

    Demo on dbfiddle.uk