The short answer is No. Unfortunately the way to do this in Oracle does require the "big stack of boring queries". The articles you linked to are some of the best information available on the subject. The datafile does indeed become fragmented, so that even if free space exists below the highest segment, Oracle will not automatically consolidate it when a RESIZE
is done.
To "defragment" the tablespace you need to move these segments to the start of the datafile rather than at the end. For tables this is an offline process meaning the table will be unavailable while the move is taking place. Indexes can be moved either offline or with Enterprise Edition they can be moved online. Since you have an outage window I recommend you follow these steps.
A. Shrink datafiles with free space beyond the high water mark. This can be done as follows (the query is similar to Frosty Z's procedure):
SELECT ceil( blocks*(a.BlockSize)/1024/1024) "Current Size",
ceil( (nvl(hwm,1)*(a.BlockSize))/1024/1024 ) "Smallest Poss.",
ceil( blocks*(a.BlockSize)/1024/1024) -
ceil( (nvl(hwm,1)*(a.BlockSize))/1024/1024 ) "Savings",
'alter database datafile '''|| file_name || ''' resize ' ||
ceil((nvl(hwm,1)*(a.BlockSize))/1024/1024/100)*100 || 'm;' "Command"
FROM (SELECT a.*, p.value BlockSize FROM dba_data_files a
JOIN v$parameter p ON p.Name='db_block_size') a
LEFT JOIN (SELECT file_id, max(block_id+blocks-1) hwm FROM dba_extents GROUP BY file_id ) b
ON a.file_id = b.file_id
WHERE ceil( blocks*(a.BlockSize)/1024/1024) - ceil( (nvl(hwm,1)*(a.BlockSize))/1024/1024 )
> 100 /* Minimum MB it must shrink by to be considered. */
ORDER BY "Savings" Desc;
B. After shrinking things above the high water mark, find out what tablespaces would still benefit from having segments moved.
SELECT DISTINCT tablespace_name FROM
(
SELECT tablespace_name, block_id + blocks LastBlock,
lead(block_id) OVER (PARTITION BY File_ID
ORDER BY tablespace_name, file_id, block_id) NextBlock
FROM dba_free_space
) WHERE LastBlock <> NextBlock AND NextBlock IS NOT NULL;
C. For each of these tablespaces determine which segments need to be moved. (Replace USERS with the name of your tablespace or join it with the previous query)
SELECT distinct de.segment_name
FROM dba_extents de
JOIN
(
SELECT tablespace_name, file_id, MIN(block_id) LowestFreeBlock
FROM dba_free_space
WHERE tablespace_name = 'USERS'
GROUP BY tablespace_name, file_id
) dfs ON dfs.tablespace_name = de.tablespace_name AND dfs.file_id = de.file_id
WHERE de.tablespace_name = 'USERS'
AND de.block_id > dfs.LowestFreeBlock;
D. Move each table and rebuild the indexes and statistics.
E. Repeat step A.
I just built most of these queries, so you will want to thoroughly test them before use. I suppose you could create a procedure that would use EXECUTE IMMEDIATE
to create the actual statements to run dynamically, but because queries will receive ORA-08103: Object no longer exists while the move is in progress, I think it is best to control that process manually even if it does mean a bit more time/effort.
One way of return select data from PL/SQL is using Oracle table functions. Below is an example where the line SELECT * FROM table_a where test_a = par1;
should be replaced by your select statement + change the table name.
Create table Table_a
(test_a varchar2(1))
/
insert into table_a
values('a')
/
create or replace package test_package as
TYPE col_table_1 is table of TABLE_A%ROWTYPE;
function test_plsql_table(par1 varchar2) return col_table_1
pipelined;
end;
/
create or replace package body test_package as
function test_plsql_table(par1 varchar2) return col_table_1
PIPELINED as
cursor temp_cur is
SELECT * FROM table_a where test_a = par1;
begin
for cur_rec in temp_cur loop
pipe row(cur_rec);
end loop;
end;
end;
/
SELECT * from TABLE( test_package.test_plsql_table('a'))
/
Best Answer
Edit: Please see the additional notes at the bottom for how this can likely be done by using PIPELINE functions.
PL/SQL in ORACLE does not return Datasets in the same way that other RDBMS do (like SQL Server, where you can just pop a random SELECT statement anywhere in a block or procedure and it will output the results to the grid).
Oracle will only output to the screen as a standard SELECT query or via a refcursor.
What you're doing is known as an anonymous block. Anonymous Blocks do not display the results of a SELECT statement. They exist to "get work done." I wrote a blog post that explains this a bit, you're welcome to take a look: https://sqldevdba.com/f/t-sql-vs-plsql-series-part-2-select-into
For your issue: You have a few options, including:
2. Write the results to a table (Using Insert INTO ... SELECT FROM), then Select from that table when done. (This is my preferred approach when running scheduled items like reports). This will also be the approach that gets this "out the door" the fastest, as you'll then be able to query the results immediately.
For option 2 (which I'd likely take but of course you don't have to), it would be something like:
EDIT!! Well, my world has been turned upside down. While this approach isn't perfect, it CERTAINLY changes things.
Return many rows on a plsql Oracle10g
This solution allows you to set up and deploy a function, and have that function return datasets from a SELECT statements. Give it a shot!!