Domanda

I am trying to run an UPSERT on a table Person like this:

BEGIN
    EXECUTE IMMEDIATE q'[
        MERGE INTO Person p
        USING (SELECT :x id, :y first_name, :z last_name FROM Dual) data

        ON (p.id = :x) -- note the repeated placeholder :x here

        WHEN MATCHED THEN
              UPDATE SET p.first_name = data.first_name, p.last_name = data.last_name
        WHEN NOT MATCHED THEN
              INSERT (id, first_name, last_name) VALUES (data.id, data.first_name, data.last_name)
    ]' USING 123, 'Foo', 'Bar';
END;
/

If I run the code as given above, Oracle throws a ORA-01008: not all variables bound ORA-06512: at line 2

According to the docs this is not supposed to happen at this point (inside an anonymous block). What am I doing wrong?

I know that I can work around this if I supply four arguments … USING 123, 'Foo', 'Bar', 123; and this works just fine, but obviously I don't want to repeat myself. Edit: as pointed out in the answers, ON (p.id = data.id) is another possible workaround.

The question is not how to work around this, it's more about understanding the cause of this error in this situation.

È stato utile?

Soluzione

You seem to be misinterpreting the documentation:

If the dynamic SQL statement represents an anonymous PL/SQL block or a CALL statement, repetition of placeholder names is significant.

This states that if the statement that is being dynamically executed is a PL/SQL block, then the bind variable don't have to be repeated. Your dynamic statement is a SQL statement, which does not qualify. You can fix this by adding begin and end; to the dynamic statement:

BEGIN
    EXECUTE IMMEDIATE q'[
        BEGIN
           MERGE INTO Person p
           USING (SELECT :x id, :y first_name, :z last_name FROM Dual) data    
           ON (p.id = :x) -- note the repeated placeholder :x here    
           WHEN MATCHED THEN
              UPDATE SET p.first_name = data.first_name, p.last_name = data.last_name
           WHEN NOT MATCHED THEN
              INSERT (id, first_name, last_name) VALUES (data.id, data.first_name, data.last_name)
        END;
    ]' USING 123, 'Foo', 'Bar';
END;
/

In an case, it's simple to fix this statement without repeating the bind variable:

BEGIN
    EXECUTE IMMEDIATE q'[
        MERGE INTO Person p
        USING (SELECT :x id, :y first_name, :z last_name FROM Dual) data
        ON (p.id = data.id) 
        WHEN MATCHED THEN
              UPDATE SET p.first_name = data.first_name, p.last_name = data.last_name
        WHEN NOT MATCHED THEN
              INSERT (id, first_name, last_name) VALUES (data.id, data.first_name, data.last_name)
    ]' USING 123, 'Foo', 'Bar';
END;
/

Finally, I feel I must point out that there's little reason to use dynamic SQL in this situation. Unless there's more going on that is affecting the SQL being run, this should just be a static SQL statement.

Altri suggerimenti

The documentation you linked to says that the problem is; or at least the previous section does:

If the dynamic SQL statement does not represent an anonymous PL/SQL block or a CALL statement, repetition of placeholder names is insignificant. Placeholders are associated with bind variables in the USING clause by position, not by name.

You referred to the part that said:

If the dynamic SQL statement represents an anonymous PL/SQL block or a CALL statement, repetition of placeholder names is significant. Each unique placeholder name must have a corresponding bind variable in the USING clause. If you repeat a placeholder name, you need not repeat its corresponding bind variable. All references to that placeholder name correspond to one bind variable in the USING clause.

Your dynamic SQL statementis not an anonymous block. You're executing your dynamic SQL within an anonymous block, but that's irrelevant; it's what the execute immediate is doing that matters. And that is a plain SQL merge statement.

So you have to repeat the values, you can't avoid that - unless you create a procedure that does the merge and pass the three bind variables as arguments to that, once each. Or create an anonymous block wrapper around your statement, which seems a bit pointless, but maybe illustrates the difference in behaviour; this will work:

BEGIN
    EXECUTE IMMEDIATE q'[
      BEGIN
        MERGE INTO Person p
        USING (SELECT :x id, :y first_name, :z last_name FROM Dual) data

        ON (p.id = :x) 

        WHEN MATCHED THEN
              UPDATE SET p.first_name = data.first_name, p.last_name = data.last_name
        WHEN NOT MATCHED THEN
              INSERT (id, first_name, last_name) VALUES (data.id, data.first_name, data.last_name);
      END;
    ]' USING 123, 'Foo', 'Bar';
END;
/

Note the BEGIN/END inside the dynamic statement.

Or in this case, as noted in comments and another answer, change your code to do ON (p.id = data.id) - which is just avoiding the issue...

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top