0

I am new to PL SQL and still learning, but I need to solve a problem and I don't know enough about PL SQL to solve my problem quickly.

I'm referencing two tables: users and attributes. I have a procedure that takes 3 arguments: attrib_id, uid, attrib_value.

I first queried the attributes table with the attrib_id to return an attribute name and assign it to a variable. My code works up to this point.

Next I want to use the variable created from the previous select statement in another select statement to query the users table and return the current value associated with the attribute that the variable represents.

CODE:

PROCEDURE value_update_proc_z(attrib_id INTEGER, uid IN VARCHAR2, attrib_value IN VARCHAR2)

IS
    v_old_attrib_name attributes.attribute_name%TYPE;
    v_oldattrib_value varchar2(100);
    v_mymsg varchar2(2000);

BEGIN
    EXECUTE IMMEDIATE 'SELECT attrib_name FROM attributes WHERE indx = ''' || attrib_id || '''' INTO v_old_attrib_name;

    EXECUTE IMMEDIATE 'SELECT' || v_old_attrib_name || 'FROM USERS WHERE USERID = ''' || uid || '''' INTO v_oldattrib_value;

    v_mymsg := v_old_attrib_name || ' ' || v_oldattrib_value;

END value_update_proc_z;

The first query should return a value from the attributes table based on a number passed into the procedure. For example, if attrib_id = 1, the query would return first_name, if attrib_id = 2, then last_name returned, and if attrib_id = 3, the would be returned email. The returned value would be assigned to the variable v_old_attrib_name.

Using the variable v_old_attrib_name in my select statement, I would expect that the second query would return a value, example; last_name would return williams, or email would return [email protected]. The result of this query would be assigned to the variable v_oldattrib_value.

Currently, the first Execute Immediate works and when I display the message I can see the value of that variable, but when I add the second Execute Immediate, I get a message that the operation could not be completed. This isn't an error generated by the system, it is a message set up by a previous developer.

I am open to suggestions for improvement.

Thanks!

3
  • I would question why you need this procedure at all. It would be simpler just to have static SQL like this in the calling procedure: select columnname from users where userid = :userid Commented Dec 11, 2017 at 7:22
  • I need to first get the column name so that I can use it in the second select statement. The procedure is being passed an attribute id number that references the name of the attribute in the attributes table. So, if the procedure gets an attribute id of 1, then the name of the attribute is first_name and that is the name of the column in the users table. So, I don't know that until the procedure is called. Commented Dec 11, 2017 at 19:18
  • Have a look at the calling code. That is where the attribute "id" is being set. That's where you could change it so you don't need to look up the data dictionary every time someone wants to query a record. What you have here has many of the drawbacks of the "EAV" pattern (considered to be an anti-pattern). Commented Dec 11, 2017 at 22:26

4 Answers 4

6

Dynamic SQL is hard because it turns compilation errors into runtime errors. It is alas not astonishing how many questions posted here using dynamic SQL are simple typos which would have been easy to spot if the statement were written as static SQL. That seems to be the case here.

The standard advice here is to write the SQL in a static form first, so you know the code works. Only then convert it it to template SQL for dynamic execution, paying careful attention to spaces, names, etc. Your second statement is missing spaces in the template SQL either side of the concatenated v_old_attrib_name variable.

Also, don't use dynamic SQL if static SQL works. For instance your first statement can be - and should be - static.

PROCEDURE value_update_proc_z(
    attrib_id INTEGER, 
    uid IN VARCHAR2, 
    attrib_value IN VARCHAR2)

IS
    v_old_attrib_name attributes.attribute_name%TYPE;
    v_oldattrib_value varchar2(100);
    v_mymsg varchar2(2000);

BEGIN
    SELECT attrib_name 
    INTO v_old_attrib_name
    FROM attributes 
    WHERE indx = attrib_id  ;

    EXECUTE IMMEDIATE 
        'SELECT ' || v_old_attrib_name || ' FROM USERS WHERE USERID = :1' 
        INTO v_oldattrib_value
        using uid;

    v_mymsg := v_old_attrib_name || ' ' || v_oldattrib_value;

   dbms_output.put_line(v_mymsg);

END value_update_proc_z;

Just noted this line in your question.

"This isn't an error generated by the system, it is a message set up by a previous developer."

Gosh but it seems like this previous developer was the Prince of Bad Code. Not only did they lumber you with a horrible EAV implementation but they are suppressing the error messages too. A generic message such as the operation could not be completed is no good to anybody, least of all us. You need to know the actual PL/SQL error message so you know why the program is failing, which is the key to fixing the problem.

Now a well-designed system would have some form of logging which displays and/or stores the real SQLERRM. It doesn't seem like you're working on a well-designed system but do you have any error logging in place?

Sign up to request clarification or add additional context in comments.

4 Comments

I changed first select statement into SQL select and it works. Adding the second statement in Execute Immediate causes the same error. FYI, there is other code in the procedure that I left out to simplify my question. The other code is good, has worked correctly for many years. I conclude that my code is still incorrect. BTW, I'm viewing results in browser with htp.p('<div>' || v_mymsg || '</div>'); I believe that my execute immediate is preventing the rest of the procedure from executing.
Well here's the problem: the code we've posted should work. You say it doesn't work for you. But you are doing something different. You haven't posted all the code you're running. We can't debug what we cannot see. You need to post the code that causes the problem. Or you can figure it on your own. The choice is yours.
For some reason, this slight modification of your answer fixed everything. EXECUTE IMMEDIATE( 'SELECT ' || v_old_attrib_name || ' FROM USERS WHERE USERID = :1' ) INTO v_oldattrib_value using uid; I had to add open and closing parenthesis.
Hmmm, frankly that sounds like voodoo but you got it working, that's the main thing.
2

In the second statement

EXECUTE IMMEDIATE 'SELECT' || v_old_attrib_name || 'FROM USERS WHERE USERID...

there should exist at least one space before and after variable v_old_attrib_name like this :

EXECUTE IMMEDIATE 'SELECT ' || v_old_attrib_name || ' FROM USERS WHERE USERID...

1 Comment

That didn't seem to help. I got the same error as before.
0

Here please find below the working example. Let me know what you don't understand...

Issue 1: I you are fetching attrib_value but declared the variable with v_old_attrib_name attributes.attribute_name%TYPE; [Attribute_Name]. It should have been v_old_attrib_name attributes.attrib_name%TYPE;

Issue 2: As stated above in another answer, you were missing some spaces in the query.

Issue 3: You have no out_variable, IDK what was your plan for seeing the return value.

create table attributes (attrib_name varchar2(20), indx number(2));

insert into attributes values('LAST_NAME',1)

create table USERS (userid number(2), last_Name varchar2(100))

insert into users values (12,'Williams')

Procedure:

create or replace PROCEDURE value_update_proc_z(attrib_id INTEGER, uid IN VARCHAR2, attrib_value OUT VARCHAR2)
IS
    v_old_attrib_name attributes.attrib_name%TYPE;
    v_oldattrib_value varchar2(100);

BEGIN
    EXECUTE IMMEDIATE 'SELECT attrib_name FROM attributes WHERE indx = ''' || attrib_id || '''' INTO v_old_attrib_name;

    EXECUTE IMMEDIATE 'SELECT ' || v_old_attrib_name || ' FROM USERS WHERE USERID = ''' || uid || '''' INTO v_oldattrib_value;

    attrib_value := v_old_attrib_name || ' ' || v_oldattrib_value;

END value_update_proc_z;

Execution: Note there is a out variable which we are printing out

DECLARE 
  ATTRIB_ID NUMBER;
  "UID" VARCHAR2(32767);
  ATTRIB_VALUE VARCHAR2(32767);

BEGIN 
  ATTRIB_ID := 1;
  "UID" := 12;
  ATTRIB_VALUE := NULL;

  VALUE_UPDATE_PROC_Z ( ATTRIB_ID, "UID", ATTRIB_VALUE );

  DBMS_OUTPUT.Put_Line('ATTRIB_VALUE = ' || ATTRIB_VALUE);

  DBMS_OUTPUT.Put_Line('');

  COMMIT; 
END; 

Comments

0

I created Sample tables according to your requirement in my schema. I found 3 changes that you have to make in your code to work fine.

1.) v_old_attrib_name attributes.attribute_name%TYPE changed to v_old_attrib_name attributes.attrib_name%TYPE(According to the code in begin block).

I am saying the above change because in the begin block when you are writing the select query on attributes table your column in select clause is "attrib_name" not "attribute_name".

2.) When ever you are using dynamic SQL you can not replace the column or table_name with the variable directly as they are required by oracle to execute the query at run time. To avoid this you will have to add another variable in which you will form the dynamic query and then use it in execute immediate. This you will see in my code below.

3.) When you have written the dynamic SQL make sure that you give appropriate spaces so that nothing will get concatenated to each other. This also you will see in my code below.

Code below:

PROCEDURE value_update_proc_z(attrib_id INTEGER, uid IN VARCHAR2, attrib_value IN VARCHAR2)
is
v_old_attrib_name attributes.attrib_name%TYPE;
    v_oldattrib_value varchar2(100);
    v_mymsg varchar2(2000);
   v_sql varchar2(4000);
BEGIN
    EXECUTE IMMEDIATE 'SELECT attrib_name FROM attributes WHERE indx = ''' || attrib_id || '''' INTO v_old_attrib_name;

    v_sql:='SELECT ' || v_old_attrib_name || ' FROM USERS WHERE USERID = ''' || uid || '''' ;

    EXECUTE IMMEDIATE v_sql INTO v_oldattrib_value;

    v_mymsg := v_old_attrib_name || ' ' || v_oldattrib_value;

END value_update_proc_z;

Hope this will resolve your issue.

Thanks Ankit.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.