Search code examples
oracle-databaseplsql

PL/SQL Function to Retrieve Unread Notifications for a User


I have a problem with a plsql function which is not returning information in data. The function has to fetch all the notifications from the NOTIFICATIONS table but which do not have any insertion in the USER_NOTIFICATIONS table.

The USER_NOTIFICATIONS table has entries where in each row there is a user_id, of the user who "read" a notification with a particular NOTIFICATION_ID. What I want is for it to bring me the notifications that have not been read by the user.

This is the function

FUNCTION NEW_NOTIFICATION(
    user_id IN NUMBER,
    fecha_val IN VARCHAR2,
    start_position IN NUMBER,
    end_position IN NUMBER
)
RETURN CLOB
AS
    l_result1 CLOB;
    l_result2 CLOB := ''; 
    l_result3 CLOB;
    l_result_size NUMBER;
    l_count NUMBER;
    l_row_check NUMBER := start_position;
    l_row_number NUMBER := 0;
    l_json_record VARCHAR2(4000);

    CURSOR c_notifications_new IS
        SELECT 
            '{ "ID": "' || ID || '", "RECIPIENTS": "' || RECIPIENTS || '", "MESSAGE_TITLE": "' || MESSAGE_TITLE || '", "MESSAGE_BODY": "' || MESSAGE_BODY || '", "MULTIMEDIA_ID": "' || MULTIMEDIA_ID || '", "NOTIFICATION_DATE_FROM": "' || TO_CHAR(NOTIFICATION_DATE_FROM, 'DD/MM/YYYY HH24:MI:SS') || '", "NOTIFICATION_DATE_TO": "' || TO_CHAR(NOTIFICATION_DATE_TO, 'DD/MM/YYYY HH24:MI:SS') || '", "SEND_BY_EMAIL": "' || SEND_BY_EMAIL || '", "CREATED_AT": "' || TO_CHAR(CREATED_AT, 'DD/MM/YYYY HH24:MI:SS') || '"}' AS json_record
        FROM NOTIFICATIONS N
        WHERE TO_DATE(fecha_val, 'DD/MM/YYYY') BETWEEN N.NOTIFICATION_DATE_FROM AND N.NOTIFICATION_DATE_TO
          AND NOT EXISTS (
              SELECT 1 
              FROM USER_NOTIFICATIONS UN
              WHERE UN.USER_ID = user_id AND UN.NOTIFICATION_ID = N.ID
          )
          AND N.DELETED_AT IS NULL
        ORDER BY N.CREATED_AT DESC;

BEGIN
    SELECT COUNT(*) INTO l_count
    FROM NOTIFICATIONS N
    WHERE TO_DATE(fecha_val, 'DD/MM/YYYY') BETWEEN N.NOTIFICATION_DATE_FROM AND N.NOTIFICATION_DATE_TO
      AND NOT EXISTS (
          SELECT 1 
          FROM USER_NOTIFICATIONS UN
          WHERE UN.USER_ID = user_id AND UN.NOTIFICATION_ID = N.ID
      )
      AND N.DELETED_AT IS NULL;

    FOR r in c_notifications_new LOOP
        IF l_row_check + 1 >= start_position AND l_row_check + 1 <= end_position THEN
            l_json_record := r.json_record;

            l_result_size := DBMS_LOB.GETLENGTH(l_result1 || l_result2 || l_json_record || '}');

            IF l_result_size > 10000 THEN
                EXIT;
            ELSE
                
                IF l_row_check != 0 THEN
                    l_result2 := l_result2 || ', ';
                END IF;
                l_result2 := l_result2 || l_json_record;
                l_row_check := l_row_check + 1;
                l_row_number := l_row_number + 1;
            END IF;
        END IF;
    END LOOP;
    
    l_result1 := '{ "count": ' || l_count ||  ',"rows": ' || l_row_number || ', "data": [';
    l_result3 := l_result1 || l_result2 || ']}';
    RETURN l_result

And this functions works

FUNCTION NEW_NOTIFICATION(
    user_id IN NUMBER,
    fecha_val IN VARCHAR2,
    start_position IN NUMBER,
    end_position IN NUMBER
)
RETURN CLOB
AS
    l_result1 CLOB;
    l_result2 CLOB := '';
    l_result3 CLOB;
    l_result_size NUMBER;
    l_count NUMBER;
    l_row_check NUMBER := start_position;
    l_row_number NUMBER := 0;
    l_json_record VARCHAR2(4000); 

    CURSOR c_notifications_new IS
        SELECT 
            '{ "ID": "' || ID || '", "RECIPIENTS": "' || RECIPIENTS || '", "MESSAGE_TITLE": "' || MESSAGE_TITLE || '", "MESSAGE_BODY": "' || MESSAGE_BODY || '", "MULTIMEDIA_ID": "' || MULTIMEDIA_ID || '", "NOTIFICATION_DATE_FROM": "' || TO_CHAR(NOTIFICATION_DATE_FROM, 'DD/MM/YYYY HH24:MI:SS') || '", "NOTIFICATION_DATE_TO": "' || TO_CHAR(NOTIFICATION_DATE_TO, 'DD/MM/YYYY HH24:MI:SS') || '", "SEND_BY_EMAIL": "' || SEND_BY_EMAIL || '", "CREATED_AT": "' || TO_CHAR(CREATED_AT, 'DD/MM/YYYY HH24:MI:SS') || '"}' AS json_record
        FROM NOTIFICATIONS N
        WHERE TO_DATE(fecha_val, 'DD/MM/YYYY') BETWEEN N.NOTIFICATION_DATE_FROM AND N.NOTIFICATION_DATE_TO
        
          AND N.DELETED_AT IS NULL
        ORDER BY N.CREATED_AT DESC;

BEGIN
    SELECT COUNT(*) INTO l_count
    FROM NOTIFICATIONS N
    WHERE TO_DATE(fecha_val, 'DD/MM/YYYY') BETWEEN N.NOTIFICATION_DATE_FROM AND N.NOTIFICATION_DATE_TO
     
      AND N.DELETED_AT IS NULL;

    FOR r in c_notifications_new LOOP
        IF l_row_check + 1 >= start_position AND l_row_check + 1 <= end_position THEN
            l_json_record := r.json_record;

            l_result_size := DBMS_LOB.GETLENGTH(l_result1 || l_result2 || l_json_record || '}');

            IF l_result_size > 10000 THEN
                EXIT;
            ELSE
                
                IF l_row_check != 0 THEN
                    l_result2 := l_result2 || ', ';
                END IF;
                l_result2 := l_result2 || l_json_record;
                l_row_check := l_row_check + 1;
                l_row_number := l_row_number + 1;
            END IF;
        END IF;
    END LOOP;
    
    l_result1 := '{ "count": ' || l_count ||  ',"rows": ' || l_row_number || ', "data": [';
    l_result3 := l_result1 || l_result2 || ']}';
    RETURN l_result

So it looks like the problem is here:

AND NOT EXISTS (
          SELECT 1 
          FROM USER_NOTIFICATIONS UN
          WHERE UN.USER_ID = user_id AND UN.NOTIFICATION_ID = N.ID
      )

In other words, this condition is responsible for selecting only rows from the NOTIFICATIONS table that do not have a match in the USER_NOTIFICATIONS table for the specific user (user_id). This implies that the notification has not yet been read by that user, since there is no entry in USER_NOTIFICATIONS that matches the combination of USER_ID and NOTIFICATION_ID.

By the way I'm using Oracle DB version 11


Solution

  • Summary: Do not name PL/SQL variable with the same name as an SQL column name.


    AND NOT EXISTS (
              SELECT 1 
              FROM USER_NOTIFICATIONS UN
              WHERE UN.USER_ID = user_id AND UN.NOTIFICATION_ID = N.ID
          )
    

    Contains the filter condition:

    WHERE UN.USER_ID = user_id
    

    In this case, user_id refers to the local SQL scope and the PL/SQL variable user_id is shadowed by the column value so you are effectively doing:

    WHERE UN.USER_ID = UN.user_id
    

    which is (almost) the same as 1 = 1 so all rows are going to be filtered out if the other filter matches.

    Change the function signature to use something other than the column name (for example, you could adopt the convention of using an i_ prefix for IN parameters):

    FUNCTION NEW_NOTIFICATION(
        i_user_id        IN NUMBER,
        i_fecha_val      IN VARCHAR2,
        i_start_position IN NUMBER,
        i_end_position   IN NUMBER
    )
    

    and then change the rest of the code to use the new identifier for the PL/SQL variables and do not try to use the same identifier for PL/SQL variables and SQL columns.