Home » SQL & PL/SQL » SQL & PL/SQL » Update null value columns based upon previous non null columns (11.2)
Update null value columns based upon previous non null columns [message #678018] Thu, 31 October 2019 10:38 Go to previous message
lott42_gmail
Messages: 146
Registered: June 2010
Senior Member
I have sets of data based upon an "ID", for each "ID", there can be many records associated with it based on "to_date". I need to possibly fill in null value(s) for 2 attribute columns(x_days, y_days) based upon subsequent last non null values for each "ID" and "to_date".

create table MRADW_STAGE.AVAILS_LO_UPD(AVAIL_ID number, VALID_TO_DATE date, X_DAYS number, y_DAYS number);

insert all
  into MRADW_STAGE.AVAILS_LO_UPD (AVAIL_ID, VALID_TO_DATE, X_DAYS, Y_DAYS) values (1, SYSDATE, 10, null)
  into MRADW_STAGE.AVAILS_LO_UPD (AVAIL_ID, VALID_TO_DATE, X_DAYS, Y_DAYS) values (1, SYSDATE+1, 10, 20)
  into MRADW_STAGE.AVAILS_LO_UPD (AVAIL_ID, VALID_TO_DATE, X_DAYS, Y_DAYS) values (1, SYSDATE+2, null, 10)
  into MRADW_STAGE.AVAILS_LO_UPD (AVAIL_ID, VALID_TO_DATE, X_DAYS, Y_DAYS) values (1, SYSDATE+3, null, null)
  into MRADW_STAGE.AVAILS_LO_UPD (AVAIL_ID, VALID_TO_DATE, X_DAYS, Y_DAYS) values (1, SYSDATE+4, 5, null)
  into MRADW_STAGE.AVAILS_LO_UPD (AVAIL_ID, VALID_TO_DATE, X_DAYS, Y_DAYS) values (1, SYSDATE+5, null, 20)
  into MRADW_STAGE.AVAILS_LO_UPD (AVAIL_ID, VALID_TO_DATE, X_DAYS, Y_DAYS) values (1, SYSDATE+6, 20, null)
  into MRADW_STAGE.AVAILS_LO_UPD (AVAIL_ID, VALID_TO_DATE, X_DAYS, Y_DAYS) values (2, SYSDATE, 10, 10)
  into MRADW_STAGE.AVAILS_LO_UPD (AVAIL_ID, VALID_TO_DATE, X_DAYS, Y_DAYS) values (2, SYSDATE+1, null, null)
  into MRADW_STAGE.AVAILS_LO_UPD (AVAIL_ID, VALID_TO_DATE, X_DAYS, Y_DAYS) values (2, SYSDATE+2, null, null)
SELECT * FROM dual;

Data:
AVAIL_ID VALID_TO_DATE  X_DAYS  Y_DAYS
1	31-OCT-19	10	
1	01-NOV-19	10	20
1	02-NOV-19		10
1	03-NOV-19		
1	04-NOV-19	5	
1	05-NOV-19		20
1	06-NOV-19	20	
2	31-OCT-19	10	10
2	01-NOV-19		
2	02-NOV-19		

I want the output to look like this
AVAIL_ID VALID_TO_DATE  X_DAYS  Y_DAYS
1	31-OCT-19	10	
1	01-NOV-19	10	20
1	02-NOV-19	10	10
1	03-NOV-19	10	10
1	04-NOV-19	5	10
1	05-NOV-19	5	20
1	06-NOV-19	20	20
2	31-OCT-19	10	10
2	01-NOV-19	10	10
2	02-NOV-19	10	10

using "X_DAYS"

I need to fill in
Id 1 02-NOV-19 with 10
Id 1 03-NOV-19 with 10

Because,
Id:1 01-NOV-19 x_days=10 was last non null value I saw

I need to fill in
Id 1 05-NOV-19 with 5

Because,
Id:1 04-NOV-19 x_days=5 was last non null value I saw

I need to fill in
Id 2 01-NOV-19 with 10
Id 2 02-NOV-19 with 10

Because,
Id:2 31-OCT-19 x_days=10 was last non null value I saw

I wrote a quick pl/sql procedure to do this:

CREATE OR REPLACE PROCEDURE avail_info_upd11 (
    p_avail_id IN NUMBER DEFAULT NULL
) AS

    CURSOR c_avail_info (
        cp_avail_id IN NUMBER
    ) IS SELECT
        ROWID,
        avail_id,
        valid_to_date,
        x_days,
        y_days
         FROM
        mradw_stage.avails_lo_upd a
         WHERE
        1 = 1
        --and a.avail_id = cp_avail_id
    ORDER BY
        avail_id,
        valid_to_date;

    r_avail_info            c_avail_info%rowtype; 

  --Error Exceptions 
    e_generic_error EXCEPTION;
    e_others_error EXCEPTION; 

  --Error Vars 
    v_error                 NUMBER;
    v_error_code            NUMBER;
    v_error_location        VARCHAR2(4000);
    v_error_msg             VARCHAR2(4000);
    v_error_msg_func_proc   VARCHAR2(4000); 

  --Generic Pack/Proc Vars 

  --Generic Results/Message Vars 
    v_result                NUMBER := 0;
    v_result_msg            VARCHAR2(4000);
    v_count                 NUMBER := 0;
    v_update_cnt            NUMBER := 0;
    v_x_days                NUMBER; -- temp
    v_y_days                NUMBER; --temp 
    v_update_avail          BOOLEAN := false;
    v_date                  DATE;

  --Parm Vars 
    v_avail_id              VARCHAR2(100) := '0';

    FUNCTION update_avail_id (
        cp_avail_id   IN NUMBER DEFAULT NULL,
        cp_date       IN DATE DEFAULT NULL,
        cp_x_value    IN NUMBER DEFAULT NULL,
        cp_y_value    IN NUMBER DEFAULT NULL,
        cp_rowid      IN VARCHAR2 DEFAULT NULL
    ) RETURN NUMBER
      --UPDATE_AVAIL_ID(CP_rowid in varchar2 default null) return number
        IS
    BEGIN
        v_update_cnt := v_update_cnt + 1;
        dbms_output.put_line('begin update avail id: '
        || cp_avail_id
        || ' date: '
        || TO_CHAR(cp_date,'mm/dd/yyyy hh:mi am')
        || ' rowid: '
        || cp_rowid
        || ' x_days: '
        || cp_x_value
        || ' '
        || TO_CHAR(SYSDATE,'mm/dd/yyyy hh:mi:ss AM') );

        UPDATE mradw_stage.avails_lo_upd
            SET
                x_days = cp_x_value,
                y_days = cp_y_value
        WHERE
            1 = 1
            AND   ROWID = cp_rowid;

        RETURN '';
    EXCEPTION
        WHEN OTHERS THEN
            RETURN sqlerrm;
    END;

BEGIN
    dbms_output.put_line(' starting. ');
    FOR r_avail_info IN c_avail_info(cp_avail_id => '') LOOP
        v_count := v_count + 1;
        IF
            nvl(v_avail_id,0) <> r_avail_info.avail_id
        THEN  --new avail, when compared to old one.
            v_x_days := NULL;
            v_y_days := NULL;
        END IF;

        IF
            v_x_days IS NOT NULL
        THEN
            IF
                r_avail_info.x_days IS NULL
            THEN
                v_update_avail := true;
            ELSIF r_avail_info.x_days <> v_x_days THEN
                v_x_days := r_avail_info.x_days;
                v_update_avail := true;
            END IF;
        ELSE
            v_x_days := r_avail_info.x_days;
        END IF;

        IF
            v_y_days IS NOT NULL
        THEN
            IF
                r_avail_info.y_days IS NULL
            THEN
                v_update_avail := true;
            ELSIF r_avail_info.y_days <> v_y_days THEN
                v_y_days := r_avail_info.y_days;
                v_update_avail := true;
            END IF;
        ELSE
            v_y_days := r_avail_info.y_days;
        END IF;

        IF
            ( v_update_avail = true )
        THEN   --Try to update avail table 
            v_result_msg := update_avail_id(cp_avail_id => r_avail_info.avail_id,cp_date => r_avail_info.valid_to_date,cp_x_value => v_x_days,cp_y_value
=> v_y_days,cp_rowid => r_avail_info.rowid);

            IF
                ( v_result_msg IS NOT NULL )
            THEN
                v_error_msg := v_result_msg;
                RAISE e_generic_error;
            END IF;

            v_update_avail := false;
        END IF;

        v_avail_id := r_avail_info.avail_id;
    END LOOP;

    COMMIT;
    dbms_output.put_line(v_update_cnt
    || ' out of '
    || v_count
    || ' rows updated'
    || ' as of '
    || TO_CHAR(SYSDATE,'mm/dd/yyyy hh:mi:ss AM') );

    return;
EXCEPTION
    WHEN e_generic_error THEN
        ROLLBACK;
        dbms_output.put_line(v_error_msg);
        return;
    WHEN OTHERS THEN
        ROLLBACK;
        v_error_location := '"ERROR(Main - ''prc_get_casrep_notes). ''"';
        dbms_output.put_line(v_error_msg);
        return;
END avail_info_upd11;


Can this be done with pure sql(it doesn't quite do what I want) in a recursive lag/lead functionality such as:

SELECT
    avail_id,
    valid_to_date,
    X_DAYS,
    CASE
            WHEN x_days IS NULL THEN LAG(x_days) OVER(
                PARTITION BY avail_id
                ORDER BY
                    valid_to_date
            )
            ELSE x_days
        end
    X_DAYS_LAG,
    Y_DAYS,
    CASE
            WHEN y_days IS NULL THEN LAG(y_days) OVER(
                PARTITION BY avail_id
                ORDER BY
                    valid_to_date
            )
            ELSE y_days
        END
    y_days_lag
from
    MRADW_STAGE.AVAILS_LO_UPD
order by avail_id, valid_to_date
    ;


 
Read Message
Read Message
Read Message
Read Message
Read Message
Read Message
Read Message
Read Message
Read Message
Previous Topic: ANALYZE TABLE STATEMENT
Next Topic: ALTER TABLE STATEMENT FOR CLUSTER
Goto Forum:
  


Current Time: Thu Apr 18 19:35:36 CDT 2024