How to Find Free Space in Oracle table - database

In the below script i am checking regularly the table size in my oracle database but i would like to be able to check free space on the database too.
Is there any way to add how much the free space is ? please
select user_segments.SEGMENT_NAME AS Table_Name,
user_segments.BYTES/1024/1024 AS Table_Size_MB,
my_indexes.Indexes_Size_MB AS Indexes_Size_MB,
((user_segments.BYTES/1024/1024) + my_indexes.Indexes_Size_MB) AS Tot_Size_MB,
u_tables.Num_Rows AS NUM_ROWS
from USER_SEGMENTS
inner join (
select
TABLE_NAME AS INDX_TABLE_NAME,
SUM(BYTES)/1024/1024 AS Indexes_Size_MB
from (
select
user_indexes.TABLE_NAME,
user_segments.SEGMENT_NAME,
user_segments.BYTES
from user_segments
inner join user_indexes ON user_segments.SEGMENT_NAME = user_indexes.INDEX_NAME
) group by TABLE_NAME
) my_indexes on my_indexes.INDX_TABLE_NAME = user_segments.SEGMENT_NAME
inner join (
select
TABLE_NAME AS USR_TABLE_NAME,
Num_Rows
from user_tables
) u_tables on u_tables.USR_TABLE_NAME = my_indexes.INDX_TABLE_NAME
order by TOT_SIZE_MB desc;

Here's an example of what you could do
Here's my table starting "clean"
SQL> create table t as
2 select d.* from dba_objects d,
3 ( select 1 from dual connect by level <= 20 );
Table created.
SQL>
SQL>
SQL> select num_rows, avg_row_len, blocks, empty_blocks
2 from user_tables
3 where table_name = 'T';
NUM_ROWS AVG_ROW_LEN BLOCKS EMPTY_BLOCKS
---------- ----------- ---------- ------------
1745660 131 33746 0
1 row selected.
Now I'll try see if I can get to the number using an estimate based on the stats I have
SQL> select num_rows*avg_row_len/8192*100/(100-pct_free) est_blocks
2 from user_tables
3 where table_name = 'T';
EST_BLOCKS
----------
31016.9081
1 row selected.
I'm close but a little bit off which is to be expected, because blocks have some overhead etc. But I can find out what the overhead is
SQL> select round(32300/29800,2) est_overhead from dual;
EST_OVERHEAD
------------
1.08
1 row selected.
So if I factor in that 8% into my calcs (for a clean table), I can now use the dictionary stats to get a good estimate of the expected blocks required for this table given the nunber of rows and their size is.
SQL> select num_rows*avg_row_len/8192*100/(100-pct_free)*1.08 est_blocks
2 from user_tables
3 where table_name = 'T';
EST_BLOCKS
----------
33498.2607
1 row selected.
Armed with this information, you can now do easy comparisons between what the size of a table is, versus what you would expect it to be based on the rows it contains
SQL> delete from t
2 where mod(object_id,3) = 0;
582000 rows deleted.
SQL>
SQL> exec dbms_stats.gather_table_stats('','T')
PL/SQL procedure successfully completed.
My calc suggests the table should be 22329 blocks but its actually 33746.
SQL> select blocks, num_rows*avg_row_len/8192*100/(100-pct_free)*1.08 est_blocks
2 from user_tables
3 where table_name = 'T';
BLOCKS EST_BLOCKS
---------- ----------
33746 22329.999
1 row selected.
Lets see how good the estimate was. I'll reorg the table to reclaim that space
SQL> alter table t move;
Table altered.
SQL>
SQL>
SQL> exec dbms_stats.gather_table_stats('','T')
PL/SQL procedure successfully completed.
SQL>
SQL>
SQL> select num_rows, avg_row_len, blocks, empty_blocks
2 from user_tables
3 where table_name = 'T';
NUM_ROWS AVG_ROW_LEN BLOCKS EMPTY_BLOCKS
---------- ----------- ---------- ------------
1163660 131 22536 0
1 row selected.
SQL>
So you can use a similar approach (and 8% is probably a good enough fudge factor)

Related

ORACLE PLSQL - Query data in a package with the result of a table column

I have 1 table with 500k records records and for each record in the table I would like to query an oracle package and return the rows from this query. How can I do this with PL SQL ORACLE?
I tried to do it here:
declare
cursor c_t is select COLUM_TABLE from SCHEMA.COMPANY;
szSql varchar2(2048);
begin
for rec in c_t loop
szSql := 'SELECT * FROM SCHEMA.PKG_COMPANY.GET_DATA_COMPANY('||rec.COLUM_TABLE||')';
dbms_output.put_line(szSql);
execute immediate szSql;
end loop;
end;
I would like to know how to return the data as a common query and if there is a more performant way to do it.
Could you help me with examples?
EDIT
When I call the package, I get the following return:
This data is the result of a complex query that the package makes
ID_COMPANY | REGION | LATITUDE | LONGITUDE | DENSITY | COUNTRY | ROLE
1. WEST. -0110110. -0110110. 22. EUA. SUBS
how to return the data as a common query and if there is a more performant way to do it
How about a function that returns ref cursor? You'd just pass table name to it and get the result:
SQL> create or replace function f_test (par_table_name in varchar2)
2 return sys_refcursor
3 is
4 l_rc sys_refcursor;
5 begin
6 open l_rc for 'select * from ' || dbms_assert.sql_object_name(par_table_name);
7 return l_rc;
8 end;
9 /
Function created.
Let's test it:
SQL> select f_test('dept') from dual;
F_TEST('DEPT')
--------------------
CURSOR STATEMENT : 1
CURSOR STATEMENT : 1
DEPTNO DNAME LOC
---------- -------------- -------------
10 ACCOUNTING NEW YORK
20 RESEARCH DALLAS
30 SALES CHICAGO
40 OPERATIONS BOSTON
Another table:
SQL> select f_test('invoice') from dual;
F_TEST('INVOICE')
--------------------
CURSOR STATEMENT : 1
CURSOR STATEMENT : 1
DATA_RUN_ FI INVOICE_ID INVOICE_
--------- -- ---------- --------
01-JUL-22 Q4 12345 Paid
01-JAN-22 Q1 12345 Not Paid
01-JUL-22 Q4 12678 Paid
01-JAN-22 Q1 12678 Not Paid
SQL>
As of your code: it is unclear what it does. There's some package and a function, but that's a black box for us as you didn't post it. Also, you're fetching values from the company table; what does it contain? Too many unknown things to debug your code.
If SCHEMA.PKG_COMPANY.GET_DATA_COMPANY() is a function and return a 'select' query like this:
select x,y,...,z from table where ....
then you can write the result into a target table:
cl scr
set SERVEROUTPUT ON
declare
cursor c_t is select COLUM_TABLE from SCHEMA.COMPANY;
szSql varchar2(3000);
begin
for rec in c_t loop
szSql := 'insert into tbl_target '||SCHEMA.PKG_COMPANY.GET_DATA_COMPANY(rec.COLUM_TABLE)||' ';
dbms_output.put_line(szSql);
execute immediate szSql;
commit;
end loop;
end;
in this manner you execute s statement like bellow and insert the result in tbl_target:
insert into tbl_target select x,y,...,z from table where ....
I can not write exact code because SCHEMA.PKG_COMPANY.GET_DATA_COMPANY() is not defined for me.

Query using a statement within a VARCHAR2 column

Is there a way for a select statement to include in the WHERE clause a statement that is contained within the table? For example, the following table:
CREATE TABLE test_tab(
date_column DATE,
frequency NUMBER,
test_statement VARCHAR2(255)
)
/
If
MOD(SYSDATE - DATE, frequency) = 0
were contained within the column test_statement, is there a way to select rows where this is true? The test_statement will vary and not be the same throughout the table. I am able to do this in PL/SQL but looking to do this without the use of PL/SQL.
This kind of dynamic SQL in SQL can created with DBMS_XMLGEN.getXML. Although the query looks a bit odd so you might want to consider a different design.
First, I created a sample table and row using your DDL. I'm not sure exactly what you're trying to do with the conditions, so I simplified them into two rows with simpler conditions. The first row matches the first condition, and neither row matches the second condition.
--Create sample table and row that matches the condition.
CREATE TABLE test_tab(
date_column DATE,
frequency NUMBER,
test_statement VARCHAR2(255)
)
/
insert into test_tab values(sysdate, 1, 'frequency = 1');
insert into test_tab values(sysdate, 2, '1=2');
commit;
Here's the large query, and it only returns the first row, which only matches the first condition.
--Find rows where ROWID is in a list of ROWIDs that match the condition.
select *
from test_tab
where rowid in
(
--Convert XMLType to relational data.
select the_rowid
from
(
--Convert CLOB to XMLType.
select xmltype(xml_results) xml_results
from
(
--Create a single XML file with the ROWIDs that match the condition.
select dbms_xmlgen.getxml('
select rowid
from test_tab where '||test_statement) xml_results
from test_tab
)
where xml_results is not null
)
cross join
xmltable
(
'/ROWSET/ROW'
passing xml_results
columns
the_rowid varchar2(128) path 'ROWID'
)
);
This calls for dynamic SQL, so - yes, it is PL/SQL that handles it. I don't think that SQL layer is capable of doing it.
I don't know what you tried so far, so - just an idea: a function that returns ref cursor might help, e.g.
SQL> create table test (date_column date, frequency number, test_statement varchar2(255));
Table created.
SQL> insert into test values (trunc(sysdate), 2, 'deptno = 30');
1 row created.
SQL> create or replace function f_test return sys_refcursor
2 is
3 l_str varchar2(200);
4 l_rc sys_refcursor;
5 begin
6 select test_statement
7 into l_str
8 from test
9 where date_column = trunc(sysdate);
10
11 open l_rc for 'select deptno, ename from emp where ' || l_str;
12 return l_rc;
13 end;
14 /
Function created.
Testing:
SQL> select f_test from dual;
F_TEST
--------------------
CURSOR STATEMENT : 1
CURSOR STATEMENT : 1
DEPTNO ENAME
---------- ----------
30 ALLEN
30 WARD
30 MARTIN
30 BLAKE
30 TURNER
30 JAMES
6 rows selected.
SQL>
A good thing about it is that you could save the whole statements into that table and run any of them using the same function.
You can try this
select * from test_tab where mod(sysdate - date, frequency) = 0;

Merging records in a table and update related tables

I have a main table containing users that are linked to various other tables. Sometimes there are duplicates in this main table due to bad imported data and I would like to merge them. See the following tables.
Table: Users
UserID Username FirstName LastName
1 Main John Doe
2 Duplicate John Doo
Table: Records1
RecordID RecordName CreatedUserID UpdatedUserID
1 Test record 1 1 2
2 Test record 2 2 null
3 Test record 3 2 null
CreatedUserID and UpdatedUserID are foreign columns of Users.UserID.
So currently if I want to merge user 1 and 2, I would do it with these SQL statements:
UPDATE Records1 SET UpdatedUserID = 1 WHERE UpdatedUserID = 2
UPDATE Records1 SET CreatedUserID = 1 WHERE CreatedUserID = 2
DELETE FROM Users WHERE UserID = 2
This is just a sample subset but in reality, there are a LOT of related records tables for which I have to add additional SQL-Update statements.
I know I'm probably pushing my luck here, but is there perhaps a way to accomplish the above (update all related tables in a batch and delete the "duplicate" record) rather than updating each foreign field and each related table manually. The users table is basically the base table that links to all other tables so to create individual statements for each table is rather cumbersome so if a shortcut is available, that would be great.
is this helpful.?
Create Table Users(Id int, UserName varchar(10),FirstName varchar(10), LastName Varchar(10))
Create Table Records1(RecordID int, RecordName varchar(20), CreatedUserID int, UpdatedUserID int)
INSERT INTO Users
SELECT 1,'Main','John','Doe' Union All
SELECT 2,'Duplicate','John','Doo' Union All
SELECT 3,'Main3','ABC','MPN' Union All
SELECT 4,'Duplicate','ABC','MPT'
Insert into Records1
SELECT 1,'Test record 1',1,2 Union All
SELECT 2,'Test record 2',2,null Union All
SELECT 3,'Test record 3',2,null Union All
SELECT 1,'Test record 1',3,4 Union All
SELECT 2,'Test record 2',4,null Union All
SELECT 3,'Test record 3',4,null
Select u1.Id as CreatedUserID,U2.id as UpdatedUserID
Into #tmpUsers
from Users u1
JOIN Users u2
--This Conidition Should be changed based on the criteria for identifying Duplicates
on u1.FirstName=u2.FirstName and U2.UserName='Duplicate'
Where u1.UserName<>'Duplicate'
Update r
Set r.UpdatedUserID=u.CreatedUserID
From Records1 r
JOIN #tmpUsers u on r.CreatedUserID=u.CreatedUserID
Update r
Set r.CreatedUserID=u.CreatedUserID
From Records1 r
JOIN #tmpUsers u on r.CreatedUserID=u.UpdatedUserID
Delete from Users Where UserName='Duplicate'
Select * from Users
Select * from Records1
Drop Table #tmpUsers
Since the process of identifying duplicate accounts will be manual then there will (generally) be pairs of accounts to be processed. (I'm assuming that the Inspector can't tick off 15 user accounts as duplicates in your UI and submit the whole lot for processsing.)
A stored procedure like the following may be a good start:
create procedure MergeUsers
#RetainedUserId Int, -- UserId that is being kept.
#VictimUserId Int -- UserId that is to be removed.
as
begin
-- Validate the input.
-- Optional, but you may want some reality checks.
-- (Usernames are probably unique already, eh?)
declare #UsernameMatch as Int, #FirstNameMatch as Int, #LastNameMatch as Int, #EmailMatch as Int;
select
#UsernameMatch = case when R.Username = V.Username then 1 else 0 end,
#FirstNameMatch = case when R.FirstName = V.FirstName then 1 else 0 end,
#LastNameMatch = case when R.LastName = V.LastName then 1 else 0 end,
#EmailMatch = case when R.Email= V.Emailthen 1 else 0 end
from Users as R inner join
Users as V on V.UserId = #VictimUserId and R.UserId = #RetainedUserId;
if #UsernameMatch + #FirstNameMatch + #LastNameMatch + #EmailMatch < 2
begin
-- The following message should be enhanced to provide a better clue as to which user
-- accounts are being processed and what did or didn't match.
RaIsError( 'MergeUsers: The two user accounts should have something in common.', 25, 42 );
return;
end;
-- Update all of the related tables.
-- Using a single pass through each table and updating all of the appropriate columns may improve performance.
-- The case expression will only alter the values which reference the victim user account.
update Records1
set
CreatedUserId = case when CreatedUserId = #VictimId then #RetainedUserId else CreatedUserId end,
UpdatedUserId = case when UpdatedUserId = #VictimId then #RetainedUserId else UpdatedUserId end
where CreatedUserId = #VictimUserId or UpdatedUserId = #VictimUserId;
update Records2
set ...
where ...;
-- Houseclean Users .
delete from Users
where UserId = #VictimUserId;
end;
NB: Left as an exercise is adding try/catch and a transaction in the SP to ensure that the merge is an all or nothing operation.

Sum of 2 values in a table

I have 2 Tables like below
Table - 1
Bank_Name
Bank_ACNO
Bank_Branch
Bank_Balance
Table - 2
Emp_ID
Amount_Paid
Table-1 contains unique records for each Bank ACNO. But Table 2 contain Multiple records. Now i want to update Table - 1 (Bank_Balance) With Sum(Table-1.Bank_Balance + Amount_Paid) where Table-1.Bank_ACNO=Table-2.Emp_ID.
I tried the below Query which did not Work.
UPDATE Bank_Master
SET Bank_Balance = ( Bank_Master.Bank_Balance
+ Order_Archieve_Temp.Amount_Paid )
OUTER JOIN Order_Archieve_Temp
ON Bank_Balance.Bank_ACNO=Order_Archieve_Temp.Emp_ID)
Here is the SQLFiddel Demo
Below is the Update Query which you can try :
Update T1
set T1.Bank_Balance = t1.Bank_Balance + t2.Amount_Paid
FROM TABLE1 T1,
(select Emp_ID,sum(Amount_Paid) as Amount_Paid
from Table2
group by Emp_ID ) as T2
WHERE T1.Bank_ACNO = T2.Emp_ID
If that's going to remain your table design, you better keep your database under really tight control: in most such circumstances, applications that have to determine a balance will do so by calculating it on-the-fly from some known and well-controlled state (say, from the last statement date) as a sum of that balance, and all the transactions that have occurred after then.
The current design appears vulnerable to miscalculation of the balance, and continued persistence of that error into the future.
Are there any possible concurrency issues here (could multiple parties possibly be executing this same statement from different connections?). What is your transaction isolation level?
Try this query:
BEGIN TRAN;
UPDATE t1
SET Bank_Balance = t1.Bank_Balance + ISNULL(x.Total_Amount_Paid,0)
-- or
-- SET Bank_Balance = ISNULL(t1.Bank_Balance,0) + ISNULL(x.Total_Amount_Paid,0)
-- or
-- SET Bank_Balance = NULLIF(ISNULL(t1.Bank_Balance,0) + ISNULL(x.Total_Amount_Paid,0), 0)
FROM dbo.Table1 t1
OUTER APPLY
(
SELECT SUM(t2.Amount_Paid) AS Total_Amount_Paid
FROM dbo.Table2 t2
WHERE t1.Bank_ACNO = t2.Emp_ID
) x
ROLLBACK
-- COMMIT

T-SQL Grouping Sets of Information

I have a problem which my limited SQL knowledge is keeping me from understanding.
First the problem:
I have a database which I need to run a report on, it contains configurations of a users entitlements. The report needs to show a distinct list of these configurations and a count against each one.
So a line in my DB looks like this:
USER_ID SALE_ITEM_ID SALE_ITEM_NAME PRODUCT_NAME CURRENT_LINK_NUM PRICE_SHEET_ID
37715 547 CultFREE CultPlus 0 561
the above line is one row of a users configuration, for every user ID there can be 1-5 of these lines. So the definition of a configuration is multiple rows of data sharing a common User ID with variable attributes..
I need to get a distinct list of these configurations across the whole table, leaving me just one configuration set for every instance where > 1 has that configuration and a count of instances of that configuration.
Hope this is clear?
Any ideas?!?!
I have tried various group by's and unions, also the grouping sets function to no avail.
Will be very greatful if anyone can give me some pointers!
Ouch that hurt ...
Ok so problem:
a row represents a configurable line
users may be linked to more than 1 row of configuration
configuration rows when grouped together form a configuration set
we want to figure out all of the distinct configuration sets
we want to know what users are using them.
Solution (its a bit messy but the idea is there, copy and paste in to SQL management studio) ...
-- ok so i imported the data to a table named SampleData ...
-- 1. import the data
-- 2. add a new column
-- 3. select all the values of the config in to the new column (Configuration_id)
--UPDATE [dbo].[SampleData]
--SET [Configuration_ID] = SALE_ITEM_ID + SALE_ITEM_NAME + [PRODUCT_NAME] + [CURRENT_LINK_NUM] + [PRICE_SHEET_ID] + [Configuration_ID]
-- 4. i then selected just the distinct values of those and found 6 distinct Configuration_id's
--SELECT DISTINCT [Configuration_ID] FROM [dbo].[SampleData]
-- 5. to make them a bit easier to read and work with i gave them int values instead
-- for me it was easy to do this manually but you might wanna do some trickery here to autonumber them or something
-- basic idea is to run the step 4 statement but select into a new table then add a new primary key column and set identity spec on it
-- that will generate u a bunch of incremental numbers for your config id's so u can then do something like ...
--UPDATE [dbo].[SampleData] sd
--SET Configuration_ID = (SELECT ID FROM TempConfigTable WHERE Config_ID = sd.Configuration_ID)
-- at this point you have all your existing rows with a unique ident for the values combined in each row.
-- so for example in my dataset i have several rows where only the user_id has changed but all look like this ...
--SALE_ITEM_ID SALE_ITEM_NAME PRODUCT_NAME CURRENT_LINK_NUM PRICE_SHEET_ID Configuration_ID
--54101 TravelFREE TravelPlus 0 56101 1
-- now you have a config id you can start to work on building sets up ...
-- each user is now matched with 1 or more config id
-- 6. we use a CTE (common table expression) to link the possibles (keeps the join small) ...
--WITH Temp (ConfigID)
--AS
--(
-- SELECT DISTINCT SD.Configuration_Id --SD2.Configuration_Id, SD3.Configuration_Id, SD4.Configuration_Id, SD5.Configuration_Id,
-- FROM [dbo].[SampleData] SD
--)
-- this extracts all the possible combinations using the CTE
-- on the basis of what you told me, max rows per user is 6, in the result set i have i only have 5 distinct configs
-- meaning i gain nothing by doing a 6th join.
-- cross joins basically give you every combination of unique values from the 2 tables but we joined back on the same table
-- so its every possible combination of Temp + Temp (ConfigID + ConfigID) ... per cross join so with 5 joins its every combination of
-- Temp + Temp + Temp + Temp + Temp .. good job temp only has 1 column with 5 values in it
-- 7. uncomment both this and the CTE above ... need to use them together
--SELECT DISTINCT T.ConfigID C1, T2.ConfigID C2, T3.ConfigID C3, T4.ConfigID C4, T5.ConfigID C5
--INTO [SETS]
--FROM Temp T
--CROSS JOIN Temp T2
--CROSS JOIN Temp T3
--CROSS JOIN Temp T4
--CROSS JOIN Temp T5
-- notice the INTO clause ... this dumps me out a new [SETS] table in my db
-- if i go add a primary key to this and set its ident spec i now have unique set id's
-- for each row in the table.
--SELECT *
--FROM [dbo].[SETS]
-- now here's where it gets interesting ... row 1 defines a set as being config id 1 and nothing else
-- row 2 defines set 2 as being config 1 and config 2 and nothing else ... and so on ...
-- the problem here of course is that 1,2,1,1,1 is technically the same set as 1,1,1,2,1 from our point of view
-- ok lets assign a set to each userid ...
-- 8. first we pull the distinct id's out ...
--SELECT DISTINCT USER_ID usr, null SetID
--INTO UserSets
--FROM SampleData
-- now we need to do bit a of operating on these that's a bit much for a single update or select so ...
-- 9. process findings in a loop
DECLARE #currentUser int
DECLARE #set int
-- while theres a userid not linked to a set
WHILE EXISTS(#currentUser = SELECT TOP 1 usr FROM UserSets WHERE SetId IS NULL)
BEGIN
-- figure out a set to link it to
SET #set = (
SELECT TOP 1 ID
FROM [SETS]
-- shouldn't really do this ... basically need to refactor in to a table variable then compare to that
-- that way the table lookup on ur main data is only 1 per User_id
WHERE C1 IN (SELECT DISTINCT Configuration_id FROM SampleData WHERE USER_ID = #currentUser)
AND C2 IN (SELECT DISTINCT Configuration_id FROM SampleData WHERE USER_ID = #currentUser)
AND C3 IN (SELECT DISTINCT Configuration_id FROM SampleData WHERE USER_ID = #currentUser)
AND C4 IN (SELECT DISTINCT Configuration_id FROM SampleData WHERE USER_ID = #currentUser)
AND C5 IN (SELECT DISTINCT Configuration_id FROM SampleData WHERE USER_ID = #currentUser)
)
-- hopefully that worked
IF(#set IS NOT NULL)
BEGIN
-- tell the usersets table
UPDATE UserSets SET SetId = #set WHERE usr = #currentUser
set #set = null
END
ELSE -- something went wrong ... set to 0 to prevent endless loop but any userid linked to set 0 is a problem u need to look at
UPDATE UserSets SET SetId = 0 WHERE usr = #currentUser
-- and round we go again ... until we are done
END
SELECT
USER_ID,
SALE_ITEM_ID, ETC...,
COUNT(*) WhateverYouWantToNameCount
FROM TableNAme
GROUP BY USER_ID

Resources