1

I'm trying to write a procedure that automatically creates indexes for foreign keys that don't have one yet. I created a function called SNK_VERIFICA_NOME_IDX which is supposed to ensure the index name is unique by appending a numeric suffix if the name already exists.

Here's the function and procedure:

CREATE OR REPLACE FUNCTION SNK_VERIFICA_NOME_IDX(P_INDICE VARCHAR2)
RETURN VARCHAR2
IS
  P_COUNT INT;
  P_QTD INT;
  P_NOVONOME VARCHAR2(100);
BEGIN
  P_NOVONOME := P_INDICE;
  SELECT COUNT(1) INTO P_COUNT FROM USER_INDEXES WHERE INDEX_NAME = P_INDICE;      
  IF P_COUNT <> 0 THEN
    P_QTD := 1;        
    WHILE (P_COUNT <> 0)
    LOOP
      P_NOVONOME := P_INDICE || P_QTD;
      SELECT COUNT(1) INTO P_COUNT FROM USER_INDEXES WHERE INDEX_NAME = P_INDICE || P_QTD;          
      P_QTD := P_QTD + 1;
    END LOOP;
  END IF;  
  RETURN P_NOVONOME;
END;
/

CREATE OR REPLACE PROCEDURE STP_GERA_INDICES_FKS
AS
BEGIN
DECLARE
  P_COUNT INT;
  P_COMANDO VARCHAR2(4000);
  P_TABELA VARCHAR2(50);
  P_INDICE VARCHAR2(50);
  P_COLUNAS VARCHAR2(1000);
  P_QTD INT;
  CURSOR CURIDX IS
  SELECT TABLE_NAME, SUBSTR(TABLE_NAME,1,10) || '_IDX_FK_' || SUBSTR(REPLACE(FK_COLUMNS,',','_'),1,10) AS INDEX_NAME, FK_COLUMNS AS INDEX_COLUMNS FROM
  (SELECT CASE WHEN B.TABLE_NAME IS NULL THEN 'unindexed' ELSE 'indexed' END AS STATUS,
          A.TABLE_NAME, A.CONSTRAINT_NAME, A.FK_COLUMNS, B.INDEX_NAME, B.INDEX_COLUMNS
     FROM (SELECT A.TABLE_NAME, A.CONSTRAINT_NAME,
                  LISTAGG(A.COLUMN_NAME, ',') WITHIN GROUP (ORDER BY A.POSITION) FK_COLUMNS
             FROM USER_CONS_COLUMNS A, USER_CONSTRAINTS B
            WHERE A.CONSTRAINT_NAME = B.CONSTRAINT_NAME
              AND B.CONSTRAINT_TYPE = 'R'
              AND A.TABLE_NAME LIKE 'T%'
         GROUP BY A.TABLE_NAME, A.CONSTRAINT_NAME) A,
          (SELECT TABLE_NAME, INDEX_NAME,
                  LISTAGG(C.COLUMN_NAME, ',') WITHIN GROUP (ORDER BY C.COLUMN_POSITION) INDEX_COLUMNS
             FROM USER_IND_COLUMNS C
            WHERE C.TABLE_NAME LIKE 'T%'
         GROUP BY TABLE_NAME, INDEX_NAME) B
    WHERE A.TABLE_NAME = B.TABLE_NAME(+)
      AND B.INDEX_COLUMNS(+) LIKE A.FK_COLUMNS || '%'
    ORDER BY 1 DESC, 2) X
  WHERE STATUS = 'unindexed';
BEGIN
  OPEN CURIDX;
  LOOP
    FETCH CURIDX INTO P_TABELA, P_INDICE, P_COLUNAS;
    EXIT WHEN CURIDX%NOTFOUND;
    P_INDICE := SNK_VERIFICA_NOME_IDX(P_INDICE);
    P_COMANDO := 'CREATE INDEX ' || P_INDICE || ' ON ' || P_TABELA || '(' || P_COLUNAS || ') tablespace SANKIND;';
    DBMS_OUTPUT.PUT_LINE(P_COMANDO);
  END LOOP;
  CLOSE CURIDX;
END;
END;
/

The problem is that even with this logic, the code still attempts to create two indexes with the same name:

SQL> CREATE INDEX XXXX_IDX_FK_XXXX ON XXXX(XXXXX);
Index created.

SQL> CREATE INDEX XXXX_IDX_FK_XXXX ON XXXX(XXXXX);
ERROR at line 1:
ORA-00955: name is already used by an existing object

Clearly, the function is not correctly ensuring unique index names. Can someone help me identify what’s wrong with the logic in the SNK_VERIFICA_NOME_IDX function or the way it’s being used in the procedure?

Edit: the problem is that I am using dbms_output.put_line.

The reason why using DBMS_OUTPUT.PUT_LINE doesn’t work in my case is because it only prints the SQL command as a string — it does not execute the statement.

As a result, the index is never actually created, so the Oracle data dictionary (e.g., USER_INDEXES) is not updated. My function SNK_VERIFICA_NOME_IDX, which checks if the index name already exists, keeps returning that the name is available — even though you've already "printed" a command with that name — because the command wasn't executed.

In contrast, when I use EXECUTE IMMEDIATE, the index is actually created in the database, which updates the data dictionary immediately. This allows your name-checking logic to work correctly on the next iteration.

9
  • The problem in itself (generating unique index names) is interesting, however I don't see how you reach the situation: how would a foreign key exist without a unique index? See this db<>fiddle showing Oracle preventing this to happen in the first place. Commented Jul 22 at 15:00
  • "Edit: the problem is that I am using dbms_output.put_line.": that's a "whoops" situation :-) However I would be interested in how your database could have lost the unique indices while keeping foreign keys on it. Commented Jul 22 at 15:02
  • 3
    @GuillaumeOutters I think what he means is the PK on the parent table is present, but he wishes to add an index on the child table to support joins from parent->child. Commented Jul 22 at 15:11
  • @Ben you're right, I mistook "unique names of indices" for "unique names of unique indices" (with the implicit fact that uniqueness should go on parent). Coffee time for me! Commented Jul 22 at 15:17
  • 4
    @astora, tempting though the idea is, I actually suggest you don't do this, instead produce a report of foreign keys, together with which do and do not have supporting indexes. Then check, and add the indexes individually. This is because there may be an index already, perhaps with additional columns, or columns in different order, or a different name, which will do the job. Commented Jul 22 at 15:19

1 Answer 1

3

Can someone help me identify what’s wrong with the logic in the SNK_VERIFICA_NOME_IDX function or the way it’s being used in the procedure?

You are using COUNT to count items in the data dictionary but you are not modifying the data dictionary so when a duplicate name is checked the duplication is not found because the data dictionary has not changed.

For example:

CREATE TABLE TABCDEFGHI1 (id NUMBER PRIMARY KEY);

CREATE TABLE TABCDEFGHI2 (
  t_id NUMBER
       PRIMARY KEY,
  id   NUMBER
       REFERENCES TABCDEFGHI1(id)
);

CREATE TABLE TABCDEFGHI3 (
  id NUMBER
     REFERENCES TABCDEFGHI1(id)
     REFERENCES TABCDEFGHI2(t_id)
);

Then the output is:

CREATE INDEX TABCDEFGHI_IDX_FK_ID ON TABCDEFGHI2(ID) tablespace SANKIND;
CREATE INDEX TABCDEFGHI_IDX_FK_ID ON TABCDEFGHI3(ID) tablespace SANKIND;
CREATE INDEX TABCDEFGHI_IDX_FK_ID ON TABCDEFGHI3(ID) tablespace SANKIND;

All 3 indexes are given identical names.

If you want to track duplicates, without changing the data dictionary, then the you need to maintain the state of the number of counts you have found for each index (probably in an associative array or something similar) and increment your local data structure when you generate each index name.

fiddle

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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.