Question

I have a problem with the a PL/SQL function. I try to get a single result, depending on the restrictions in the statement. If no result matches the result should be null.

If I execute the select statement directly on the table status_history I get the correct result. If I execute the function I get an id which is not part of the range of the veh_id. Its another id from the table. So I get a result, but I expect no result.

Here is the function:

 CREATE OR REPLACE FUNCTION is_inventory (
        veh_id NUMBER,
        status_date DATE) RETURN NUMBER IS
      cnt number;
    BEGIN
      SELECT sh.id
  INTO cnt
  FROM status_history sh
 WHERE     sh.veh_id = veh_id
       AND sh.code >= 100
       AND sh.code < 200
       AND sh.id =
              (SELECT id
                 FROM (  SELECT sh2.id, sh2.status_date, sh2.code
                           FROM status_history sh2
                          WHERE     sh2.veh_id = veh_id
                                AND sh2.status_date <= status_date
                       ORDER BY sh2.status_date DESC, sh2.code DESC)
                WHERE ROWNUM = 1);

    if cnt > 0 THEN
      RETURN cnt;
    else
      RETURN cnt;
    END IF;
    END is_inventory;
    /

Maybe the function could be written much better, but I think I should get the same result if I execute the function or the SQL statement directly.

In generell the result of the function should not return the id. I first tried a count, but for debuggin I changed the result to the id.

Thank you for your help.

Was it helpful?

Solution

The following where clause does not do what you expect:

WHERE sh2.veh_id = veh_id AND sh2.status_date <= status_date 

It is doing:

WHERE sh2.veh_id = sh2.veh_id AND sh2.status_date <= sh2.status_date 

It is not plugging in the variables. This is because of the scoping rules of SQL, that first looks for matching column names before looking for variable names. The right fix is to prefix the parameters and variables with something that distinguishes them from column names.

CREATE OR REPLACE FUNCTION is_inventory (
    v_veh_id NUMBER,
    v_status_date DATE) RETURN NUMBER IS
    v_cnt number;
BEGIN
. . .

OTHER TIPS

You've named the parameters to your function the same as columns in your tables. That's almost certainly a bad idea and probably the source of your bug.

In your innermost query, when Oracle sees

WHERE sh2.veh_id = veh_id 
  AND sh2.status_date <= status_date

veh_id and status_date are resolved to columns in the status_history table, not to the parameters of the same name. That effectively turns these predicates into

WHERE sh2.veh_id = sh2.veh_id 
  AND sh2.status_date <= sh2.status_date

which is, self-evidently, true for every row in status_history unless either veh_id or status_date is NULL.

This is why most people adopt a standard naming convention that differentiates parameters, local variables, and column names. Something like

CREATE OR REPLACE FUNCTION is_inventory (
    p_veh_id NUMBER,
    p_status_date DATE)

which then changes your query to

WHERE sh2.veh_id = p_veh_id 
  AND sh2.status_date <= p_status_date

Alternately, you could fully qualify the parameter names in your query but that is less common and I find it to be more error-prone (though others disagree)

WHERE sh2.veh_id = is_inventory.veh_id 
  AND sh2.status_date <= is_inventory.status_date
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top