Filewatcher File Search
FTP Search
  
Directory 
  
Content Search 
   
pkg://DDL-Oracle-1.04.tar.gz:46377/DDL-Oracle-1.04/defrag.pl  downloads

#! /usr/bin/perl -w

# $Id: defrag.pl,v 1.17 2001/01/27 16:23:25 rvsutherland Exp $
#
# Copyright (c) 2000, 2001 Richard Sutherland - United States of America
#
# See COPYRIGHT section in pod text below for usage and distribution rights.

use Cwd;
use DBI;
use DDL::Oracle;
use English;
use Getopt::Long;

use strict;

my %args;
my %uniq;

my @constraints;
my @export_objects;
my @export_temps;
my @logfiles;
my @perf_tables = ( 
                   'DBA_ALL_TABLES',
                   'DBA_INDEXES', 
                   'DBA_PART_INDEXES',
                   'DBA_PART_TABLES',
                   'DBA_SEGMENTS', 
                   'DBA_TABLES', 
                   'THE_CONSTRAINTS',
                   'THE_IOTS',
                   'THE_INDEXES',
                   'THE_PARTITIONS',
                   'THE_TABLES',
                  );
my @sizing_array;

my $add_ndx_log;
my $add_tbl_log;
my $add_temp_log;
my $add_temp_sql;
my $alttblsp;
my $aref;
my $create_ndx_ddl;
my $create_tbl_ddl;
my $create_temp_ddl;
my $date;
my $dbh;
my $drop_all_log;
my $drop_ddl;
my $drop_temp_ddl;
my $drop_temp_log;
my $drop_temp_sql;
my $expdir;
my $exp_log;
my $header10;
my $home = $ENV{HOME}
        || $ENV{LOGDIR}
        || ( getpwuid( $REAL_USER_ID ) )[7]
        || die "\nCan't determine HOME directory.\n";
my $imp_log;
my $logdir;
my $obj;
my $other_constraints;
my $partitions;
my $prefix;
my $prttn_exp_log;
my $prttn_exp_par;
my $prttn_exp_text;
my $prttn_imp_log;
my $prttn_imp_par;
my $prttn_imp_text;
my $row;
my $script;
my $sqldir;
my $sth;
my $stmt;
my $tblsp;
my $text;
my $user = getlogin
        || scalar getpwuid( $REAL_USER_ID )
        || undef;

########################################################################

set_defaults();

if (
         @ARGV    == 0
     or  $ARGV[0] eq "?"
     or  $ARGV[0] eq "-?"
     or  $ARGV[0] eq "-h"
     or  $ARGV[0] eq "--help"
   ) 
{
  print_help();
  exit 0;
}

print "\n$0 is being executed by $user\non ", scalar localtime,"\n\n";
get_args();
print "Generating files to defrag Tablespace $tblsp.\n",
      "Using Tablespace $alttblsp for partition operations.\n\n";
initialize_queries();

#
# Display user options, and save them in .defrag.rc
#

delete $args{ sid } if $args{ sid } eq "";
open RC, ">$home/.defragrc" or die "Can't open $home/.defragrc:  $!\n";
KEY:
  foreach my $key ( sort keys %args ) 
  {
    next KEY unless (
                         $key eq "sid"
                      or $key eq "logdir"
                      or $key eq "sqldir"
                      or $key eq "prefix"
                      or $key eq "expdir"
                      or $key eq "resize"
                    );
    print "$key = $args{ $key }\n";
    print RC "$key = $args{ $key }\n";
  }
close RC or die "Can't close $home/.defragrc:  $!\n";

print "\nWorking...\n\n";

########################################################################

#
# Now we're ready -- start dafriggin' defraggin'
#

# The 10 steps below issue queries mostly comprised of 5 main queries,
# sometimes doing UNIONs and/or MINUSes among them.  The query results
# are stored in temporary tables for performance reasons.
#
# See sub 'initialize_queries' for the queries and their descriptions.
#

# Step 1 - Export the stray partitions -- those in our tablespace whose
#          table also has partitions in at least one other tablespace.
#          If said partitions exist, there will be 2 exports.  After the
#          first export, for each such partition:
#            a) Create a Temp table mirroring the partition.
#            b) Create indexes on the Temp table matching the LOCAL 
#               indexes on the partitioned table.
#            c) Create a PK matching the PK of the partitioned table,
#               if any.
#            d) EXCHANGE the Temp table with the partition.
#            e) MOVE the [now empty] partition to the alternate tablespace.
#
#          With the data now in the Temp table, the Temp table gets 
#          treated the same as other regular tables in our tablespace
#          (see Steps 2 - 9), but has added operations following the
#          creation of its indexes (same as the LOCAL indexes on the 
#          partition) and the addition of its PK (if any).
#
#            a) the Temp table does an EXCHANGE PARTITION so that the
#               data (which was imported into the Temp table) rejoins
#               the partitioned table.
#            b) the [now empty] Temp table is DROPped.
#
#            c) REBUILD all Global indexes (if any) on the partitioned
#               table(s).
#
#          NOTE:  Two 'fall back' scripts are created which are to be
#                 used ONLY in the event that problems occur during
#                 Step 1 (Shell #2 when such partitions exist).
#
#                  ***  DO NOT PROCEED IF Shell #2 HAS ERRORS ***
#
#                 Shells #8 and #9  will restore the data to the original
#                 condition Their Steps are:
#                   a) DROP the Temp table(s).
#                   b) TRUNCATE the partitions
#                   c) MOVE the partitions back to our tablespace
#                   d) Import the data back into the partitions.
#

$stmt =
      "
       SELECT
              owner
            , segment_name
            , partition_name
            , segment_type
            , partitioning_type
            , analyzed
       FROM
              THE_PARTITIONS
       ORDER
          BY
              1, 2, 3
      ";

$sth = $dbh->prepare( $stmt );
$sth->execute;
$aref = $sth->fetchall_arrayref;

foreach $row ( @$aref )
{
  my ( 
       $owner, 
       $table, 
       $partition, 
       $type, 
       $partitioning_type,
       $analyzed
     ) = @$row;

  $obj = DDL::Oracle->new(
                           type => 'exchange table',
                           list => [
                                     [
                                       "$owner",
                                       "$table:$partition",
                                     ]
                                   ],
                         );
  my $create_tbl = $obj->create;
  # Remove REM lines created by DDL::Oracle
  $create_tbl = ( join "\n",grep !/^REM/,split /\n/,$create_tbl )."\n\n";

  my $temp = "${tblsp}_${date}_" . unique_nbr();

  push @export_temps,   "\L$owner.$table:$partition";
  push @export_objects, "\L$owner.$temp";

  # Change the CREATE TABLE statement to create the temp
  my $ownr    = escaped_dollar_signs( $owner );
  my $tabl    = escaped_dollar_signs( $table );
  $create_tbl =~ s|\L$ownr.$tabl|\L$owner.$temp|g;

  my $exchange = index_and_exchange( $temp, @$row );

  $obj = DDL::Oracle->new(
                           type => 'table',
                           list => [
                                     [
                                       "$owner",
                                       "$temp",
                                     ]
                                   ],
                         );
  my $drop_tbl = $obj->drop;
  # Remove REM lines created by DDL::Oracle
  $drop_tbl = ( join "\n", grep !/^REM/, split /\n/, $drop_tbl ) . "\n\n";

  $obj = DDL::Oracle->new(
                           type => 'table',
                           list => [
                                     [
                                       "$owner",
                                       "$table:$partition",
                                     ]
                                   ],
                         );
  my $resize = $obj->resize;
  # Remove REM lines created by DDL::Oracle
  $resize =  ( join "\n", grep !/^REM/, split /\n/, $resize ) . "\n\n";
  $resize =~ s|\;|\nTABLESPACE \L$tblsp \;\n\n|;

  my $drop_temp =  $drop_tbl .
                   trunc( @$row ) .
                   $resize;

  $create_temp_ddl  = group_header( 1 )   unless $create_temp_ddl;
  $create_temp_ddl .= $create_tbl .
                      $exchange .
                      move( @$row, $alttblsp );

  $drop_ddl         = group_header( 2 )   unless $drop_ddl;
  $drop_ddl        .= $drop_tbl;

  $create_tbl_ddl   = group_header( 7 )   unless $create_tbl_ddl;
  $create_tbl_ddl  .= $create_tbl;

  $create_ndx_ddl   = group_header( 9 )   unless $create_ndx_ddl;
  $create_ndx_ddl  .= $exchange .
                      $drop_tbl;      

  $drop_temp_ddl    = group_header( 15 )  unless $drop_temp_ddl;
  $drop_temp_ddl   .= $drop_temp;
}

#
# Step 2 - Drop all Foreign Keys referenceing our tables and IOT's or
#          referenceing the tables of our other indexes.  NOTE:  our
#          indexes may not be the target of a foreign key, but for 
#          simplicity purposes if the index's table holds said target
#          (i.e., its index is in some other tablespace but it belongs
#          to the same table as our index), we'll drop the FK anyway --
#          it won't hurt anything and we promise to put it back.
#

$stmt =
      "
       SELECT --+ use_hash(c r)
              c.owner
            , c.constraint_name
       FROM
              THE_CONSTRAINTS  c
            , THE_CONSTRAINTS  r
       WHERE
                  c.constraint_type   = 'R'
              AND c.r_owner           = r.owner
              AND c.r_constraint_name = r.constraint_name
              AND (
                      r.owner
                    , r.table_name
                  ) IN (
                         SELECT
                                owner
                              , table_name
                         FROM
                                THE_TABLES
                         UNION ALL
                         SELECT
                                owner
                              , table_name
                         FROM
                                THE_IOTs
                         UNION ALL
                         SELECT
                                owner
                              , table_name
                         FROM
                                THE_INDEXES
                       )
       ORDER
          BY
              1, 2
      ";

$sth = $dbh->prepare( $stmt );
$sth->execute;
my $fk_aref = $sth->fetchall_arrayref;

$obj = DDL::Oracle->new(
                         type => 'constraint',
                         list => $fk_aref,
                       );

$drop_ddl .= group_header( 3 ) . $obj->drop    if @$fk_aref;

#
# Step 3 - Drop and create the tables.  NOTE:  the DROP statements are in
#          one file followed by COALESCE tablespace statements, and the
#          CREATE statements are put in a separate file.  The assumption
#          here is that the user will verify that the DROP and COALESCE
#          statements executed OK before executing the CREATE tables file.
#

$stmt =
      "
       SELECT DISTINCT
              owner
            , table_name
            , analyzed
       FROM
            (
              SELECT
                     owner
                   , table_name
                   , analyzed
              FROM
                     THE_TABLES
              UNION ALL
              SELECT
                     owner
                   , table_name
                   , analyzed
              FROM
                     THE_IOTs
            )
       ORDER
          BY
              1, 2
      ";

$sth = $dbh->prepare( $stmt );
$sth->execute;
$aref = $sth->fetchall_arrayref;

if ( @$aref )
{
  $obj = DDL::Oracle->new(
                           type => 'table',
                           list => $aref,
                         );

  $drop_ddl       .= group_header( 4 ) . $obj->drop;

  $create_tbl_ddl .= group_header( 8 ) . $obj->create;

  foreach $row ( @$aref )
  {
    my ( $owner, $table, $analyzed ) = @$row;

    push @export_objects, "\L$owner.$table";

    if ( $analyzed eq 'YES' )
    {
      $create_ndx_ddl .= group_header( 10 )    unless $header10++;

      $create_ndx_ddl .= "PROMPT " .
                         "ANALYZE TABLE \L$owner.$table\n\n" .
                         "ANALYZE TABLE \L$owner.$table " .
                         "ESTIMATE STATISTICS ;\n\n";
    }
  }

}

#
# Step 4 - Drop all Primary Key, Unique and Check constraints on the tables
#          of our indexes (those on our tables disappeared with the DROP
#          TABLE statements).
#

$stmt =
      "
       SELECT
              owner
            , constraint_name
       FROM
              THE_CONSTRAINTS
       WHERE
                  constraint_type IN ('P','U','C')
              AND (
                      owner
                    , table_name
                  ) IN (
                         SELECT
                                owner
                              , table_name
                         FROM
                                THE_INDEXES
                         MINUS
                         (
                           SELECT
                                  owner
                                , table_name
                           FROM
                                  THE_TABLES
                           UNION ALL
                           SELECT
                                  owner
                                , table_name
                           FROM
                                  THE_IOTs
                         )
                       )
       ORDER
          BY
              1, 2
      ";

$sth = $dbh->prepare( $stmt );
$sth->execute;
$aref = $sth->fetchall_arrayref;

$obj = DDL::Oracle->new(
                         type => 'constraint',
                         list => $aref,
                       );

$drop_ddl .= group_header( 5 ) . $obj->drop    if @$aref;

#
# Step 5 - Drop all of our indexes, unless they are the supporting index
#          of a Primary Key or Unique constraint -- these disappeared in
#          the preceding step.
#

$stmt =
      "
       SELECT
              owner
            , index_name
       FROM 
              THE_INDEXES i
       WHERE
              NOT EXISTS   (
                             SELECT
                                    null
                             FROM
                                    THE_CONSTRAINTS
                             WHERE
                                        owner           = i.owner
                                    AND constraint_name = i.index_name
                           )
              AND (
                      owner
                    , table_name
                  ) NOT IN (
                             SELECT
                                    owner
                                  , table_name
                             FROM
                                    THE_TABLES
                             UNION ALL
                             SELECT
                                    owner
                                  , table_name
                             FROM
                                    THE_IOTs
                           )
       ORDER
          BY
              1, 2
      ";

$sth = $dbh->prepare( $stmt );
$sth->execute;
$aref = $sth->fetchall_arrayref;

$obj = DDL::Oracle->new(
                         type => 'index',
                         list => $aref,
                       );

$drop_ddl .= group_header( 6 ) . $obj->drop    if @$aref;

#
# Step 6 - Create ALL indexes.
#

$stmt =
      "
       SELECT
              owner
            , index_name
            , table_name
            , analyzed
       FROM 
              THE_INDEXES
       ORDER
          BY
              1, 2
      ";

$sth = $dbh->prepare( $stmt );
$sth->execute;
$aref = $sth->fetchall_arrayref;

$obj = DDL::Oracle->new(
                         type => 'index',
                         list => $aref,
                       );

$create_ndx_ddl .= group_header( 10 )    unless $header10++;

$create_ndx_ddl .= $obj->create    if @$aref;

foreach $row ( @$aref )
{
  my ( $owner, $index, $table, $analyzed ) = @$row;

  if ( $analyzed eq 'YES' )
  {
    $create_ndx_ddl .= "PROMPT " .
                       "ANALYZE INDEX \L$owner.$index\n\n" .
                       "ANALYZE INDEX \L$owner.$index\n" .
                       "   ESTIMATE STATISTICS ;\n\n" .
                       "PROMPT " .
                       "ANALYZE TABLE \L$owner.$table\n\n" .
                       "ANALYZE TABLE \L$owner.$table\n" .
                       "   ESTIMATE STATISTICS " .
                       "FOR ALL INDEXED COLUMNS ;\n\n";
  }
}

#
# Step 7 - Create all Primary Key, Unique and Check constraints on our
#          tables and on the tables of our indexes.  NOTE:  do not create
#          the constraints for the IOT tables -- their primary keys were
#          defined in the CREATE TABLE statements.
#

$stmt =
      "
       SELECT
              owner
            , constraint_name
            , constraint_type
            , search_condition
       FROM
              dba_constraints
       WHERE
                  constraint_type     IN ('P','U','C')
              AND (
                      owner
                    , table_name
                  ) IN (
                         SELECT
                                owner
                              , table_name
                         FROM
                                THE_TABLES
                         UNION ALL
                         SELECT
                                owner
                              , table_name
                         FROM
                                THE_INDEXES
                       )
       ORDER
          BY
              1, 2
      ";

$dbh->{ LongReadLen } = 8192;    # Allows SEARCH_CONDITION length of 8K
$dbh->{ LongTruncOk } = 1;

$sth = $dbh->prepare( $stmt );
$sth->execute;
$aref = $sth->fetchall_arrayref;

foreach $row ( @$aref )
{
  my ( $owner, $constraint_name, $cons_type, $condition, ) = @$row;

  if ( $cons_type ne 'C' )
  {
    push @constraints, [ $owner, $constraint_name ];
  }
  elsif ( $condition !~ /IS NOT NULL/ )  # NOT NULL is part of CREATE TABLE
  {
    push @constraints, [ $owner, $constraint_name ];
  }
}

$obj = DDL::Oracle->new(
                         type => 'constraint',
                         list => \@constraints,
                       );

$create_ndx_ddl .= group_header( 11 ) . $obj->create    if @constraints;

#
# Step 8 - Create all Check constraints on our IOT tables (their PK was
#          part of the CREATE TABLE, and they can't have any other indexes,
#          thus no UK's)
#

$stmt =
      "
       SELECT
              owner
            , constraint_name
            , constraint_type
            , search_condition
       FROM
              dba_constraints
       WHERE
                  constraint_type = 'C'
              AND (
                      owner
                    , table_name
                  ) IN (
                         SELECT
                                owner
                              , table_name
                         FROM
                                THE_IOTs
                       )
       ORDER
          BY
              1, 2
      ";

$dbh->{ LongReadLen } = 8192;    # Allows SEARCH_CONDITION length of 8K
$dbh->{ LongTruncOk } = 1;

$sth = $dbh->prepare( $stmt );
$sth->execute;
$aref = $sth->fetchall_arrayref;

@constraints = ();
foreach $row ( @$aref )
{
  my ( $owner, $constraint_name, $cons_type, $condition, ) = @$row;

  if ( $condition !~ /IS NOT NULL/ )  # NOT NULL is part of CREATE TABLE
  {
    push @constraints, [ $owner, $constraint_name ];
  }
}

$obj = DDL::Oracle->new(
                         type => 'constraint',
                         list => \@constraints,
                       );

$create_ndx_ddl .= group_header( 12 ) . $obj->create    if @constraints;

#
# Step 9 - Recreate all Foreign Keys referenceing our tables and IOT's or
#          referenceing the tables of our other indexes.  Use the same list
#          used in Step 2 to drop them ($fk_aref).
#

$obj = DDL::Oracle->new(
                         type => 'constraint',
                         list => $fk_aref,
                       );

$create_ndx_ddl .= group_header( 13 ) . $obj->create    if @$fk_aref;

#
# Step 10 - REBUILD all UNUSABLE indexes/index [sub]partitions. These are
#           the non-partitioned or Global partitioned indexes on THE
#           PARTITIONS.
#

$stmt =
      "
       SELECT 
              owner
            , index_name
       FROM
              dba_indexes
       WHERE
              (
                  owner
                , table_name
              ) IN (
                     SELECT
                            owner
                          , segment_name
                     FROM
                            THE_PARTITIONS
                   )
       MINUS
       SELECT              -- Ignore partitioned, LOCAL indexes
              owner
            , index_name
       FROM
              dba_part_indexes
       WHERE
              locality = 'LOCAL'
       ORDER
          BY
              1
      ";

$sth = $dbh->prepare( $stmt );
$sth->execute;
$aref = $sth->fetchall_arrayref;

$obj = DDL::Oracle->new(
                         type => 'index',
                         list => $aref,
                       );

$create_ndx_ddl .= group_header( 14 ) . $obj->resize    if @$aref;

#
# It's hard to believe, but maybe they gave us an empty tablespace
# to practice on.
#

die "\n***Error:  Tablespace $tblsp is empty. 
           Doest thou take me for a fool?\n\n"
     unless $create_tbl_ddl . $create_ndx_ddl;

#
# OK, we're ligit.  Coalesce all data/index tablespaces
#

$stmt =
      "
       SELECT
              LOWER(tablespace_name)
       FROM
              dba_tablespaces  t
       WHERE
                  status            = 'ONLINE'
              AND contents         <> 'TEMPORARY'
              AND tablespace_name  <> 'SYSTEM'
              AND extent_management = 'DICTIONARY'
       MINUS
       SELECT
              LOWER(tablespace_name)
       FROM
              dba_segments
       WHERE
              segment_type    = 'ROLLBACK'
       ORDER
          BY
              1
      ";

$sth = $dbh->prepare( $stmt );
$sth->execute;
$aref = $sth->fetchall_arrayref;

foreach $row ( @$aref )
{
  $drop_ddl .= "PROMPT ALTER TABLESPACE @$row->[0] COALESCE\n\n" .
               "ALTER TABLESPACE @$row->[0] COALESCE ;\n\n",
}

# Get rid of double blank lines
$drop_ddl        =~ s|\n\n+|\n\n|g;
$drop_temp_ddl   =~ s|\n\n+|\n\n|g;
$create_tbl_ddl  =~ s|\n\n+|\n\n|g;
$create_ndx_ddl  =~ s|\n\n+|\n\n|g;
$create_temp_ddl =~ s|\n\n+|\n\n|g;

drop_perf_temps();

#
# Wrap it up -- open, write and close all files
#

if ( $create_temp_ddl )
{
  $add_temp_sql = "$sqldir/$prefix${tblsp}_add_temp.sql";
  print "Create temps            : $add_temp_sql\n";
  write_file( $add_temp_sql, $create_temp_ddl, 'REM' );

  $drop_temp_sql = "$sqldir/$prefix${tblsp}_drop_temp.sql";
  print "Drop temps              : $drop_temp_sql\n";
  write_file( $drop_temp_sql, $drop_temp_ddl, 'REM' );
}

my $drop_all_sql = "$sqldir/$prefix${tblsp}_drop_all.sql";
print "Drop objects            : $drop_all_sql\n";
write_file( $drop_all_sql, $drop_ddl, 'REM' );

my $add_tbl_sql = "$sqldir/$prefix${tblsp}_add_tbl.sql";
print "Create tables           : $add_tbl_sql\n";
write_file( $add_tbl_sql, $create_tbl_ddl, 'REM' );

my $add_ndx_sql = "$sqldir/$prefix${tblsp}_add_ndx.sql";
print "Create indexes          : $add_ndx_sql\n\n";
write_file( $add_ndx_sql, $create_ndx_ddl, 'REM' );

my $pipefile = "$expdir/$prefix$tblsp.pipe";
unlink $pipefile;
eval { system ("mknod $pipefile p") };

if ( $create_temp_ddl )
{
  $prttn_exp_par   = "$expdir/$prefix${tblsp}_prttn_exp.par";
  $prttn_exp_text  = export_par_text( $prttn_exp_log, \@export_temps);
  print "Partition Export parfile: $prttn_exp_par\n";
  print "Partition Export logfile: $prttn_exp_log\n";
  write_file( $prttn_exp_par, $prttn_exp_text, '#' );

  $prttn_imp_par  = "$expdir/$prefix${tblsp}_prttn_imp.par";
  $prttn_imp_text = import_par_text( $prttn_imp_log, \@export_temps );
  print "Partition Import parfile: $prttn_imp_par\n";
  print "Partition Import logfile: $prttn_imp_log\n\n";
  write_file( $prttn_imp_par, $prttn_imp_text, '#' );
}

my $exp_par   = "$expdir/$prefix${tblsp}_exp.par";
my $exp_text  = export_par_text( $exp_log, \@export_objects );
print "Table Export parfile    : $exp_par\n";
print "Table Export logfile    : $exp_log\n";
write_file( $exp_par, $exp_text, '#' );

my $imp_par  = "$expdir/$prefix${tblsp}_imp.par";
my $imp_text = import_par_text( $imp_log, \@export_objects );
print "Table Import parfile    : $imp_par\n";
print "Table Import logfile    : $imp_log\n\n";
write_file( $imp_par, $imp_text, '#' );

print "Export FIFO pipe        : $pipefile\n\n";

#
# And, finally, the little shell scripts to help with the driving
#

print "\n";

my $i     = 0;
my $shell = "$sqldir/$prefix$tblsp.sh";
my $gzip  = "$expdir/$prefix${tblsp}_prttn.dmp.gz";

if ( $create_temp_ddl )
{

  $script = $shell . ++$i;
  $text =
    "# Step $i -- Export the partitions in Tablespace $tblsp\n\n" .
    "nohup cat $pipefile | gzip -c \\\n" .
    "        > $gzip &\n\n" .
    "exp / parfile = $prttn_exp_par\n" .
    check_exp_log( $script, $prttn_exp_log );
  create_shell( $script, $text );

  $script = $shell . ++$i;
  $text =
    "# Step $i -- Use SQL*Plus to run $add_temp_sql\n" .
    "#           which will create temp tables for partitions " .
    "in tablespace $tblsp\n\n" .
    "sqlplus -s / << EOF\n\n" .
    "   SPOOL $add_temp_log\n\n" .
    "   @ $add_temp_sql\n\n" .
    "EOF\n" .
    check_sql_log( $script, $add_temp_log );
  create_shell( $script, $text );
}

$script = $shell . ++$i;
$text = "# Step $i -- Export the tables in Tablespace $tblsp\n\n";
if ( @export_objects )
{
  $text .=
  "nohup cat $pipefile | gzip -c \\\n" .
  "        > $gzip &\n\n" .
  "exp / parfile = $exp_par\n" .
  check_exp_log( $script, $exp_log );
}
else
{
  $text .=
  "echo\n" .
  "echo There are no Tables in tablespace $tblsp.\n" .
  "echo Skipping Export.\n" .
  "echo\n" .
  "echo $shell\n" .
  "echo completed successfully without errors.\n" .
  "echo on \` date \`\n" .
  "echo\n\n";
}
create_shell( $script, $text );

$script = $shell . ++$i;
$text =
  "# Step $i -- Use SQL*Plus to run $drop_all_sql\n" .
  "#           which will drop all objects in tablespace $tblsp\n\n" .
  "sqlplus -s / << EOF\n\n" .
  "   SPOOL $drop_all_log\n\n" .
  "   @ $drop_all_sql\n\n" .
  "EOF\n" .
  check_sql_log( $script, $drop_all_log );
create_shell( $script, $text );

$script = $shell . ++$i;
$text =
  "# Step $i -- Use SQL*Plus to run $add_tbl_sql\n".
  "#           which will recreate all tables in tablespace $tblsp\n\n" .
  "sqlplus -s / << EOF\n\n" .
  "   SPOOL $add_tbl_log\n\n" .
  "   @ $add_tbl_sql\n\n" .
  "EOF\n" .
  check_sql_log( $script, $add_tbl_log );
create_shell( $script, $text );

$script = $shell . ++$i;
$text = "# Step $i -- Import the tables back into Tablespace $tblsp\n\n";
if ( @export_objects )
{
  $text .=
  "nohup gunzip -c $gzip \\\n" .
  "              > $pipefile &\n\n" .
  "imp / parfile = $imp_par\n" .
  check_imp_log( $script, $imp_log );
}
else
{
  $text .=
  "echo\n" .
  "echo There are no Tables in tablespace $tblsp.\n" .
  "echo Skipping Import.\n" .
  "echo\n" .
  "echo $shell\n" .
  "echo completed successfully without errors.\n" .
  "echo on \` date \`\n" .
  "echo\n\n";
}
create_shell( $script, $text );

$script = $shell . ++$i;
$text =
  "# Step $i -- Use SQL*Plus to run $add_ndx_sql\n" .
  "#           which will recreate all indexes/constraints " .
  "in tablespace $tblsp\n\n" .
  "sqlplus -s / << EOF\n\n" .
  "   SPOOL $add_ndx_log\n\n" .
  "   @ $add_ndx_sql\n\n" .
  "EOF\n" .
  check_sql_log( $script, $add_ndx_log );
create_shell( $script, $text );

$text = "echo $shell is being executed by $user\n" .
        "echo on \` date \`\n\n";

foreach my $j ( 1 .. $i )
{
  $text .= "$shell$j\n\n" .
           "RC=\$?\n\n" .
           "if [ \${RC} -gt 0 ]\n" .
           "then\n\n" .
           "   echo\n" .
           "   echo\n" .
           "   echo '*** ERROR'\n" .
           "   echo $shell$j failed\n" .
           "   echo on \` date \`\n" .
           "   echo\n" .
           "   exit \${RC}\n\n" .
           "fi\n\n";
}

$text .= "echo And so did $shell\n" .
         "echo\n" .
         "echo YAHOO!!\n" .
         "echo\n" .
         "exit 0\n\n";

print "\nAnd if you want a driver script for all of the above, it is:\n\n",
      "   $shell\n\n\n";
open SHELL, ">$shell"     or die "Can't open $shell: $!\n";
write_header( \*SHELL, $shell, '# ' );
print SHELL $text . "#  --- END OF FILE ---\n\n";
close SHELL                  or die "Can't close $shell: $!\n";

if ( $create_temp_ddl )
{
  $gzip  = "$expdir/$prefix${tblsp}_prttn.dmp.gz";

  print "\n*** The following 2 scripts ARE FOR FALLBACK PURPOSES ONLY!!\n" .
        "*** Use these scripts ONLY IF Shell #2 HAD ERRORS.\n\n";

  $script = $shell . ++$i;
  $text =
    "# USE FOR FALLBACK PURPOSES ONLY\n\n" .
    "# Use SQL*Plus to run $drop_temp_sql\n" .
    "# which will drop the temp tables holding data for partitions " .
    "in tablespace $tblsp\n\n" .
    "sqlplus -s / << EOF\n\n" .
    "   SPOOL $drop_temp_log\n\n" .
    "   @ $drop_temp_sql\n\n" .
    "EOF\n" .
    check_sql_log( $script, $drop_temp_log );
  create_shell( $script, $text );

  $script = $shell . ++$i;
  $text =
    "# USE FOR FALLBACK PURPOSES ONLY\n\n" .
    "#Import the tables back into the partitions in " .
    "Tablespace $tblsp\n\n" .
    "echo\n" .
    "echo \"**************** NOTICE ***************\"\n" .
    "echo\n" .
    "echo Ignore warnings about missing partitions -- because not\n" .
    "echo all partitions were exported, and thus not all partitions\n" .
    "echo need be re-imported.\n" .
    "echo The error to be ignored is:\n" .
    "echo\n" .
    "echo \"  IMP-00057: Warning: Dump file may not contain data of all partitions...\"\n" .
    "echo\n" .
    "echo \"************ END OF NOTICE ************\"\n\n" .
    "nohup gunzip -c $gzip \\\n" .
    "              > $pipefile &\n\n" .
    "imp / parfile = $prttn_imp_par\n" .
    check_imp_log( $script, $prttn_imp_log );
  create_shell( $script, $text );
}

my @shells = glob( "$sqldir/$prefix$tblsp.sh*" );
chmod( 0754, @shells ) == @shells or die "\nCan't chmod some shells: $!\n";

print "\n$0 completed successfully\non ", scalar localtime,"\n\n";

exit 0;

#################### Subroutines (alphabetically) ######################

# sub check
#
# returns text for a shell script to check its LOG file for errors
#
sub check
{
  my ($shell, $log ) = @_;

  return
"then

   echo
   echo '*** ERRORS during'
   echo $shell
   echo
   echo CHECK LOG $log
   echo
   exit 1

else

   echo
   echo $shell
   echo completed successfully without errors.
   echo on \` date \`
   echo

fi

";

}

# sub check_exp_log
#
# returns text for a shell script to check its exp log file for errors
#
sub check_exp_log
{
  my ( $shell, $log ) = @_;

  return
"
cat $log 

EXP=\` grep -c ^EXP- $log \`
ORA=\` grep -c ^ORA- $log \`

if [ \${ORA} -gt 0 -o \${EXP} -gt 0 ]
" . 
check( @_ );
}

# sub check_imp_log
#
# returns text for a shell script to check its imp log file for errors
#
sub check_imp_log
{
  my ( $shell, $log ) = @_;

  # Check log for errors, but ignore:
  #   IMP-00057 -- Not all partitions imported (we didn't export them all)
  #   IMP-00041 -- Store PL/SQL compilation errors (not our fault)
  return
"
cat $log 

IMP=\` grep -v ^IMP-00057 $log | \\
      grep -v ^IMP-00041 | \\
      grep -c ^IMP- \`
ORA=\` grep -c ^ORA- $log \`

if [ \${ORA} -gt 0 -o \${IMP} -gt 0 ]
" . 
check( @_ );
}

# sub check_sql_log
#
# returns text for a shell script to check its SQL spool file for errors
#
sub check_sql_log
{
  my ( $shell, $log ) = @_;

  return
"
ORA=\` grep -c ^ORA- $log \`

if [ \${ORA} -gt 0 ]
" . 
check( @_ );

}

# sub connect_to_oracle
#
# Requires both "user" and "password", or neither.  If "user" is supplied
# but not "password", will prompt for a "password".  On Unix systems, a
# system call to "stty" is made before- and after-hand to control echoing
# of keystrokes.  [How do we do this on Windows?]
#
sub connect_to_oracle
{
  if ( $args{ user } and not $args{ password } )
  {
    print "Enter password: ";
    eval{ system("stty -echo" ); };
    chomp( $args{ password } = <STDIN> );
    print "\n";
    eval{ system( "stty echo" ); };
  }

  $args{ sid }      = "" unless $args{ sid };
  $args{ user }     = "" unless $args{ user };
  $args{ password } = "" unless $args{ password };

  $dbh = DBI->connect(
                       "dbi:Oracle:$args{ sid }",
                       "$args{ user }",
                       "$args{ password }",
                       {
                         PrintError => 0,
                         RaiseError => 1,
                       }
                     );

  # $dbh->do( "alter session set sql_trace = true" );

  DDL::Oracle->configure(
                          dbh    => $dbh,
                          view   => 'DBA',
                          schema => 1,
                          resize => $args{ resize } || 1,
                        );
}

# sub create_shell
#
# Opens, writes $text, closes the named shell script
#
sub create_shell
{
  my ( $script, $text ) = @_;

  print "Shell #$i is $script\n";
  open SHELL, ">$script"     or die "Can't open $script: $!\n";
  write_header( \*SHELL, $script, '# ' );
  print SHELL $text . "#  --- END OF FILE ---\n\n";
  close SHELL                  or die "Can't close $script: $!\n";
}

# sub drop_perf_temps
#
# Drops the temporary tables created to boost performance
#
sub drop_perf_temps
{
  foreach my $table ( @perf_tables )
  {
    $stmt =
     "
      SELECT
             'Yo!'
      FROM
             user_synonyms
      WHERE
             synonym_name = UPPER( ? )
     ";

    $sth = $dbh->prepare( $stmt );
    $sth->execute( $table );
    my $present = $sth->fetchrow_array;
    $dbh->do( "DROP SYNONYM $table" )    if $present;

    if ( $table =~ /^DBA/ )
    {
      $stmt =
       "
        SELECT
               'Present, sir!'
        FROM
               user_tables
        WHERE
               table_name = UPPER( ? )
       ";

      $sth = $dbh->prepare( $stmt );
      $sth->execute( "$prefix$table" );
      my $present = $sth->fetchrow_array;

      if ( $present )
      {
        $dbh->do( "TRUNCATE TABLE $prefix$table" );
        $dbh->do( "DROP     TABLE $prefix$table" );
      }
    }
    else
    {
      $stmt =
       "
        SELECT
               'Present, sir!'
        FROM
               user_tables
        WHERE
               table_name = ?
       ";

      $sth = $dbh->prepare( $stmt );
      $sth->execute( $table );
      my $present = $sth->fetchrow_array;

      if ( $present )
      {
        $dbh->do( "TRUNCATE TABLE $table" );
        $dbh->do( "DROP     TABLE $table" );
      }
    }
  }
}

# sub escaped_dollar_signs
#
# Routines dealing with the Temp tables, indexes and constraints must
# substitute generated names for the names of real objects returned by
# DDL::Oracle.  However, Oracle allows dollar signs ('$') within names
# for database objects.  This causes problems with the s/// operator,
# since it sees the '$' as a meta character, causing the substitution
# to fail.
#
# This little subroutine inserts a '\' in front of each '$', which
# effectively escapes it for the s/// operator.
#
sub escaped_dollar_signs
{
  my ( $str ) = @_;

  my $pos = 0;

  until ( $pos == -1 )
  {
    $pos = index( $str, '$', $pos );
    if ( $pos > -1 )
    {
      substr( $str, $pos, 0 ) = qq#\\#;
      $pos += 2;
    }
  }

  return $str;
}

# sub export_par_text
#
# Returns the text for the parfile of an export
#
sub export_par_text
{
  my ( $log, $table_aref ) = @_;

  my $text = "log          = $log\n" .
             "file         = $pipefile\n" .
             "rows         = y\n" .
             "grants       = y\n";

  # My linux Oracle 8.1.6 has a bug, so
  $text   .= "direct       = y\n"    unless $OSNAME eq 'linux';

  $text   .= "buffer       = 65535\n" .
             "indexes      = n\n" .
             "compress     = n\n" .
             "triggers     = y\n" .
             "statistics   = none\n" .
             "constraints  = n\n" .
             "recordlength = 65535\n" .
             "tables       = (\n" .
             "                   " .
             join ( "\n                 , ", @$table_aref ) .
             "\n               )\n\n";

  return $text
}

# sub get_args
#
# Uses supplied module Getopt::Long to place command line options into the
# hash %args.  Ensures that at least the mandatory argument --tablespace
# was supplied.  Also verifies directory arguments and connects to Oracle.
#
sub get_args
{
  #
  # Get options from command line and store in %args
  #
  GetOptions(
              \%args,
              "alttablespace:s",
              "expdir:s",
              "logdir:s",
              "password:s",
              "prefix:s",
              "sid:s",
              "resize:s",
              "sqldir:s",
              "tablespace:s",
              "user:s",
            );

  #
  # If there is anything left in @ARGV, we have a problem
  #
  die "\n***Error:  unrecognized argument",
      ( @ARGV == 1 ? ":  " : "s:  " ),
      ( join " ",@ARGV ),
      "\n$0 aborted,\n\n" ,
    if @ARGV;
  
  #
  # Validate arguments (maybe they type as badly as we do!
  #

  $tblsp = uc( $args{ tablespace } ) or
  die "\n***Error:  You must specify --tablespace=<NAME>\n",
      "\n$0 aborted,\n\n";

  $sqldir = ( $args{ sqldir } eq "." ) ? cwd : $args{ sqldir };
  die "\n***Error:  sqldir '$sqldir', is not a Directory\n",
      "\n$0 aborted,\n\n"
    unless -d $sqldir;

  die "\n***Error:  sqldir '$sqldir', is not a writeable Directory\n",
      "\n$0 aborted,\n\n"
    unless -w $sqldir;

  $logdir = ( $args{ logdir } eq "." ) ? cwd : $args{ logdir };
  die "\n***Error:  logdir '$logdir', is not a Directory\n",
      "\n$0 aborted,\n\n"
    unless -d $logdir;

  die "\n***Error:  logdir '$logdir', is not a writeable Directory\n",
      "\n$0 aborted,\n\n"
    unless -w $logdir;

  $expdir = ( $args{ expdir } eq "." ) ? cwd : $args{ expdir };
  die "\n***Error:  expdir '$expdir', is not a Directory\n",
      "\n$0 aborted,\n\n"
    unless -d $expdir;

  die "\n***Error:  sqldir '$expdir', is not a writeable Directory\n",
      "\n$0 aborted,\n\n"
    unless -w $expdir;

  $prefix = $args{ prefix };

  $add_ndx_log     = "$logdir/$prefix${tblsp}_add_ndx.log";
  $add_tbl_log     = "$logdir/$prefix${tblsp}_add_tbl.log";
  $add_temp_log    = "$logdir/$prefix${tblsp}_add_temp.log";
  $drop_all_log    = "$logdir/$prefix${tblsp}_drop_all.log";
  $drop_temp_log   = "$logdir/$prefix${tblsp}_drop_temp.log";
  $exp_log         = "$logdir/$prefix${tblsp}_exp.log";
  $imp_log         = "$logdir/$prefix${tblsp}_imp.log";
  $prttn_exp_log   = "$logdir/$prefix${tblsp}_prttn_exp.log";
  $prttn_imp_log   = "$logdir/$prefix${tblsp}_prttn_imp.log";

  push @logfiles, (
                    $add_ndx_log,
                    $add_tbl_log,
                    $add_temp_log,
                    $drop_all_log,
                    $drop_temp_log,
                    $exp_log,
                    $imp_log,
                    $prttn_exp_log,
                    $prttn_imp_log,
                  );

  validate_log_names( \@logfiles );

  $alttblsp = uc( $args{ alttablespace } );

  connect_to_oracle();      # Will fail unless sid, user, password are OK

  print "Initializing private copies of some dictionary views...\n\n";

  initialize_perf_temps();

  # Confirm the tablespace exists
  $stmt =
      "
       SELECT
              tablespace_name
       FROM
              dba_tablespaces  t
       WHERE
                  tablespace_name   = '$tblsp'
              AND status            = 'ONLINE'
              AND contents         <> 'TEMPORARY'
              AND extent_management = 'DICTIONARY'
       MINUS
       SELECT
              tablespace_name
       FROM
              dba_segments
       WHERE
              segment_type = 'ROLLBACK'
      ";

  $sth = $dbh->prepare( $stmt );
  $sth->execute;
  $row = $sth->fetchrow_array;

  die "\n***Error:  Tablespace \U$tblsp",
      " does not exist\n",
      "           or is not ONLINE\n",
      "           or is managed LOCALLY\n",
      "           or is a TEMPORARY tablespace\n",
      "           or contains ROLLBACK segments.\n\n"
    unless $row;

  # First row returned is valid tablespace, and is $alttblsp.
  # Since we know $tblsp is good, we're guaranteed at least one row.
  $stmt =
      "
       (
         SELECT
                tablespace_name
         FROM
                dba_tablespaces
         WHERE
                    tablespace_name   = '$alttblsp'
                AND status            = 'ONLINE'
                AND contents         <> 'TEMPORARY'
                AND extent_management = 'DICTIONARY'
         MINUS
         SELECT
                tablespace_name
         FROM
                dba_segments
         WHERE
                segment_type = 'ROLLBACK'
       )
       UNION ALL
       (
         SELECT
                tablespace_name
         FROM
                dba_tablespaces
         WHERE
                    tablespace_name   = 'USERS'
                AND status            = 'ONLINE'
                AND contents         <> 'TEMPORARY'
                AND extent_management = 'DICTIONARY'
         MINUS
         SELECT
                tablespace_name
         FROM
                dba_segments
         WHERE
                segment_type = 'ROLLBACK'
       )
       UNION ALL
       (
         SELECT
                '$tblsp'
         FROM
                dual
       )
      ";

  $sth = $dbh->prepare( $stmt );
  $sth->execute;
  $aref = $sth->fetchall_arrayref;

  $alttblsp = ( shift @$aref )->[0];

  my ( undef,undef,undef,$day,$month,$year,undef,undef,undef ) = localtime;
  $date = $year + 1900 . $month + 1 . $day;
}

# sub group_header
#
# Returns a Remark to identify the ensuing DDL statements
#
sub group_header
{
  my ( $nbr ) = @_;

  return 'REM ' . '#' x 60 . "\n" .
         "REM\n" .
         "REM                      Statement Group $nbr\n" .
         "REM\n" .
         'REM ' . '#' x 60 . "\n\n";
}

# sub import_par_text
#
# Returns the text for the parfile of an import
#
sub import_par_text
{
  my ( $log, $table_aref ) = @_;

  return            "log          = $log\n" .
                    "file         = $pipefile\n" .
                    "rows         = y\n" .
                    "commit       = y\n" .
                    "ignore       = y\n" .
                    "buffer       = 65535\n" .
                    "analyze      = n\n" .
                    "recordlength = 65535\n" .
                    "full         = y\n\n" .
                    "#tables       = (\n" .
                    "#                   " .
                    join ( "\n#                 , ", @$table_aref ) .
                    "\n#               )\n\n";
}

# sub index_and_exchange
#
# Generate the DDL to:
#
# 1.  Create an index on named temp table equal to every LOCAL index on the
#     named partitioned table.
# 2.  Create a PK for the temp table equal to the PK of the partitioned table,
#     if any.
# 3.  Exchange the temp table with the named partition.
#
sub index_and_exchange
{
  my ( 
       $temp,
       $owner, 
       $table, 
       $partition, 
       $type, 
       $partitioning_type,
       $analyzed
     ) = @_;

  my $sql;
  my $text;

  # Get partitioned, local indexes
  $stmt =
      "
       SELECT DISTINCT
              index_name
       FROM
              dba_indexes
       WHERE
                  owner      = ?
              AND table_name = ?
       MINUS
       SELECT                     -- Ignore GLOBAL indexes
              index_name
       FROM
              dba_part_indexes
       WHERE
                  owner      = ?
              AND table_name = ?
              AND locality   = 'GLOBAL'
       MINUS
       SELECT                     -- Ignore non-partitioned indexes
              segment_name
       FROM
              dba_segments
       WHERE
              segment_type = 'INDEX'
       ORDER
          BY
              1
      ";

  $sth = $dbh->prepare( $stmt );
  $sth->execute( $owner, $table, $owner, $table );
  $aref = $sth->fetchall_arrayref;

  foreach $row ( @$aref )
  {
    my $index = @$row->[0];

    $obj = DDL::Oracle->new(
                             type => 'exchange index',
                             list => [
                                       [
                                         "$owner",
                                         "$index:$partition",
                                       ]
                                     ],
                           );
    my $sql = $obj->create;
    # Remove REM lines created by DDL::Oracle
    $sql =  ( join "\n", grep !/^REM/, split /\n/, $sql ) . "\n\n";

    my $indx =  "${tblsp}_${date}_" . unique_nbr();

    # Change the CREATE INDEX statement
    # to use the Temp Index and Table names
    my $ownr = escaped_dollar_signs( $owner );
    my $tabl = escaped_dollar_signs( $table );
    my $indr = escaped_dollar_signs( $index );
    $sql     =~ s|\L$ownr.$indr|\L$owner.$indx|g;
    $sql     =~ s|\L$ownr.$tabl|\L$owner.$temp|g;

    $text .= $sql;
  }

  $stmt =
      "
       SELECT
              constraint_name
       FROM
              THE_CONSTRAINTS
       WHERE
                  owner           = ?
              AND table_name      = ?
              AND constraint_type = 'P'
      ";

  $sth = $dbh->prepare( $stmt );
  $sth->execute( $owner, $table );
  my @row = $sth->fetchrow_array;

  if ( @row )
  {
    my ( $constraint ) = @row;

    $obj = DDL::Oracle->new(
                             type => 'constraint',
                             list => [
                                       [
                                         "$owner",
                                         "$constraint",
                                       ]
                                     ],
                           );
    my $sql = $obj->create;
    # Remove REM lines created by DDL::Oracle
    $sql =  ( join "\n", grep !/^REM/, split /\n/, $sql ) . "\n\n";

    my $cons =  "${tblsp}_${date}_" . unique_nbr();

    # Change the ALTER TABLE ADD CONSTRAINT statement
    # to use the Temp Constraint and Table names
    my $ownr = escaped_dollar_signs( $owner );
    my $tabl = escaped_dollar_signs( $table );
    my $conr = escaped_dollar_signs( $constraint );
    $sql     =~ s|\L$ownr.$tabl|\L$owner.$temp|g;
    $sql     =~ s|\L$conr|\L$cons|g;

    $text .= $sql;
  }

  if ( $analyzed eq 'YES' )
  {
    $text .= "PROMPT " .
             "ANALYZE TABLE \L$owner.$temp\n\n" .
             "ANALYZE TABLE \L$owner.$temp \UESTIMATE STATISTICS\n" .
             "   FOR TABLE\n" .
             "   FOR ALL INDEXED COLUMNS ;\n\n";
  }

  $text .= "PROMPT " .
           "ALTER TABLE \L$owner.$table \UEXCHANGE $type \L$partition\n\n" .
           "ALTER TABLE \L$owner.$table\n" .
           "   \UEXCHANGE $type \L$partition \UWITH TABLE \L$owner.$temp\n" .
           "   INCLUDING INDEXES\n".
           "   WITHOUT VALIDATION ;\n\n";

  return $text;
}

# sub initialize_perf_temps
#
sub initialize_perf_temps
{
  # Drop the Performance enhancing tables -- they shouldn't be here,
  # but who knows, maybe we crashed last time (how rude!)

  drop_perf_temps();

  # Some Dictionary views are queried repeatedly by us (defrag.pl) as well
  # as by DDL::Oracle.  They are often complex views, taking as much as 3
  # to 10 seconds for each query on a large database (e.g., 50,000 segments).
  # Let's get our own, more efficient copy of this data and avoid this
  # overhead

  TABLE:
    foreach my $table ( @perf_tables )
    {
      next TABLE unless $table =~ /^DBA/;

      $dbh->do
      ( "
         CREATE GLOBAL TEMPORARY TABLE $prefix$table
         ON COMMIT PRESERVE ROWS
         AS
         SELECT
                *
         FROM
                sys.$table
        "
      );
      $dbh->do( "CREATE SYNONYM $table FOR $prefix$table" );
    }
}

# sub initialize_queries
#
# Initializes the driving queries used to retrieve object names involved in
# the defrag.  Because these are UNIONed and MINUSed, at times, store the
# the results in in-memory temporary tables for efficiency reasons.
#
sub initialize_queries
{
  # This query produces a list of THE CONSTRAINTS, sans search_condition
  # which is needed for creating Check Constraints
  $stmt =
      "
       CREATE GLOBAL TEMPORARY TABLE the_constraints
       ON COMMIT PRESERVE ROWS
       AS
       SELECT
              owner
            , constraint_name
            , constraint_type
            , table_name
            , r_owner
            , r_constraint_name
       FROM
              dba_constraints
      ";

  $dbh->do( $stmt );

  # This query produces a list of THE PARTITIONS, which are the partitions
  # in THE TABLESPACE belonging to tables which have at least one partition
  # in some other tablespace.  These will be the target of ALTER TABLE
  # EXCHANGE [SUB]PARTITION statements with "temp" tables.
  #
  $stmt =
      "
       CREATE GLOBAL TEMPORARY TABLE the_partitions
       ON COMMIT PRESERVE ROWS
       AS
       SELECT
              s.owner
            , s.segment_name
            , s.partition_name
            , SUBSTR(s.segment_type,7)                 AS segment_type
            , p.partitioning_type                      AS partitioning_type
            , DECODE(
                      s.segment_type
                     ,'TABLE PARTITION'   ,DECODE(
                                                   a.last_analyzed
                                                  ,null,'NO'
                                                  ,'YES'
                                                 )
                     ,'TABLE SUBPARTITION',DE
Results 1 - 1
Help - FTP Sites List - Software Dir.
Searching half a billion files worldwide
© 1997-2009 MARUHN Internet Solutions