Calling SQL scripts through Shell Script

Oracle and Scripting gurus,

I need some help with this script...

I am trying to add the query

SELECT * FROM 
ALL_SYNONYMS 
WHERE SYNONYM_NAME = 'METADATA'

in the current script....

Read the result set and look for the TABLE_NAME field. If the field is pointing to one table eg. METADATA_A, I need to truncate the opposite table METADATA_B.

Then, load the opposite table using SQLLOADER.

Then, switch Synonym by CREATE OR REPLACE PUBLIC SYNONYM to the opposite table.

I tried to add these commands in the script..Somehow, it is not working...

#!/usr/bin/ksh

FILE_NAME=`basename $0 .ksh`

SPOOL_FILE=${PARTNER_DB}/$FILE_NAME.spool
LOG_FILE=${PARTNER_DB}/$FILE_NAME.log

DT_START=`date`

echo "-----------------------------------------" >> $LOG_FILE
echo "Execution Started at :"$DT_START>>$LOG_FILE
echo "Connecting to Oracle...">>$LOG_FILE

tab=`$ORACLE_HOME/bin/sqlplus -s /nolog <<EOF >>$LOG_FILE
      CONNECT ${DB_USER}/${DB_PASSWORD}@${DB_NAME}
      set lines 250
      set trims off
      set wrap off
      set head off
      set pages 0
      spool $SPOOL_FILE

      WHENEVER SQLERROR EXIT SQL.SQLCODE;

      TRUNCATE TABLE METADATA_A;

      spool off;
      exit
EOF`

SQL_STATUS=$?

if [ ${SQL_STATUS} -ne 0 ]
then
   echo "Error in the SQL...."  >>${LOG_FILE}
else
   echo "Successfully Completed...."  >>${LOG_FILE}
fi

DT_END=`date`

echo "Execution Ended at :"$DT_END>>$LOG_FILE
echo "--------------------------------------------">>$LOG_FILE
exit ${SQL_STATUS}

For starters, you can retrieve all of the information from a single SQL statement and base logic from these values:

IFS=$(echo '\012\001') # Newlines only
for LINE in $(
    sqlplus -s /nolog <<!
        connect un/pw@db
        SET FEEDBACK OFF VERIFY OFF ECHO OFF PAGES 0 TRIMSPOOL ON
        COL text FORMAT A150 FOLD_AFTER
        SELECT CASE
                   WHEN table_name = 'METADATA_A'
                       THEN 'sqlldr_table=METADATA_B'
                       ELSE 'sqlldr_table=METADATA_A'
               END text
              ,CASE
                   WHEN table_name = 'METADATA_A'
                       THEN 'dml_cmd=CREATE OR REPLACE PUBLIC SYNONYM METADATA FOR METADATA_B'
                       ELSE 'dml_cmd=CREATE OR REPLACE PUBLIC SYNONYM METADATA FOR METADATA_A'
               END text
        FROM   all_synonyms
        WHERE  synonym_name = 'METADATA';
!)
do
    case ${LINE} in
        dml*)    DML_CMD=${LINE##*=} ;;
        sqlldr*) SQLLDR_TBL=${LINE##*=} ;;
    esac
done
print ${DML_CMD}
print ${SQLLDR_TBL}
DML_CMD=CREATE OR REPLACE PUBLIC SYNONYM METADATA FOR METADATA_A
SQLLDR_TBL=METADATA_A

Thank you for the advice Thomas..

I did get an idea of what you gave me....

I have to truncate the opposite table and then use this sqlldr script to load the table. I have already written a generic sqlldr script to load the table...

#!/usr/bin/ksh

USERID=$1
CONTROL_FILE=$2
DATA_FILE=$3

$ORACLE_HOME/bin/sqlldr UN/PW@DB control=$SCRIPT_DIR/${CONTROL_FILE} log=$SCRIPT_DIR/${CONTROL_FILE}.log data=$FILES/temp/${DATA_FILE}

I will have to use this generic script to load the table. But the table name is hard coded in the Control File....
Do I require two different control files?

One for table A and another for table B.

Control File:

LOAD DATA
append
INTO TABLE METADATA_A
FIELDS TERMINATED BY X'09'
TRAILING NULLCOLS
(
 PRODUCT_CD "SUBSTRB(:PRODUCT_CD , 1, 30)"
,NAME "SUBSTRB(:NAME , 1, 30)"
,NO_CUSTOM_VALUE "SUBSTRB(:NO_CUSTOM_VALUE , 1, 1)"
,CUSTOM_VALUE1_NAME "SUBSTRB(:CUSTOM_VALUE1_NAME , 1, 30)"
,CUSTOM_VALUE1_TYPE "SUBSTRB(:CUSTOM_VALUE1_TYPE , 1, 1)"

.................

Please advise...

Thank You,
Madhu

Certainly, use two control files. You can't bind variables in a control file. Otherwise, you'll need to modify a copy of the control file at run-time as I have had to at times or you'll have to load an intermediate table and use some addition code to move the data from the intermediate table to the METADATA table.

Thomas,

I tried to use your script with two control files MetadataA.ctl and MetadataB.ctl

I am unable to execute the Load.ksh script through your script. The Load.ksh script requires three parameters. 1) USERID 2) CONTROL FILE and 3) DATA FILE.

I am not sure how to pass the control file parameter in your SELECT CASE statement.

Please advice...This is becoming more and more complicated for me and I am scratching my head :confused:

I need to see what your script looks like; please post the section that fails when calling your Load.ksh script and please post the part of Load.ksh that handles the parameters.

Thank you Thomas....

The LoadMetadata.ksh script

USERID_logon

userid=user/pwd@db
#!/usr/bin/ksh

USERID=$1
CONTROL_FILE=$2
DATA_FILE=$3

LOGON=`cat ${PARTNER_DB}/${USERID}_logon`
$ORACLE_HOME/bin/sqlldr $LOGON control=$PARTNER_DB/${CONTROL_FILE} log=$PARTNER_DB/${CONTROL_FILE}.log data=$FILES/temp/${DATA_FILE}

I am calling this Load.ksh through your script....I also need to truncate the opposite table after checking which table is currently in use...

USERID=$1
DATA_FILE=$2
CONTROL_FILEA=MetadataA.ctl
CONTROL_FILEB=MetadataB.ctl

IFS=$(echo '\012\001') # Newlines only
for LINE in $(
    sqlplus -s /nolog <<!
        CONNECT ${DB_USER}/${DB_PASSWORD}@${DB_NAME}
        SET FEEDBACK OFF VERIFY OFF ECHO OFF PAGES 0 TRIMSPOOL ON
        COL text FORMAT A150 FOLD_AFTER
        SELECT CASE
                   WHEN table_name = 'ACXIOM_METADATA_A'
                       THEN 'LoadMetadata.ksh ${USERID} ${CONTROL_FILEB} ${DATA_FILE}'
                       ELSE 'LoadMetadata.ksh ${USERID} ${CONTROL_FILEA} ${DATA_FILE}'
               END text
              ,CASE
                   WHEN table_name = 'ACXIOM_METADATA_A'
                       THEN 'dml_cmd=CREATE OR REPLACE PUBLIC SYNONYM ACXIOM_METADATA FOR ACXIOM_METADATA_B'
                       ELSE 'dml_cmd=CREATE OR REPLACE PUBLIC SYNONYM ACXIOM_METADATA FOR ACXIOM_METADATA_A'
               END text
        FROM   all_synonyms
        WHERE  synonym_name = 'ACXIOM_METADATA';
!)
do
    case ${LINE} in
        dml*)    DML_CMD=${LINE##*=} ;;
        sqlldr*) SQLLDR_TBL=${LINE##*=} ;;
    esac
done
print ${DML_CMD}
print ${SQLLDR_TBL}

Probably this is along the lines of what you want:
WARNING: This is not tested so you assume the risks if you're going to simply paste it into you Load.ksh script.

USERID=$1
DATA_FILE=$2
CONTROL_FILEA=MetadataA.ctl
CONTROL_FILEB=MetadataB.ctl

IFS=$(echo '\012\001') # Newlines only
for LINE in $(
    sqlplus -s /nolog <<!
        CONNECT ${DB_USER}/${DB_PASSWORD}@${DB_NAME}
        SET FEEDBACK OFF VERIFY OFF ECHO OFF PAGES 0 TRIMSPOOL ON
        COL text FORMAT A150 FOLD_AFTER
        SELECT CASE WHEN table_name = 'ACXIOM_METADATA_A'
                    THEN 'TRUNCATE TABLE ACXIOM_METADATA_B;'
                    ELSE 'TRUNCATE TABLE ACXIOM_METADATA_A;'
               END text
              ,CASE WHEN table_name = 'ACXIOM_METADATA_A'
                    THEN 'LoadMetadata.ksh ${USERID} ${CONTROL_FILEB} ${DATA_FILE}'
                    ELSE 'LoadMetadata.ksh ${USERID} ${CONTROL_FILEA} ${DATA_FILE}'
               END text
              ,CASE WHEN table_name = 'ACXIOM_METADATA_A'
                    THEN 'CREATE OR REPLACE PUBLIC SYNONYM ACXIOM_METADATA FOR ACXIOM_METADATA_B'
                    ELSE 'CREATE OR REPLACE PUBLIC SYNONYM ACXIOM_METADATA FOR ACXIOM_METADATA_A'
               END text
        FROM   all_synonyms
        WHERE  synonym_name = 'ACXIOM_METADATA';
!)
do
    case ${LINE} in
        TRUNCATE*|CREATE*)
            sqlplus -s /nolog <<!
connect un/pw
${LINE}
!
 ;;
        LoadMetadata*) exec ${LINE} ;;
    esac
done

This will perform the command in this order:

  1. sqlplus TRUNCATE TABLE ACXIOM_METADATA_B;
  2. LoadMetadata.ksh ${USERID} ${CONTROL_FILEB} ${DATA_FILE}
  3. sqlplus CREATE OR REPLACE PUBLIC SYNONYM ACXIOM_METADATA FOR ACXIOM_METADATA_b;

Thank you again Thomas..

I have loaded a sample of 4 rows in to Table B. Switched the synonym to have Table B and then ran the script...

USERID=$1
DATA_FILE=$2
CONTROL_FILEA=prtnrAcxiomMetadataA.ctl
CONTROL_FILEB=prtnrAcxiomMetadataB.ctl

IFS=$(echo '\012\001') # Newlines only
for LINE in $(
    sqlplus -s /nolog <<!
        CONNECT ${DB_USER}/${DB_PASSWORD}@${DB_NAME}
        SET FEEDBACK OFF VERIFY OFF ECHO OFF PAGES 0 TRIMSPOOL ON
        COL text FORMAT A150 FOLD_AFTER
        SELECT CASE WHEN table_name = 'ACXIOM_METADATA_A'
                    THEN 'TRUNCATE TABLE PARTNERDBA.ACXIOM_METADATA_B;'
                    ELSE 'TRUNCATE TABLE PARTNERDBA.ACXIOM_METADATA_A;'
               END text
              ,CASE WHEN table_name = 'ACXIOM_METADATA_A'
                    THEN 'prtnrLoadMetadata.ksh ${USERID} ${CONTROL_FILEB} ${DATA_FILE}'
                    ELSE 'prtnrLoadMetadata.ksh ${USERID} ${CONTROL_FILEA} ${DATA_FILE}'
               END text
              ,CASE WHEN table_name = 'ACXIOM_METADATA_A'
                    THEN 'CREATE OR REPLACE PUBLIC SYNONYM ACXIOM_METADATA FOR ACXIOM_METADATA_B'
                    ELSE 'CREATE OR REPLACE PUBLIC SYNONYM ACXIOM_METADATA FOR ACXIOM_METADATA_A'
               END text
        FROM   all_synonyms
        WHERE  synonym_name = 'ACXIOM_METADATA';
!)
do
    case ${LINE} in
        TRUNCATE*|CREATE*)
            sqlplus -s /nolog <<!
connect ${DB_USER}/${DB_PASSWORD}@${DB_NAME}
${LINE}
!
 ;;
        prtnrLoadMetadata*) exec ${LINE} ;;
    esac
done

The script didn't truncate Table B, didn't load Table A and didn't replace the synonym to Table A.

The scripts is easy to understand but I am not sure why it is not executing anything...I wish I would atleast see any messages printing...

Looks like the quotes were getting clobbered. Try moving the SQL into a variable as follows:

USERID=$1
DATA_FILE=$2
CONTROL_FILEA=prtnrAcxiomMetadataA.ctl
CONTROL_FILEB=prtnrAcxiomMetadataB.ctl

IFS=$(echo '\012\001') # Newlines only
SQL="
        CONNECT ${DB_USER}/${DB_PASSWORD}@${DB_NAME}
        SET FEEDBACK OFF VERIFY OFF ECHO OFF PAGES 0 TRIMSPOOL ON
        COL text FORMAT A150 FOLD_AFTER
        SELECT 
               CASE WHEN table_name = 'ACXIOM_METADATA_A'
                    THEN 'TRUNCATE TABLE PARTNERDBA.ACXIOM_METADATA_B;'
                    ELSE 'TRUNCATE TABLE PARTNERDBA.ACXIOM_METADATA_A;'
               END text
              ,CASE WHEN table_name = 'ACXIOM_METADATA_A'
                    THEN 'prtnrLoadMetadata.ksh ${USERID} ${CONTROL_FILEB} ${DATA_FILE}'
                    ELSE 'prtnrLoadMetadata.ksh ${USERID} ${CONTROL_FILEA} ${DATA_FILE}'
               END text
              ,CASE WHEN table_name = 'ACXIOM_METADATA_A'
                    THEN 'CREATE OR REPLACE PUBLIC SYNONYM ACXIOM_METADATA FOR ACXIOM_METADATA_B'
                    ELSE 'CREATE OR REPLACE PUBLIC SYNONYM ACXIOM_METADATA FOR ACXIOM_METADATA_A'
               END text
        FROM   all_synonyms
        WHERE  synonym_name = 'ACXIOM_METADATA';
"
for LINE in $(
    sqlplus -s /nolog <<EOF
    ${SQL}
EOF)
do
    case ${LINE} in
        TRUNCATE*|CREATE*)
            sqlplus -s /nolog <<!
connect ${DB_USER}/${DB_PASSWORD}@${DB_NAME}
${LINE}
!
 ;;
        prtnrLoadMetadata*) exec ${LINE} ;;
    esac
done

No Luck Thomas...

Output

Connected.

0 rows deleted.

Connected.

I changed the TRUNCATE statement to just DELETE...It is not going further down the script to execute the shell script and also switch synonym..

USERID=$1
DATA_FILE=$2
CONTROL_FILEA=prtnrAcxiomMetadataA.ctl
CONTROL_FILEB=prtnrAcxiomMetadataB.ctl


IFS=$(echo '\012\001') # Newlines only
SQL="
        CONNECT ${DB_USER}/${DB_PASSWORD}@${DB_NAME}
        SET FEEDBACK OFF VERIFY OFF ECHO OFF PAGES 0 TRIMSPOOL ON
        COL text FORMAT A150 FOLD_AFTER
        SELECT
               CASE WHEN table_name = 'ACXIOM_METADATA_A'
                    THEN 'DELETE PARTNERDBA.ACXIOM_METADATA_B;'
                    ELSE 'DELETE PARTNERDBA.ACXIOM_METADATA_A;'
               END text
              ,CASE WHEN table_name = 'ACXIOM_METADATA_A'
                    THEN '$PARTNER_DB/prtnrLoadMetadata.ksh ${USERID} ${CONTROL_FILEB} ${DATA_FILE}'
                    ELSE '$PARTNER_DB/prtnrLoadMetadata.ksh ${USERID} ${CONTROL_FILEA} ${DATA_FILE}'
               END text
              ,CASE WHEN table_name = 'ACXIOM_METADATA_A'
                    THEN 'CREATE OR REPLACE PUBLIC SYNONYM ACXIOM_METADATA FOR ACXIOM_METADATA_B'
                    ELSE 'CREATE OR REPLACE PUBLIC SYNONYM ACXIOM_METADATA FOR ACXIOM_METADATA_A'
               END text
        FROM   all_synonyms
        WHERE  synonym_name = 'ACXIOM_METADATA';
"
for LINE in $(
    sqlplus -s /nolog <<EOF
    ${SQL}
EOF)
do
    case ${LINE} in
        DELETE*|CREATE*)
            sqlplus -s /nolog <<!
connect ${DB_USER}/${DB_PASSWORD}@${DB_NAME}
${LINE}
!
 ;;
        prtnrLoadMetadata*) exec ${LINE} ;;
    esac
done

Check the syntax; it's fairly simple to find the bugs at this point :rolleyes:

1) You changed:

THEN 'prtnrLoadMetadata.ksh ${USERID} ${CONTROL_FILEB} ${DATA_FILE}'
ELSE 'prtnrLoadMetadata.ksh ${USERID} ${CONTROL_FILEA} ${DATA_FILE}'

to:

THEN '$PARTNER_DB/prtnrLoadMetadata.ksh ${USERID} ${CONTROL_FILEB} ${DATA_FILE}'
ELSE '$PARTNER_DB/prtnrLoadMetadata.ksh ${USERID} ${CONTROL_FILEA} ${DATA_FILE}'

therefore, you must change the KSH expression that you are looking for:

case
...
    $PARTNER_DB/prtnrLoadMetadata*) exec ${LINE} ;;
...
esac

or

case
...
    *prtnrLoadMetadata*) exec ${LINE} ;;
...
esac

2) We both simply missed the missing semicolon here:

THEN 'CREATE OR REPLACE PUBLIC SYNONYM ACXIOM_METADATA FOR ACXIOM_METADATA_B;'  <===
ELSE 'CREATE OR REPLACE PUBLIC SYNONYM ACXIOM_METADATA FOR ACXIOM_METADATA_A;'  <===

Conclusions:

  1. Korn shell can't guess where all your strings are changing. Make sure that your case statement can actually match the patter returning from sqlplus
  2. SQL must be valid SQL. Check those semicolons.

Thank you very much for the conclusions....I am still learning a whole lot of it...

I have also changed it as per your suggestions...But I think I am going wrong somewhere....It is not recognizing the shell script inside the SQL.

Connected.

0 rows deleted.

prtnrSynonym.ksh[57]: /partnerdb/bin/prtnrLoadMetadata.ksh dataload prtnrAcxiomMetadataA.:  not found

I tried both ways....

*prtnrLoadMetadata* and $PARTNER_DB/prtnrLoadMetadata*

USERID=$1
DATA_FILE=$2
CONTROL_FILEA=prtnrAcxiomMetadataA.ctl
CONTROL_FILEB=prtnrAcxiomMetadataB.ctl

IFS=$(echo '\012\001') # Newlines only
SQL="
        CONNECT ${DB_USER}/${DB_PASSWORD}@${DB_NAME}
        SET FEEDBACK OFF VERIFY OFF ECHO OFF PAGES 0 TRIMSPOOL ON
        COL text FORMAT A150 FOLD_AFTER
        SELECT
               CASE WHEN table_name = 'ACXIOM_METADATA_A'
                    THEN 'DELETE PARTNERDBA.ACXIOM_METADATA_B;'
                    ELSE 'DELETE PARTNERDBA.ACXIOM_METADATA_A;'
               END text
              ,CASE WHEN table_name = 'ACXIOM_METADATA_A'
                    THEN '$PARTNER_DB/prtnrLoadMetadata.ksh ${USERID} ${CONTROL_FILEB} ${DATA_FILE}'
                    ELSE '$PARTNER_DB/prtnrLoadMetadata.ksh ${USERID} ${CONTROL_FILEA} ${DATA_FILE}'
               END text
              ,CASE WHEN table_name = 'ACXIOM_METADATA_A'
                    THEN 'CREATE OR REPLACE PUBLIC SYNONYM ACXIOM_METADATA FOR ACXIOM_METADATA_B;'
                    ELSE 'CREATE OR REPLACE PUBLIC SYNONYM ACXIOM_METADATA FOR ACXIOM_METADATA_A;'
               END text
        FROM   all_synonyms
        WHERE  synonym_name = 'ACXIOM_METADATA';
"
for LINE in $(
    sqlplus -s /nolog <<EOF
    ${SQL}
EOF)
do
    case ${LINE} in
        DELETE*|CREATE*)
            sqlplus -s /nolog <<!
connect ${DB_USER}/${DB_PASSWORD}@${DB_NAME}
${LINE}
!
 ;;
        $PARTNER_DB/prtnrLoadMetadata*) exec ${LINE} ;;
    esac
done

Sorry, my bad again!

Your line is getting chopped from sqlplus so change:

SET FEEDBACK OFF VERIFY OFF ECHO OFF PAGES 0 TRIMSPOOL ON

to:

SET FEEDBACK OFF VERIFY OFF ECHO OFF PAGES 0 TRIMSPOOL ON LINES 300

This gives you enough space for your path and filename. Add more if 300 character isn't enough.

I also changed this:

        $PARTNER_DB/prtnrLoadMetadata*) exec ${LINE} ;;

to:

        $PARTNER_DB/prtnrLoadMetadata*) eval ${LINE} ;;

Thank You Thomas....

It is working fine....I need to run this and test this on real tables and see how it goes..

I really appreciate your time!

Thomas....

All these days I was trying to do this and with your code I was able to execute successfully...

When the code review happened, they want to pass the table name as a parameter and told me to code it this way...I tried to put pieces together and couldn't go beyond this point..

I think this opens and closes connections. Is there any better way to do this?

#!/usr/bin/ksh
###################################################################################
SetupEnvironment () {
  ConnectionParameter="dataload/dataload@dadb"
}
###################################################################################
GetWorkingTableName() {
 sqlplus -silent /nolog @getAcxTableName.sql $ConnectionParameter 1.tmp >/dev/null
 status=$?
 if [[ $status -ne 0 ]]
 then
   print "Error from sql script getAcxTableName.sql"
   exit 1
 fi
 grep ACX 1.tmp | read tempval
 print "$tempval"
}
###################################################################################
Truncate_Table () {
  sqlplus $ConnectionParameter <<!
whenever sqlerror exit failure
set serveroutput on size 1000000
exec p_truncate("$1","$2");
exit;
!
  print "Error from sql"
}
###################################################################################
RunTheETLJob () {
 print "dummy"
}
###################################################################################
SwitchSynonym () {
 CREATE OR REPLACE PUBLIC SYNONYM ACXIOM_PROSPECT FOR $WorkTableName
 print "dummy"
}

################ M A I N   P R O G R A M ##########################################
SetupEnvironment

GetWorkingTableName|read WorkTableName
print "Tablename is $WorkTableName"
#exit 0
Truncate_Table $WorkTableName
export USER_VAR_0=$WorkTableName
print "Truncating table $WorkTableName"
RunTheETLJob
SwitchSynonym $WorkTableName
print "Switched synonym to $WorkTableName
exit 0;

I would really appreciate any help on this...

What do you mean that it opens and closes connections?

Thomas,

I think I am opening and closing connections in every function. I am not sure of this too...I am not sure if this is a good way to code. I am learning everyday...

I could put all these pieces together after all these days..I want to pass the table name as a parameter during runtime which was received from the getAcxTableName.sql

whenever sqlerror exit failure
set feed off
set echo off
set pages 0
set trimspool on
connect &1
spool &2
select decode(table_name,'ACXIOM_PROSPECT_B','ACXIOM_PROSPECT_A','ACXIOM_PROSPECT_A','ACXIOM_PROSPECT_B','')
from all_synonyms where SYNONYM_NAME='ACXIOM_PROSPECT'
/
spool off;
exit;

I am really going crazy here!!

Yes, you are opening and closing connections every time that you run sqlplus and provide it logon credentials. An alternative technique, which, since you are struggling so much, I do not recommend is to run sqlplus as a coprocessor, which keeps one session open no matter how many times that you need to interact with Oracle. This has advantages and disadvantages and, for me, the advantanges are substantial enough that I have developed libraries of functions to seemlessly and simply interact with up to four (which is the limit for ksh88) databases via coprocessors. However it is harder to do so you will be better served using the method that you been using. You are not doing that much work and opening and closing sessions isn't costing you that much for this load operation. A coprocessor implementation, while it would be a great learning experience, is not for the beginner.

As far as question goes, I'm not sure I understand what you are asking me. Is your script working or not? If not, what's wrong? If you want to combine all of your sqlplus calls then you certainly can but it's going to require more advanced sqlplus techniques or to do everything in PL/SQL. At any rate, it the script works, and, if performance isn't an issue (which, judging by the script simply can't possibly be the case), and, if you have plenty of other work to do, leave it alone and move on. When you get better and scripting, you can make changes then.

By the way, one comment regarding your script. You are logging on two different ways, once with the logon credentials as sqlplus parameters and once inside the here document. I would stick to the second method since it keeps your database password from being viewed by other Unix users.