----------------------------------------------------------------------------------------
--
-- File name:   cs_tbs_resize.sql
--
-- Purpose:     Tablespace Resize
--
-- Author:      Rodrigo Righetti
--
-- Version:     2020/12/09
--
-- Usage:       Execute connected to CDB or PDB.
--
--
-- Example:     $ sqlplus / as sysdba
--              SQL> @cs_tbs_resize.sql TBSNAME 10
--
--              This execution would increase the maxsize for Tablespace TBSNAME in 10%.
--
-- Notes:       Developed and tested on 12.1.0.2 and 19c.
--
---------------------------------------------------------------------------------------
--
--
DEF permanent = 'Y';
DEF undo = 'Y';
DEF temporary = 'Y';
-- order_by: [{pdb_name, tablespace_name}|max_size_gb DESC|allocated_gb DESC|used_gb DESC|free_gb DESC]
DEF order_by = 'pdb_name, tablespace_name';
DEF rows = '999';
--
@@cs_internal/cs_primary.sql
@@cs_internal/cs_cdb_warn.sql
@@cs_internal/cs_set.sql
@@cs_internal/cs_def.sql
@@cs_internal/cs_file_prefix.sql
--
DEF cs_script_name = 'cs_tbs_resize';
--
COL tablespace_name FOR A30;
SELECT tablespace_name 
FROM   dba_tablespaces;
PRO
--
PRO 1. Tablespace to resize: 
DEF tbs_name = '&1.';
UNDEF 1;

--
SELECT '&&cs_file_prefix._&&cs_script_name.' cs_file_name FROM DUAL;
--
@@cs_internal/cs_spool_head.sql
PRO SQL> @&&cs_script_name..sql "&&tbs_name."
@@cs_internal/cs_spool_id.sql
--
--
CLEAR BREAK COMPUTE;
BREAK ON REPORT;
COMPUTE SUM LABEL 'TOTAL' OF allocated_gb used_gb free_gb max_size_gb ON REPORT; 
--
COL pdb_name FOR A30;
COL tablespace_name FOR A30;
COL allocated_gb FOR 999,990.000 HEA 'ALLOCATED|SPACE (GB)';
COL used_gb FOR 999,990.000 HEA 'USED|SPACE (GB)';
COL used_percent FOR 990.0 HEA 'USED|PERC';
COL free_gb FOR 999,990.000 HEA 'FREE|SPACE (GB)';
COL free_percent FOR 990.0 HEA 'FREE|PERC';
COL max_size_gb FOR 999,990.000 HEA 'MAX|SIZE (GB)';
COL met_used_space_GB FOR 999,990.000 HEA 'METRICS|USED|SPACE (GB)';
COL met_used_percent FOR 990.0 HEA 'METRICS|USED|PERC';
--
WITH
t AS (
SELECT /*+ MATERIALIZE NO_MERGE */
       con_id,
       tablespace_name,
       SUM(NVL(bytes, 0)) bytes
  FROM cdb_data_files
  WHERE con_id=&&cs_con_id.
 GROUP BY 
       con_id,
       tablespace_name
 UNION ALL
SELECT /*+ MATERIALIZE NO_MERGE */
       con_id,
       tablespace_name,
       SUM(NVL(bytes, 0)) bytes
  FROM cdb_temp_files
    WHERE con_id=&&cs_con_id.
 GROUP BY 
       con_id,
       tablespace_name
),
u AS (
SELECT /*+ MATERIALIZE NO_MERGE */
       con_id,
       tablespace_name,
       SUM(bytes) bytes
  FROM cdb_free_space
    WHERE con_id=&&cs_con_id.
 GROUP BY 
        con_id,
        tablespace_name
 UNION ALL
SELECT /*+ MATERIALIZE NO_MERGE */
       con_id,
       tablespace_name,
       NVL(SUM(bytes_used), 0) bytes
  FROM gv$temp_extent_pool
    WHERE con_id=&&cs_con_id.
 GROUP BY 
       con_id,
       tablespace_name
),
un AS (
SELECT /*+ MATERIALIZE NO_MERGE */
       ts.con_id,
       ts.tablespace_name,
       NVL(um.used_space * ts.block_size, 0) bytes
  FROM cdb_tablespaces              ts,
       cdb_tablespace_usage_metrics um
 WHERE ts.contents           = 'UNDO'
   AND um.tablespace_name(+) = ts.tablespace_name
   AND um.con_id(+)          = ts.con_id
),
oem AS (
SELECT /*+ MATERIALIZE NO_MERGE */
       ts.con_id,
       pdb.name pdb_name,
       ts.tablespace_name,
       ts.contents,
       ts.bigfile,
       ts.block_size,
       NVL(t.bytes, 0) allocated_space_bytes,
       NVL(
       CASE ts.contents
       WHEN 'UNDO'         THEN un.bytes
       WHEN 'PERMANENT'    THEN t.bytes - NVL(u.bytes, 0)
       WHEN 'TEMPORARY'    THEN
         CASE ts.extent_management
         WHEN 'LOCAL'      THEN u.bytes
         WHEN 'DICTIONARY' THEN t.bytes - NVL(u.bytes, 0)
         END
       END 
       , 0) used_space_bytes
  FROM cdb_tablespaces ts,
       v$containers    pdb,
       t,
       u,
       un
 WHERE 1 = 1
   AND t.tablespace_name = upper('&&tbs_name.')
   AND pdb.con_id            = ts.con_id
   AND t.tablespace_name(+)  = ts.tablespace_name
   AND t.con_id(+)           = ts.con_id
   AND u.tablespace_name(+)  = ts.tablespace_name
   AND u.con_id(+)           = ts.con_id
   AND un.tablespace_name(+) = ts.tablespace_name
   AND un.con_id(+)          = ts.con_id
),
tablespaces AS (
SELECT o.pdb_name,
       o.tablespace_name,
       o.contents,
       o.bigfile,
       ROUND(m.maxbytes / POWER(10, 9), 3) AS max_size_gb,
       ROUND(o.allocated_space_bytes / POWER(10, 9), 3) AS allocated_gb,
       ROUND(o.used_space_bytes / POWER(10, 9), 3) AS used_gb,
       ROUND((o.allocated_space_bytes - o.used_space_bytes) / POWER(10, 9), 3) AS free_gb,
       ROUND(100 * o.used_space_bytes / o.allocated_space_bytes, 3) AS used_percent, -- as per allocated space
       ROUND(100 * (o.allocated_space_bytes - o.used_space_bytes) / o.allocated_space_bytes, 3) AS free_percent -- as per allocated space
  FROM oem                          o,
       (SELECT con_id, tablespace_name, sum(maxbytes) maxbytes
       FROM cdb_data_files
       WHERE con_id = &&cs_con_id.
       GROUP BY con_id, tablespace_name
       UNION
       SELECT con_id, tablespace_name, sum(maxbytes) maxbytes
       FROM cdb_temp_files
       WHERE con_id = &&cs_con_id.
       GROUP BY con_id, tablespace_name
       ) m
 WHERE m.tablespace_name(+) = o.tablespace_name
   AND m.con_id(+)          = o.con_id
)
SELECT pdb_name,
       tablespace_name,
       contents,
       bigfile,
       '|' AS "|",
       max_size_gb,
       allocated_gb,
       used_gb,
       free_gb,
       used_percent,
       free_percent 
  FROM tablespaces
 ORDER BY
       &&order_by.
FETCH FIRST &&rows. ROWS ONLY
/
--
--
@@cs_internal/&&cs_set_container_to_cdb_root.
--
COL p_u02 NEW_V p_u02 FOR 99999999;
--
PRO
PRO OS SPACE AVAILABLE - FREE GB U02
 -- OS space available
SELECT round(U02_AVAILABLE/power(2,20)) p_u02
FROM C##IOD.dbc_system
WHERE TIMESTAMP= (select max(TIMESTAMP) from C##IOD.dbc_system);
--
PRO
--
@@cs_internal/&&cs_set_container_to_curr_pdb.
--
SET SERVEROUTPUT ON
BEGIN
    DBMS_OUTPUT.put_line('-----------------------------------------------------------------------------------------');
    DBMS_OUTPUT.put_line('Max permissible increase must be between 1% and 20%, different values will default to 10%');
    DBMS_OUTPUT.put_line('-----------------------------------------------------------------------------------------');
    DBMS_OUTPUT.put_line('Increments are always rounded up "ceil", small files always added with 1G and maxsize 32G');
    DBMS_OUTPUT.put_line('-----------------------------------------------------------------------------------------');

END;
/
PRO
PRO 2. What percentange to increase the TBS maxsize  {[10]|1-20}?:
DEF perc_increase = '&2.';
UNDEF 2;
--
COL p_perc_increase NEW_V p_perc_increase NOPRI;
SELECT CASE WHEN &&perc_increase. BETWEEN 1 AND 20 THEN &&perc_increase. ELSE 10 END AS p_perc_increase FROM DUAL
/
--
SET SERVEROUTPUT ON
--
DECLARE

l_u02_avail NUMBER := &&p_u02.;
l_perc_increase NUMBER := (&&p_perc_increase./100)+1;
l_statement CLOB;
l_newmax    NUMBER;
l_bytes     NUMBER;
l_maxbytes  NUMBER;
l_nfiles    NUMBER;
l_smmaxfix  NUMBER;
l_addfilesc NUMBER;
l_counter   NUMBER := 1;
l_temp      VARCHAR2(10) := ' DATAFILE ';

BEGIN

    IF upper('&&tbs_name.') like 'TEMP%' THEN

        SELECT round(sum(bytes)/power(2,30)) bytes_gb , round(sum(maxbytes)/power(2,30)) maxbytes_gb, count(*) numfiles
        INTO   l_bytes, l_maxbytes, l_nfiles
        FROM  dba_temp_files
        WHERE tablespace_name = upper('&&tbs_name.');

        l_temp := ' TEMPFILE ';

    ELSE
        SELECT round(sum(bytes)/power(2,30)) bytes_gb , round(sum(maxbytes)/power(2,30)) maxbytes_gb, count(*) numfiles
        INTO   l_bytes, l_maxbytes, l_nfiles
        FROM  dba_data_files
        WHERE tablespace_name = upper('&&tbs_name.');

    END IF;



    FOR i IN (SELECT tablespace_name, bigfile
              FROM dba_tablespaces
              WHERE tablespace_name = upper('&&tbs_name.') ) LOOP

              IF i.bigfile = 'YES' THEN

                    FOR j IN (SELECT file_id, autoextensible, (bytes/power(2,30)) bytes_gb, (maxbytes/power(2,30)) maxbytes_gb
                          FROM dba_data_files
                          WHERE tablespace_name = upper('&&tbs_name.')
                            ) LOOP

                            IF j.maxbytes_gb <= j.bytes_gb THEN
                                l_newmax := ceil(j.bytes_gb * l_perc_increase);
                            ELSIF j.maxbytes_gb < (j.maxbytes_gb*l_perc_increase) THEN 
                                l_newmax := ceil(j.maxbytes_gb * l_perc_increase);
                            ELSIF j.maxbytes_gb >= (j.maxbytes_gb*l_perc_increase) THEN
                                DBMS_OUTPUT.put_line( 'Maxsize already has over '||l_perc_increase||'% increase requested.');
                                RETURN;
                            END IF;

                            IF l_newmax = 0 THEN
                                l_newmax := 1;
                                DBMS_OUTPUT.put_line('Tablespace too small, minimal adjustment is 1G:');
                            END IF;

                            IF (l_newmax - j.bytes_gb) >= (l_u02_avail*.7) THEN
                                DBMS_OUTPUT.put_line('Your new Maxsize request is over 70% of the available /u02 free space !!!');
                                RETURN;
                            END IF;

                            DBMS_OUTPUT.put_line('-------------------------------------------------------------------------------------');

                            l_statement := 'ALTER DATABASE DATAFILE '||j.file_id||' AUTOEXTEND ON NEXT 1G MAXSIZE '||l_newmax||'G';
                            DBMS_OUTPUT.put_line(l_statement);
                            execute immediate l_statement;
                            
                    END LOOP;
            ELSE
                    -- small file support
                    -- if all datafiles already at max 32 gb, then add datafile, else fix Maxsize and add datafile if needed
                    -- Small files always increamented in 32gb files maxsize

                    -- fix max size of current datafiles

                    IF l_maxbytes > l_bytes THEN
                        l_newmax := ceil((l_maxbytes*l_perc_increase)-l_maxbytes);
                    ELSE
                        l_newmax := ceil((l_bytes*l_perc_increase)-l_bytes);
                    END IF;

                    IF (l_newmax - l_bytes) >= (l_u02_avail*.7) THEN
                        DBMS_OUTPUT.put_line( 'Your new Maxsize request is over 70% of the available /u02 free space !!!');
                        RETURN;
                    END IF;

                    FOR j IN (SELECT file_id, autoextensible, (bytes/power(2,30)) bytes_gb, (maxbytes/power(2,30)) maxbytes_gb, (maxbytes/power(2,30))-(bytes/power(2,30)) free_gb
                          FROM dba_data_files
                          WHERE tablespace_name = upper('&&tbs_name.')
                          AND   round(maxbytes/power(2,30)) < 32
                          UNION 
                          SELECT file_id, autoextensible, (bytes/power(2,30)) bytes_gb, (maxbytes/power(2,30)) maxbytes_gb, (maxbytes/power(2,30))-(bytes/power(2,30)) free_gb
                          FROM dba_temp_files
                          WHERE tablespace_name = upper('&&tbs_name.')
                          AND   round(maxbytes/power(2,30)) < 32
                          ORDER by file_id) LOOP

                            l_statement := 'ALTER DATABASE '||l_temp||' '||j.file_id||' AUTOEXTEND ON NEXT 1G MAXSIZE UNLIMITED';
                            DBMS_OUTPUT.put_line(l_statement);
                            execute immediate l_statement;

                    END LOOP;

                     SELECT max(maxbytes_gb)
                     INTO   l_smmaxfix
                     FROM ( 
                         SELECT sum(maxbytes)/power(2,30) maxbytes_gb
                         FROM  dba_data_files
                         WHERE tablespace_name = upper('&&tbs_name.')
                         UNION 
                         SELECT sum(maxbytes)/power(2,30) maxbytes_gb
                         FROM  dba_temp_files
                         WHERE tablespace_name = upper('&&tbs_name.')
                     )
                     ;

                    DBMS_OUTPUT.put_line('-------------------------------------------------------------------------------------');

                    IF l_smmaxfix >= l_maxbytes+l_newmax THEN
                        DBMS_OUTPUT.PUT_LINE('SmallFile Tablespace maxsize increased to: '||round(l_smmaxfix,2)||'G, no need to add more datafiles');
                    ELSE
                        DBMS_OUTPUT.PUT_LINE( 'Smallfile tablespaces are added with 1G autoexted and Maxsize of 32G.');

                        l_newmax := ceil((l_smmaxfix*l_perc_increase)-l_smmaxfix);

                        IF (l_newmax - l_bytes) >= (l_u02_avail*.7) THEN
                           DBMS_OUTPUT.PUT_LINE( 'Your new Maxsize request is over 70% of the available /u02 free space !!!');
                           RETURN;
                        END IF;

                        l_addfilesc := ceil(l_newmax/32);

                        WHILE l_counter <= l_addfilesc
                        LOOP

                          l_statement := 'ALTER TABLESPACE '||i.tablespace_name||' ADD '||l_temp||' SIZE 1G AUTOEXTEND ON NEXT 1G MAXSIZE UNLIMITED';
                           DBMS_OUTPUT.put_line(l_statement);
                           execute immediate l_statement;
                          l_counter := l_counter + 1;
                        END LOOP;
 
                    END IF;
                        
            END IF;
    END LOOP;

END;
/

PRO -------------------
PRO
PRO New Tablespace Size
PRO -------------------

WITH
t AS (
SELECT /*+ MATERIALIZE NO_MERGE */
       con_id,
       tablespace_name,
       SUM(NVL(bytes, 0)) bytes
  FROM cdb_data_files
  WHERE con_id=&&cs_con_id.
 GROUP BY 
       con_id,
       tablespace_name
 UNION ALL
SELECT /*+ MATERIALIZE NO_MERGE */
       con_id,
       tablespace_name,
       SUM(NVL(bytes, 0)) bytes
  FROM cdb_temp_files
    WHERE con_id=&&cs_con_id.
 GROUP BY 
       con_id,
       tablespace_name
),
u AS (
SELECT /*+ MATERIALIZE NO_MERGE */
       con_id,
       tablespace_name,
       SUM(bytes) bytes
  FROM cdb_free_space
    WHERE con_id=&&cs_con_id.
 GROUP BY 
        con_id,
        tablespace_name
 UNION ALL
SELECT /*+ MATERIALIZE NO_MERGE */
       con_id,
       tablespace_name,
       NVL(SUM(bytes_used), 0) bytes
  FROM gv$temp_extent_pool
    WHERE con_id=&&cs_con_id.
 GROUP BY 
       con_id,
       tablespace_name
),
un AS (
SELECT /*+ MATERIALIZE NO_MERGE */
       ts.con_id,
       ts.tablespace_name,
       NVL(um.used_space * ts.block_size, 0) bytes
  FROM cdb_tablespaces              ts,
       cdb_tablespace_usage_metrics um
 WHERE ts.contents           = 'UNDO'
   AND um.tablespace_name(+) = ts.tablespace_name
   AND um.con_id(+)          = ts.con_id
),
oem AS (
SELECT /*+ MATERIALIZE NO_MERGE */
       ts.con_id,
       pdb.name pdb_name,
       ts.tablespace_name,
       ts.contents,
       ts.bigfile,
       ts.block_size,
       NVL(t.bytes, 0) allocated_space_bytes,
       NVL(
       CASE ts.contents
       WHEN 'UNDO'         THEN un.bytes
       WHEN 'PERMANENT'    THEN t.bytes - NVL(u.bytes, 0)
       WHEN 'TEMPORARY'    THEN
         CASE ts.extent_management
         WHEN 'LOCAL'      THEN u.bytes
         WHEN 'DICTIONARY' THEN t.bytes - NVL(u.bytes, 0)
         END
       END 
       , 0) used_space_bytes
  FROM cdb_tablespaces ts,
       v$containers    pdb,
       t,
       u,
       un
 WHERE 1 = 1
   AND t.tablespace_name = upper('&&tbs_name.')
   AND pdb.con_id            = ts.con_id
   AND t.tablespace_name(+)  = ts.tablespace_name
   AND t.con_id(+)           = ts.con_id
   AND u.tablespace_name(+)  = ts.tablespace_name
   AND u.con_id(+)           = ts.con_id
   AND un.tablespace_name(+) = ts.tablespace_name
   AND un.con_id(+)          = ts.con_id
),
tablespaces AS (
SELECT o.pdb_name,
       o.tablespace_name,
       o.contents,
       o.bigfile,
       ROUND(m.maxbytes / POWER(10, 9), 3) AS max_size_gb,
       ROUND(o.allocated_space_bytes / POWER(10, 9), 3) AS allocated_gb,
       ROUND(o.used_space_bytes / POWER(10, 9), 3) AS used_gb,
       ROUND((o.allocated_space_bytes - o.used_space_bytes) / POWER(10, 9), 3) AS free_gb,
       ROUND(100 * o.used_space_bytes / o.allocated_space_bytes, 3) AS used_percent, -- as per allocated space
       ROUND(100 * (o.allocated_space_bytes - o.used_space_bytes) / o.allocated_space_bytes, 3) AS free_percent -- as per allocated space
  FROM oem                          o,
       (SELECT con_id, tablespace_name, sum(maxbytes) maxbytes
       FROM cdb_data_files
       WHERE con_id = &&cs_con_id.
       GROUP BY con_id, tablespace_name
       UNION
       SELECT con_id, tablespace_name, sum(maxbytes) maxbytes
       FROM cdb_temp_files
       WHERE con_id = &&cs_con_id.
       GROUP BY con_id, tablespace_name
       ) m
 WHERE m.tablespace_name(+) = o.tablespace_name
   AND m.con_id(+)          = o.con_id
)
SELECT pdb_name,
       tablespace_name,
       contents,
       bigfile,
       '|' AS "|",
       max_size_gb,
       allocated_gb,
       used_gb,
       free_gb,
       used_percent,
       free_percent 
  FROM tablespaces
 ORDER BY
       &&order_by.
FETCH FIRST &&rows. ROWS ONLY
/

CLEAR BREAK COMPUTE;
--
PRO
PRO SQL> @&&cs_script_name..sql "&&tbs_name." "&&perc_increase."
--
@@cs_internal/cs_spool_tail.sql
@@cs_internal/cs_undef.sql
@@cs_internal/cs_reset.sql
--