Tuesday, December 27, 2016

How to analyze a specific partition only using dbms_stats

To analyze only specific partitions, including subpartitions, use the dbms_stats.gather_table_stats while specifying the directives PART_NAME and GRANULARITY.

The following query reveals that several of the recently added subpartitions in the COMPLETED_ORDERS partition have not yet been analyzed:
SELECT P.PARTITION_NAME "partition name",P.NUM_ROWS "partition rows",P.LAST_ANALYZED "last analyzed", SP.SUBPARTITION_NAME "subpart name", SP.NUM_ROWS "subpartition rows",SP.LAST_ANALYZED "subpart last analyzed"
FROM DBA_TAB_PARTITIONS P INNER JOIN DBA_TAB_SUBPARTITIONS SP
ON (P.TABLE_NAME = SP.TABLE_NAME)
WHERE P.TABLE_NAME='ORDERS'
AND P.PARTITION_NAME = SP.PARTITION_NAME
AND P.PARTITION_NAME = 'COMPLETED_ORDERS'
AND SP.NUM_ROWS IS NULL
ORDER BY P.PARTITION_NAME DESC,SP.SUBPARTITION_NAME;

Output:

partition name partition rows last analyzed subpart name subpartition rows subpart last analyzed
COMPLETED_ORDERS
2527257
19/10/2015 07:17:44 CO_SP_2017    
COMPLETED_ORDERS
2527257
19/10/2015 07:17:44 CO_SP_2018    
COMPLETED_ORDERS
2527257
19/10/2015 07:17:44 CO_SP_2019    
COMPLETED_ORDERS
2527257
19/10/2015 07:17:44 CO_SP_2020    

To analyze them, use dbms_stats with the PART_NAME directive, and the GRANULARITY directive set to "SUBPARTITION"
BEGIN
  SYS.DBMS_STATS.GATHER_TABLE_STATS (
     OwnName        => 'SCOTT'
    ,TabName        => 'ORDERS'
    ,partname       => 'COMPLETED_ORDERS'
    ,Estimate_Percent  => DBMS_STATS.AUTO_SAMPLE_SIZE
    ,method_opt        => 'FOR ALL COLUMNS SIZE AUTO'
    ,Degree            => DBMS_STATS.AUTO_DEGREE
    ,Cascade           => DBMS_STATS.AUTO_CASCADE
    ,granularity       => 'SUBPARTITION'
    ,No_Invalidate     => DBMS_STATS.AUTO_INVALIDATE);
END;
/

After statistics gathering, execute the query again, but without the "AND SP.NUM_ROWS IS NULL" predicative:

partition name partition rows last analyzed subpart name subpartition rows subpart last analyzed
COMPLETED_ORDERS
2527257
19/10/2015 07:17:44 CO_SP_2017
0
27/12/2016 09:10:04
COMPLETED_ORDERS
2527257
19/10/2015 07:17:44 CO_SP_2018
0
27/12/2016 09:10:05
COMPLETED_ORDERS
2527257
19/10/2015 07:17:44 CO_SP_2019
0
27/12/2016 09:10:07
COMPLETED_ORDERS
2527257
19/10/2015 07:17:44 CO_SP_2020
0
27/12/2016 09:10:08


The 12.2 version of dbms_stats.gather_table_stats is documented here

Wednesday, December 21, 2016

How to create a partitioned table using a subpartition template

Using templates for your subpartitions is a handy shortcut to avoid specifying attributes for every subpartition in the table.

Oracle calls this concept "vertical striping", since it allows for each subpartition matching the template to end up in the same tablespace and thus allows you to "stripe" your partitions across multiple tablespaces, even though they logically belong to different partitions.

You only describe subpartitions once in the template; Oracle will then apply the template to every partition you create in the table.

The original table's layout is shown below. It is an imaginative tables that will store received documents in a governmental office.
In this example, the documents stored in the CLOB column will be saved in the same tablespace based on year, rather than the type of document it represents.

It will create

* A heap-organized, LIST-LIST partitioned table
* A column of type CLOB (character large object). We will call the CLOB object "REC_DOCS_XML_CLOB"
* 2 pre-defined LIST partitions
* 4 pre-defined LIST sub-partitions
* A subpartition template to simplify future additions of partitions.

CREATE TABLE RECEIVED_DOCUMENTS
(
  UUID                 VARCHAR2(160 BYTE),
  FISCAL_YEAR          NUMBER(4),
  DOCUMENTTYPE         VARCHAR2(100 CHAR),
  DOCUMENTNAME         VARCHAR2(1000 CHAR),
  DOCUMENTSTATE        VARCHAR2(30 CHAR),
  VALID                CHAR(1 BYTE),
  CREATED_TIMESTAMP    NUMBER(20),
  VERSION              NUMBER(20),
  DATA_XML             CLOB,
  FORMAT               VARCHAR2(1000 CHAR),
  PERIOD               VARCHAR2(1000 CHAR)
 )
LOB (DATA_XML) STORE AS SECUREFILE REC_DOCS_XML_CLOB(
 TABLESPACE RECEIVED_DOCUMENTS_LOB_DATA
 COMPRESS HIGH
)
-- The table definition is stored in the tablespace DOCS_DATA
TABLESPACE DOCS_DATA
PARTITION BY LIST (DOCUMENTTYPE)
SUBPARTITION BY LIST (PERIOD)
SUBPARTITION TEMPLATE
  (SUBPARTITION SP_2014 VALUES ('2014') TABLESPACE DOCS_2014,
   SUBPARTITION SP_2015 VALUES ('2015') TABLESPACE DOCS_2015,
   SUBPARTITION SP_2016 VALUES ('2016') TABLESPACE DOCS_2016,
   SUBPARTITION SP_DEFAULT VALUES (default) TABLESPACE DOCS_DATA
   )
(
  PARTITION SETTELM
    VALUES ('SETTLEM_ACCEPTED', 'SETTLEM_REJECTED'),
  PARTITION APPLICATIONS
    VALUES ('SINGLE_PARENT_SUPP','UNEMPLOYMENT_SUPP','CHILD_SUPP','HOUSING_SUPP')    
)
;

If you later would like to add a partition, it is as simple as
ALTER TABLE RECEIVED_DOCUMENTS
 ADD PARTITION APPLICANT VALUES ('FAMILY','SINGLE_PARENT','ASYLUM_SEEKER')
;

Similarly, dropping a partition with its subpartition would be done with:
ALTER TABLE RECEIVED_DOCUMENTS
DROP PARTITION APPLICATION;

Notice that if you have a default partition to handle incoming data that doesn't fit in any particular partition, you will get an error when attempting to add a partition.

Sources:

"Specifying Subpartition Templates to Describe Composite Partitioned Tables"

"Specifying a Subpartition Template for a *-List Partitioned Table"

How to remove the APEX option from the database



cd $ORACLE_HOME/apex
sqlplus / as sysdba
@apxremov.sql
drop public synonym htmldb_system;
drop PACKAGE HTMLDB_SYSTEM;

If you have older APEX installations, they may be left in the database, but not registred in the dba_registry.

I had an old APEX schema called APEX_030200, with several invalid objects:

OWNER OBJECT_TYPE COUNT(*)
PUBLIC SYNONYM
3
APEX_030200 PACKAGE
2
APEX_030200 PACKAGE BODY
114
APEX_030200 PROCEDURE
3


This means that the script above won't work. You will get this output when attempting to remove the installation:
sqlplus / as sysdba @apxremov.sql
...
Error:
You can only use this script to remove Application Express

I found some useful information about these situations at this blog

Basically, you can simply drop the old schema directly.

Check first:
SELECT username, 'drop user ' || username || ' cascade;' AS remove_statement
  FROM dba_users
 WHERE     (username LIKE 'FLOWS_%' OR username LIKE 'APEX_%')
       AND username NOT IN ('FLOWS_FILES',
                            'APEX_PUBLIC_USER',
                            'APEX_LISTENER',
                            'APEX_REST_PUBLIC_USER',
                            'APEX_INSTANCE_ADMIN_USER')
       AND username NOT IN (SELECT schema s
                              FROM dba_registry
                             WHERE comp_id = 'APEX');

So cleaning up can be done as easily as this:
drop user APEX_030200 cascade;

Tuesday, December 20, 2016

Will interrupting a "split partition"-command cause any harm to your database?

Will interrupting a "alter table split partition"-command cause any harm to your database?

No, it will not. It is safe to kill the session. Oracle will be able to recover and the table would be left in its original state, prior to time when the "alter table split partition" command was executed.

I was recently in contact with Oracle support regarding such a case. My goal was to split a sub-partition, in order to accommodate rows for years 2017 to 2020, but there was way too much concurrent activities in the database to complete the "alter table split partition"-command.
The database was almost completely non-responsive and there was pressure to finish within a certain time frame.

The supporting engineer confirmed that there would be some rollback activities in the wake of this command, how much depended on the activity level in the database during the time when the DDL was executing.

He added the following explanation to how Oracle handles the split internally:

As for kill the split partition, the way it works internally is to create temporary segments for the 2 new partitions it is splitting into, and when the operation is complete, these new temporary segments are attached to the original table and become the new partitions. So the result of this is that it's perfectly safe to terminate the split partition, and it simply stops its work, drops the temporary segments, and you are left with the original table in-tact, so there will be no ill-effects of this.

Sunday, December 18, 2016

How to create a common and a local user in a 12c multitenant database

In a multitenant container database, there are two types of users:

* Common users, who are known in both the root containers and in all the pluggable database containers
* Local users, who are only known in a single pluggable database container

Common users can, if granted the necessary privileges, perform administrative tasks across all the PDBs in multitenant database.
They can also perform tasks specific to the container database, also called the ROOT container.

To create a common user, make sure you are connected to the ROOT container

On my client, my tnsnames.ora entry looks as follows:
# container database
CDB =
   (DESCRIPTION =
     (ADDRESS = (PROTOCOL = TCP)(HOST = myhost.mydomain.no)(PORT = 1531))
     (CONNECT_DATA =
       (SERVER = DEDICATED)
       (SERVICE_NAME = "container#db01")
     )
   )


Since my client is a windows pc, I start sqlplus from the start menu, and connect as system@cdb and enter the password.
My default container will be CDB$ROOT, which is what you need to create a common user:
show con_name

CON_NAME
------------------------------
CDB$ROOT


Create the common user:
create user C##DBAMASTER
identified by ****
default tablespace USERS
temporary tablespace TEMP
quota unlimited on USERS
container=all;

SQL> user C##DBAMASTER created.

Note the new rules for creating common users in a CDB database:
In Oracle Database 12c Release 1 (12.1.0.1), the name of a common user must begin with C## or c##

Verify that the common user was created:
select USERNAME,CREATED,LAST_LOGIN,COMMON
from dba_USERS
where trunc(created) = trunc(sysdate)

USERNAME             CREATED   LAST_LOGIN                               COM
-------------------- --------- ---------------------------------------- ---
C##DBAMASTER         18-DEC-16 18-DEC-16 03.16.16.000000000 PM +01:00   YES


Give the common user the right to create a session, to switch between all containers in the CDB and the right to create and drop users in all the containers in the CDB:
grant connect to C##DBAMASTER;
grant set container to C##DBAMASTER container=all;
grant create user to C##DBAMASTER container=all;
grant drop user to C##DBAMASTER container=all;

SQL> Grant succeeded.

Connect to ROOT container as the new common user:
connect C##DBAMASTER/***@cdb
Connected.
show user
USER is "C##DBAMASTER"
Switch container:
alter session set container=vpdb01;

SQL> Session altered.
Create a new local user in the current container:
create user "1Z0061"
identified by ****
default tablespace users
quota unlimited on users
temporary tablespace TEMP
container=current;

SQL> User created.

Note that without the "container=all" privilege, the new common user C##DBAMASTER cannot connect directly to the vpdb01 pluggable database

Here is what happened:
SQL> connect C##DBAMASTER/****@vpdb01
ERROR:
ORA-01045: user C##DBAMASTER lacks CREATE SESSION privilege; logon denied


Warning: You are no longer connected to ORACLE.
SQL> connect system/*****@vpdb01
Connected.
SQL> show con_name

CON_NAME
------------------------------
VPDB01
SQL> grant create session to C##DBAMASTER container=current;

Grant succeeded.

SQL> connect C##DBAMASTER/****@vpdb01
Connected.
SQL> show con_name

CON_NAME
------------------------------
VPDB01

This means that these two privileges are distinctly different:
grant create session to C##DBAADMIN container=all;
vs
grant set container to C#DBAADMIN container=all;

How to display the current container in an Oracle multitenant database

You can display the current container your session is currently connected to using three different methods.

First, connect to either the container database or one of the pluggable databases running out of your container database. In this example, I chose to connect to the CDB.


1. Display the current container using the "show con_name" or "show con_id" in sqlplus
SQL> show con_name

CON_NAME
------------------------------
CDB$ROOT --> We are connectted to the ROOT container.

Switch to a different container, and try again:
SQL> alter session set container=vpdb01;

Session altered.

SQL> show con_name

CON_NAME
------------------------------
VPDB01 --> We are now connected to the container named VPDB01.


SQL> show con_id

CON_ID
------------------------------
6 --> The current container id is 6.


2. Display the current container using the SYS_CONTEXT function

In the example below I have thrown in a couple of other useful parameters, along with the
"con_name" and the "con_id" parameters:

select SYS_CONTEXT('userenv','con_name') "container name",
        SYS_CONTEXT('userenv','con_id') "container id",
        SYS_CONTEXT('userenv','CURRENT_SCHEMA') "Current schema",
        SYS_CONTEXT('userenv','SID') "SID"
FROM DUAL;

container name       container id         Current schema       SID
-------------------- -------------------- -------------------- --------
VPDB01               6                    SYSTEM               63

From the output above, we see that we are connected to the container name VPDB01 with con_id 6.

Switch to the root container again, and verify that we are now connected to the ROOT container:
SQL> alter session set container=CDB$ROOT;

Session altered.
Rerun the SYS_CONTEXT statement:
select SYS_CONTEXT('userenv','con_name') "container name",
        SYS_CONTEXT('userenv','con_id') "container id",
        SYS_CONTEXT('userenv','CURRENT_SCHEMA') "Current schema",
        SYS_CONTEXT('userenv','SID') "SID"
FROM DUAL;

container name       container id         Current schema       SID
-------------------- -------------------- -------------------- --------
CDB$ROOT             1                    SYSTEM               63


3. Display the current container using V$CONTAINERS view

This final method only makes sense if you are connected to a non-root container.

SQL> alter session set container=vpdb01;

Session altered.
The result:
SQL> select CON_ID,DBID,name,OPEN_MODE
  2  from v$containers order by con_id;

    CON_ID       DBID NAME                           OPEN_MODE
---------- ---------- ------------------------------ ----------
         6 2049231443 VPDB01                         READ WRITE

If you are connected to the root container, and when querying the v$container, the resulting output will list all containers in your cdb:
SQL> select CON_ID,DBID,name,OPEN_MODE
  2  from v$containers order by con_id;

    CON_ID       DBID NAME                           OPEN_MODE
---------- ---------- ------------------------------ ----------
         1 2294367729 CDB$ROOT                       READ WRITE
         2 3254699093 PDB$SEED                       READ ONLY
         6 2049231443 VPDB01                         READ WRITE

3 rows selected.
I have also written a short blog post about the same topic here, but with a slightly different angle.

Friday, December 16, 2016

How to change passwords for eBS schemas in an Oracle database

In an Oracle database supporting eBS, there are a number of schemas that supports the different modules in eBS. Everything is installed and must be maintained, regardless if the module is in use or not.
If you want to change the passwords for these accounts, you need to do that through the utility AFPASSWD or the deprecated FNDCPASS utility.

Requirements:

* The environment variable TWO_TASK must be set on the eBS application server
* The SYSTEM password for your eBS database
* The current APPS password

I strongly recommend using the same password for all eBS schemas.
Unfortunately, you cannot use special characters in the passwords, so you need to compensate this by making them at least 12-20 characters lang.

To change the passwords, use the following procedure:

1. logon to your eBS application server
2. change user to the owner of the software installation
3. shut down all eBS processes
4. take backup of FND_USER and FND_ORACLE_USERID tables (you can use CTAS for this)
connect apps/oldpassword
create table FND_USER_BUP AS SELECT * FROM FND_USER;
create table FND_ORACLE_USERID_BUP AS SELECT * FROM FND_ORACLE_USERID;

5.
cd $FND_TOP/bin
6. Change the password for all type 3 passwords (meaning all eBS schemas except APPS, APPLSYS,APPLSYSPUB)
./AFPASSWD -c APPS@$TWO_TASK -a

7. Change the passwords for type 2 schema password
./AFPASSWD -c APPS@$TWO_TASK -s APPS

8. Change the password for type 2 schema password APPLSYS
./AFPASSWD -c APPS@$TWO_TASK -s APPLSYS 


9. Change the password for type 2 schema password for APPLSYSPUB. Note that the APPLSYSPUB password must be uppercase, even if you have enabled case sensitive passwords
./AFPASSWD -c APPS@$TWO_TASK -o APPLSYSPUB

10. Update the s_gwyuid_pass variable in the AutoConfig context file to reflect the new password
vi $CONTEXT_FILE
Change from
<oa_user type="GWYUID">
            <username oa_var="s_gwyuid_user">APPLSYSPUB</username>
            <password oa_var="s_gwyuid_pass">PUB</password>
         </oa_user>
to
<oa_user type="GWYUID">
            <username oa_var="s_gwyuid_user">APPLSYSPUB</username>
            <password oa_var="s_gwyuid_pass">YOURNEWSECRETCOMPLICATEDPASSWORD</password>
         </oa_user>


11. verify that the users now have a new password:
sqlplus /nolog
SQL> connect APPS/YOURNEWSECRETCOMPLICATEDPASSWORD
Connected.
SQL> show user
USER is "APPS"

12. start your eBS application processes

Here is a query that will extract the description of each schema, and when the password was last changed:
SELECT ORACLE_USERNAME,LAST_UPDATE_DATE, DESCRIPTION
FROM FND_ORACLE_USERID
WHERE ORACLE_USERNAME IN ('APPS','APPLSYSPUB','APPLSYS','AR','GMO','PFT')
ORDER BY LAST_UPDATE_DATE;
Output:

ORACLE_USERNAME LAST_UPDATE_DATE DESCRIPTION
AR 15.12.2016 16:28:45 Oracle Receivables Account
GMO 15.12.2016 16:28:47 Oracle Manufacturing Execution System for Process Manufacturing Account
PFT 15.12.2016 16:28:47 Oracle Profitability Manager Account
APPLSYS 15.12.2016 16:30:34 Application Object Library Account
APPS 15.12.2016 16:30:34 APPS #1 Account
APPLSYSPUB 15.12.2016 16:31:09 Application Object Library Public Account

For a complete list, leave out the WHERE-clause.

Thursday, December 15, 2016

Cloning a pluggable database in 12c


I have created a CDB using Oracles database configuration assistant (dbca).
While installing, I choosed to create a Pluggable database, too, called pdbvegdb01.

If you want to make a clone of a PDB, it's a relative simple process.


1. Check your current setup by connecting to the root container (CDB$ROOT), and issue the following query:


select C.PDB_ID,C.PDB_NAME,C.STATUS,C.CON_ID "container id", P.OPEN_MODE,P.restricted
from CDB_PDBS C inner join V$PDBS P
on C.CON_ID = P.CON_ID
order by c.con_id;


PDB_ID PDB_NAME STATUS container id OPEN_MODE RESTRICTED
2 PDB$SEED NORMAL 2 READ ONLY NO
3 PDBVEGDB01 NORMAL 3 READ WRITE NO

I would like to clone the PDBVEGDB01, to a new one, that I will name vpdb01.

2. Check if the root container uses OMF (Oracle Managed Files)
select name,value,DESCRIPTION 
from V$SYSTEM_PARAMETER 
WHERE NAME in( 'db_create_file_dest');

NAME VALUE DESCRIPTION
db_create_file_dest /u02/oradata default database location

It does, and that means your new PDB will inherit this setting.

In this example, I would NOT like Oracle to manage the files in my PDB.
I want to place them in a folder called /u02/oradata/VPDB01.

3. Make directory on server:
mkdir -p /u02/oradata/VPDB01

4. Check the names and paths of the files being used by the source PDB:
select FILE_NAME from CDB_DATA_FILES where CON_ID=3
union
select file_name from cdb_temp_files where con_id=3;
The result of this union is:

FILE_NAME
/u02/oradata/CONTAINER#DB01/436546A3F70642C3E0537800A8C03C16/datafile/o1_mf_system_d4tytlns_.dbf
/u02/oradata/CONTAINER#DB01/436546A3F70642C3E0537800A8C03C16/datafile/o1_mf_sysaux_d4tytlo2_.dbf
/u02/oradata/CONTAINER#DB01/436546A3F70642C3E0537800A8C03C16/datafile/o1_mf_temp_d4tytlo2_.dbf
/u02/oradata/CONTAINER#DB01/436546A3F70642C3E0537800A8C03C16/datafile/o1_mf_users_d4tyw5y9_.dbf

All the above files must be accounted for in your clone script, when deciding not to continue using OMF.

5. If not already done, switch to the root container:
alter session set container = CDB$ROOT;

6. Close the source pdb, and open it read only:
alter PLUGGABLE database pdbvegdb01 CLOSE;
alter PLUGGABLE database pdbvegdb01 OPEN READ ONLY;

7. Clone the source pdb. Notice that I have matched the source pdb's datafiles, one by one, with a file name of my choosing:
create PLUGGABLE DATABASE VPDB01
from pdbvegdb01
FILE_NAME_CONVERT=(
'/u02/oradata/CONTAINER#DB01/436546A3F70642C3E0537800A8C03C16/datafile/o1_mf_system_d4tytlns_.dbf',
'/u02/oradata/VPDB01/system01.dbf',
'/u02/oradata/CONTAINER#DB01/436546A3F70642C3E0537800A8C03C16/datafile/o1_mf_sysaux_d4tytlo2_.dbf',
'/u02/oradata/VPDB01/sysaux01.dbf',
'/u02/oradata/CONTAINER#DB01/436546A3F70642C3E0537800A8C03C16/datafile/o1_mf_temp_d4tytlo2_.dbf',
'/u02/oradata/VPDB01/temp01.dbf'
'/u02/oradata/CONTAINER#DB01/436546A3F70642C3E0537800A8C03C16/datafile/o1_mf_users_d4tyw5y9_.dbf,
'/u02/oradata/VPDB01/users01.dbf'
);

Your sqlplus session should return "pluggable DATABASE created."

8. Open the source pdb for read/write:
alter pluggable database pdbvegdb01 close;
alter pluggable database pdbvegdb01 open read write;

You now see that your freshly cloned database is in status NEW and is MOUNTED, but not open:
PDB_ID PDB_NAME STATUS container id OPEN_MODE RESTRICTED
2 PDB$SEED NORMAL 2 READ ONLY NO
3 PDBVEGDB01 NORMAL 3 READ WRITE NO
6 VPDB01 NEW 6 MOUNTED  

9. Open the pdb by switching your session container to the newly cloned pdb
alter session set container=vpdb01;
Just to verify, check the open mode of the container.
select CON_ID,DBID,name,OPEN_MODE 
from v$containers order by con_id;
You should have one row worth of output, since you switched from the root container to the pdb container:
CON_ID DBID NAME OPEN_MODE
6 2049231443 VPDB01 MOUNTED

10. Open the pluggable database:
alter pluggable database vpdb01 open;

For the last step to work, you strictly don't need to switch container, it can be done while you have your session set to the root container as well.
But it demonstrates the ability for the DBA to switch containers and work isolated with them, as though they were separate databases.
Finally, let's switch back to the root container and check the status of our PDB's:
alter session set container = CDB$ROOT;

select C.PDB_ID,C.PDB_NAME,C.STATUS,C.CON_ID "container id", P.OPEN_MODE,P.restricted
from CDB_PDBS C inner join V$PDBS P
on C.CON_ID = P.CON_ID
order by c.con_id;
Result:
PDB_ID PDB_NAME STATUS container id OPEN_MODE RESTRICTED
2 PDB$SEED NORMAL 2 READ ONLY NO
3 PDBVEGDB01 NORMAL 3 READ WRITE NO
4 VPDB01 NORMAL 4 READ WRITE NO

How to create a line-by-line comparison between the parameters used in two different databases

Here is a simple way to create a line-by-line comparison between two databases over a database link.

First make sure there is tns Connectivity between the database you execute the script in, and the remote database.

Then, create a datatabase link:
-- create db link from prod to test
create public database link testdb
connect to system
identified by ****
using 'testdb';

-- make sure that the link works before you proceed:
select host_name from v$instance@testdb;

The following script will spool a text file to your current directory, which can be investigated for differences between the two databases' initialization parameters:
set trimspool on
spool parameter_comparison.lst
set lines 200
col name format a40
col "test value" format a50
col "prod value" format a50
select prod.name,prod.value "prod value",test.value "test value"
from v$system_parameter prod full outer join v$system_parameter@testdb test
on prod.name = test.name;

exit

Monday, December 12, 2016

How to find the last password change a user made


Limit the search to those users who changed their password today:

select name,ctime "created", ptime "passw change time",ltime "last locked", lcount "# failed logins"
 from user$
 where trunc(ptime) = trunc(sysdate)
 order by 3 desc;

Example output:

NAME created passw change time last locked # failed logins
JIM 25.11.2016 02:25:38 12.12.2016 09:59:25  
0
JOHN 25.11.2016 02:25:55 12.12.2016 09:53:19  
0
JAMES 25.11.2016 02:25:54 12.12.2016 09:29:50  
0

Wednesday, December 7, 2016

How to find dependencies between views and the tables they're based upon

Use the following Query to list the base tables on which the
select o.object_type,o.owner,o.object_name, CASE WHEN D.REFERENCED_NAME = O.OBJECT_NAME THEN NULL
                                            ELSE D.REFERENCED_NAME || '  --> ' || d.referenced_type 
                                            END "dependency to" 
from dba_objects o join dba_dependencies d
on o.object_name = d.name
where object_name in 
(
'MYVIEW1', 
'MYVIEW2',
) 
and object_type <> 'SYNONYM'
order by object_type desc, "dependency to";

Monday, December 5, 2016

Is it possible to temporarily change the system time on my server, without the Oracle instance complaining?

Yes, it's possible. Oracle doesn't care about the wall-clock time, and will keep the redo log stream intact, regardless of what time the hosting server is set to.

The whole procedure is quite simple:

1. shutdown the database and listener
sqlplus / as sysdba
shutdown immediate
exit
lsnrctl stop
2. as root, change the system time. In my example, I am setting it to 10 days into the future:
Thu Dec 15 12:27:00 CET 2016
  [root@myserver ~]# date
  Thu Dec 15 12:27:01 CET 2016


3. Disable NTP på serveren:
  service ntpd stop

4. Open the instance and the listener
sqlplus / as sysdba
startup
exit
lsnrctl start

5. Verify that the database has picked up the new system time
SQL> select startup_time from v$instance;

STARTUP_TIME
-------------------
15.12.2016 12:28:17

6. Have a look at the archived logs:
select name,sequence#, archived,applied, status, completion_time from v$archived_log;

NAME                                                                      SEQUENCE# ARCHIVED  APPLIED    STA COMPLETION_TIME
------------------------------------------------------------------------- ---------- --------- ---------- --- -------------------
/u04/fra/mydb/archivelog/2016_12_05/o1_mf_1_12538__1480935048084626_.arc  12538 YES       NO         A   05.12.2016 11:50:48
/u04/fra/mydb/archivelog/2016_12_05/o1_mf_1_12539__1480935059227231_.arc  12539 YES       NO         A   05.12.2016 11:50:59
Force a log switch and note the time stamp of the last log:
alter system archive log current;

NAME                                                                      SEQUENCE# ARCHIVED  APPLIED    STA COMPLETION_TIME
------------------------------------------------------------------------- ---------- --------- ---------- --- -------------------
/u04/fra/mydb/archivelog/2016_12_05/o1_mf_1_12538__1480935048084626_.arc  12538 YES       NO         A   05.12.2016 11:50:48
/u04/fra/mydb/archivelog/2016_12_05/o1_mf_1_12539__1480935059227231_.arc  12539 YES       NO         A   05.12.2016 11:50:59
/u04/fra/mydb/archivelog/2016_12_15/o1_mf_1_12540__1481801585872720_.arc  12540 YES       NO         A   15.12.2016 12:33:06 

Change back again:

7. shutdown the database and the listener
sqlplus / as sysdba
shutdown immediate
exit
lsnrctl stop
8. enable ntp to have the server syncronized with the current time:
service ntpd start
9. Open the database
sqlplus / as sysdba
startup
exit
lsnrctl start
10. Check the archived logs again. Note that the log sequence doesn't care what the registered time was at the time of the log switch:

select name,sequence#, archived,applied, status, completion_time from v$archived_log;

NAME                                                                      SEQUENCE# ARCHIVED  APPLIED    STA COMPLETION
------------------------------------------------------------------------- ---------- --------- ---------- --- ----------
/u04/fra/mydb/archivelog/2016_12_05/o1_mf_1_12538__1480935048084626_.arc  12538 YES       NO         A   05.12.2016
/u04/fra/mydb/archivelog/2016_12_05/o1_mf_1_12539__1480935059227231_.arc  12539 YES       NO         A   05.12.2016
/u04/fra/mydb/archivelog/2016_12_15/o1_mf_1_12540__1481801585872720_.arc  12540 YES       NO         A   15.12.2016

alter system archive log current;

select name,sequence#, archived,applied, status, completion_time from v$archived_log;

NAME                                                                      SEQUENCE# ARCHIVED  APPLIED    STA COMPLETION
------------------------------------------------------------------------- ---------- --------- ---------- --- ----------
/u04/fra/mydb/archivelog/2016_12_05/o1_mf_1_12538__1480935048084626_.arc  12538 YES       NO         A   05.12.2016
/u04/fra/mydb/archivelog/2016_12_05/o1_mf_1_12539__1480935059227231_.arc  12539 YES       NO         A   05.12.2016
/u04/fra/mydb/archivelog/2016_12_15/o1_mf_1_12540__1481801585872720_.arc  12540 YES       NO         A   15.12.2016 
/u04/fra/mydb/archivelog/2016_12_05/o1_mf_1_12541__1480937880624137_.arc  12541 YES       NO         A   05.12.2016 

Friday, November 25, 2016

How to find the page size for a Linux installation

In order to set one of the mandatory kernel parameters in a Red Hat flavored Linux distribution, you need to know the page size.
To find it:
[root@myserver]$ getconf PAGESIZE
4096

Monday, November 21, 2016

How to install and use the character set scanner

Although deprecated since the release of DMU (Database Migration utility for Unicode), there are still cases where you need to run csscan.

One such incident happened to me when migrating a database used for Oracles E-Business Suite.

Some errors were found in DMU, but when clicking on the number indicating the number of rows with errors, the Cleansing Editor showed up empty. To get the rowids of the offending rows, I had to resort to the character scanner.

If not installed do as follows:

1. edit the script $ORACLE_HOME/rdbms/admin/csminst.sql so that it has a valid password, and that its object will be saved in a separate tablespace
create user csmig identified by ***** account lock
/
alter user csmig default tablespace tools quota unlimited on tools
/
2. execute the script as sysdba
sqlplus / as sysdba @csminst.sql

That's it. The csscanner tool is now installed.

A simple example of usage would be when I scanned one - 1 - table.
First, I created a parameter file
userid='sys/**** as sysdba'
table=scott.emp
fromchar=WE8ISO8859P1
tochar=al32utf8
array=4096000
process=4
feedback=1000

Execute it as follows:
csscan parfile=myscan.par

This process creates three files for you:
1. scan.out - Screen log file
2. scan.err - individual exception report. This is where you would typically find your rows that contains errors
3. scan.txt - contains the Database Scan Summary Report.

Here's how my scan.err looked like after I scanned the table mentioned above:
Database Scan Individual Exception Report


[Database Scan Parameters]

Parameter                      Value
------------------------------ ------------------------------------------------
CSSCAN Version                 v2.1
Instance Name                  proddb01
Database Version               11.2.0.4.0
Scan type                      Selective tables
Scan CHAR data?                YES
Database character set         WE8ISO8859P1
FROMCHAR                       WE8ISO8859P1
TOCHAR                         al32utf8
Scan NCHAR data?               NO
Array fetch buffer size        4096000
Number of processes            4
Capture convertible data?      NO
------------------------------ ------------------------------------------------

[Data Dictionary individual exceptions]


[Application data individual exceptions]

User  : SCOTT
Table : EMP
Column: ATTRIBUTE15
Type  : VARCHAR2(150)
Number of Exceptions         : 2
Max Post Conversion Data Size: 153

ROWID              Exception Type      Size Cell Data(first 30 bytes)
------------------ ------------------ ----- ------------------------------
AAS/WmAHwAACI5ZAAC exceed column size   153 
AAS/WmAHwAACI5ZAAD exceed column size   153 
------------------ ------------------ ----- ------------------------------
As expected, the offending rows contained Scandinavian characters, which will use 2 bytes after conversion to a Unicode database. Therefore the text will no longer fit, and actions need to be taken by the data owner before the migration.

For more information, look up the official documentation here

Thursday, November 17, 2016

How to create a blackout in Cloud Control using the command-line interface

Source your environment to point to the correct ORACLE_HOME. Put the following in a file called .agentenv
export ORACLE_BASE=/software/oracle
export ORACLE_HOME=/software/oracle/product/agent13c/agent_13.2
export PATH=$ORACLE_HOME/bin:$PATH
echo "\n\nOH is : $ORACLE_HOME"

If you need to create the blackout as a part of a script which needs root privileges, execute the following:
su - oracle -c ". .agent1?c;emctl start blackout  -nodeLevel -d 15;emctl stop agent"

For usage by the oracle software installation owner, skip the first part:
. .agent1?c;emctl start blackout  -nodeLevel -d 15;emctl stop agent

To stop the blackout use
. /home/oracle/.agent1?c;emctl stop blackout 

Monday, November 14, 2016

A short history of the Automated Maintenance Tasks in Oracle

Predefined Automatic System Tasks were introduced in Oracle 10g, and consisted of two jobs:

1. Automated statistics collection
2. The Automated segment space advisor

Two scheduler jobs were set up by default to run these automated tasks: The GATHER_STATS_JOB and the AUTO_SPACE_ADVISOR_JOB.

You could enable or disable the jobs using the procedures enable or disable in the dbms_scheduler package:
EXECUTE DBMS_SCHEDULER.ENABLE('GATHER_STATS_JOB');
EXECUTE DBMS_SCHEDULER.DISABLE('GATHER_STATS_JOB');

In Oracle 11g, Oracle redesigned the the Automatic System Tasks and introduced the concept of Automated Maintenance Tasks.
At the same time, a third automated task was introduced: The Automated SQL Tuning advisor.

Oracle removed the GATHER_STATS_JOB and AUTO_SPACE_ADVISOR_JOB altogether and instead introduced a new package called DBMS_AUTO_TASK_ADMIN.

To enable or disable an Automatic tasks using DBMS_AUTO_TASK_ADMIN, see this short post.

To check the status of your automated maintenance tasks, see this post.

How to check the status of the Automated Maintenance Tasks in Oracle 11g

To see the currently enabled jobs in your database, use the dictionary view DBA_AUTOTASK_OPERATION
which displays all automated maintenance task operations for each client:
select client_name,operation_name,status  
from DBA_AUTOTASK_OPERATION;
Example from one of my 11g databases:
CLIENT_NAME OPERATION_NAME STATUS
auto optimizer stats collection auto optimizer stats job DISABLED
auto space advisor auto space advisor job ENABLED
sql tuning advisor automatic sql tuning task DISABLED

Friday, November 4, 2016

Quick guide to set up Transparent Data Encryption for a tablespace

If not already done, create a folder for the wallet:
# mkdir -p  /u01/oracle/admin/proddb01/wallet

Step 1: configure the software keystore location in the sqlnet.ora file

Edit your $TNS_ADMIN/sqlnet.ora:

SQLNET.WALLET_OVERRIDE = TRUE
SQLNET.AUTHENTICATION_SERVICES = (BEQ, TCPS)
SSL_VERSION = 0
SSL_CLIENT_AUTHENTICATION = FALSE
ENCRYPTION_WALLET_LOCATION =
  (SOURCE =
    (METHOD = FILE)
    (METHOD_DATA =
      (DIRECTORY = /u01/oracle/admin/proddb01/wallet)
    )
  )
SSL_CIPHER_SUITES = (SSL_RSA_WITH_3DES_EDE_CBC_SHA, SSL_RSA_WITH_DES_CBC_SHA)


Step 2: create the software keystore

There are three different types of software keystores.

You can create a) password-based software keystores, b) auto-login software keystores, and c) local auto-login software keystores.
In this guide, I am setting up password-based software keystore.
Password-based software keystores are protected by using a password that you create.

You must open this type of keystore before the keys can be retrieved or used.


Verify wallet location:
SYS@proddb01 SQL> select wrl_parameter, status,wallet_type from  v$encryption_Wallet;

WRL_PARAMETER                            STATUS                         WALLET_TYPE
---------------------------------------- ------------------------------ --------------------
/u01/oracle/admin/proddb01/wallet/        NOT_AVAILABLE                  UNKNOWN

Note that wallet type = UNKNOWN , status=NOT_AVALABLE
Run "ADMINISTER KEY MANAGEMENT" to create the keystore:

SYS@proddb01 SQL> administer key management create keystore '/u01/oracle/admin/proddb01/wallet' identified by "secretpassword";

Keystore altered.


Check the view v$encryption_Wallet now, and you can see that it exists, but the wallet type is still UNKNOWN:

SYS@proddb01 SQL> select wrl_parameter, status,wallet_type from  v$encryption_Wallet;

WRL_PARAMETER                            STATUS                         WALLET_TYPE
---------------------------------------- ------------------------------ --------------------
/u01/oracle/admin/proddb01/wallet/        CLOSED                         UNKNOWN


Step 3: Open the Software Keystore

Depending on the type of keystore you create, you must manually open the keystore before you can use it.
You must manually open a password-based software keystore before any TDE master encryption keys can be created or accessed in the keystore.

After you open a keystore, it remains open until you manually close it.
Each time you restart a database instance, you must manually open the password keystore to reenable encryption and decryption operations.

Open the keystore:
SYS@proddb01 SQL> administer key management set keystore open identified by "secretpassword";
 
Keystore altered.

Check the view v$encryption_wallet again:
SYS@proddb01 SQL> select wrl_parameter, status,wallet_type from  v$encryption_Wallet;
 

WRL_PARAMETER                            STATUS                         WALLET_TYPE
---------------------------------------- ------------------------------ --------------------
/u01/oracle/admin/proddb01/wallet/        OPEN_NO_MASTER_KEY             PASSWORD


Note that if the keystore is open but you have not created a TDE master encryption key yet, the STATUS column of the V$ENCRYPTION_WALLET view reminds you with a status of OPEN_NO_MASTER_KEY.

Step 4: Set the Software TDE Master Encryption Key


The TDE master encryption key is stored in the keystore.
This key protects the TDE table keys and tablespace encryption keys.
By default, the TDE master encryption key is a key that Transparent Data Encryption (TDE) generates.
Password-based software keystores must be open before you can set the TDE master encryption key.

Create a master key:
SYS@proddb01 SQL> administer key management set key identified by "****" with backup using 'initial_key_backup';

Keystore altered.

Check the view v$encryption_wallet again:
SYS@proddb01 SQL>  select wrl_parameter, status,wallet_type from  v$encryption_Wallet;

WRL_PARAMETER                            STATUS                         WALLET_TYPE
---------------------------------------- ------------------------------ --------------------
/u01/oracle/admin/proddb01/wallet/        OPEN                           PASSWORD

Step 5: Encrypt Your Data

After you complete the software keystore configuration, you can begin to encrypt data.
You can encrypt data in individual table columns or in entire tablespaces, like I do below:

create bigfile tablespace SECRET_DATA_TBS datafile '/u02/oradata/proddb01/secret_data_01.dbf'  size 256M autoextend on next 128M maxsize unlimited
encryption using 'AES128'
default storage(encrypt);

Check status of the tablespaces:
SYS@proddb01 SQL> SELECT t.name, e.encryptionalg algorithm
FROM  v$tablespace t, v$encrypted_tablespaces e
WHERE t.ts# = e.ts#;
 
NAME                           ALGORIT
------------------------------ -------
SECRET_DATA_TBS                AES128

When the database is restarted, the wallet will be in status CLOSED.

To access your data, the wallet must be opened first:

ALTER SYSTEM SET ENCRYPTION WALLET OPEN IDENTIFIED BY "*****";

You may want to add a startup trigger that opens the wallet every time the database starts. See this post for how I solved this.

Sources:

1. configure the software keystore location in the sqlnet.ora file
2. log in to the database instance to create and open the keystore
3. Open the Software keystore
4. set the TDE master encryption key
5. begin to encrypt data


Wednesday, October 5, 2016

Find column semantics



When migrating from single-byte code to multi-byte code character sets, one way to move your data without data loss is to change the semantics used. In other words, VARCHAR2 and CHAR type columns can be redefined to use character semantics instead of the default byte semantics.

A schema-by-schema overview of the tables and column involved can be extracted by using the query below:

select owner "owner",
                CASE 
                    WHEN char_used = 'B' then 'BYTE'
                    WHEN char_used = 'C' then 'CHAR'
                END "semantics"
,count(*) "antall"
from dba_tab_columns
where owner in ('SCOTT','RYAN')
and data_Type in ('CHAR','VARCHAR2')
group by OWNER, ROLLUP(char_used);

owner semantics antall
SCOTT BYTE
62
SCOTT CHAR
610
SCOTT  
672
RYAN BYTE
486
RYAN  
486

In this example, the SCOTT schema contains columns of both semantics types.
The schema RYAN contains only the default, BYTE.

Monday, October 3, 2016

Use the dbms_flashback to set your session back in time

A convenient way to set your session back in time, is to execute the dbms_flashback.enable_at_time procedure.
This way you can work with your data as they were at a previous point-in-time, given that your UNDO settings support it.

Oracle states

"The DBMS_FLASHBACK package acts as a time machine: you can turn back the clock, perform normal queries as if you were at that earlier time, and then return to the present. Because you can use the DBMS_FLASHBACK package to perform queries on past data without special clauses such as AS OF or VERSIONS BETWEEN, you can reuse existing PL/SQL code to query the database at earlier times."



A simple test of the procedure follows.

First, the user would need execute privileges on the package in which enable_at_time belongs, the dbms_flashback package:
connect sys/passwd as sysdba
grant execute on dbms_flashback to scott;
SQL>connect scott/passwd 
SQL>select album_id,album_title,artist_id from album where artist_id=10;

  ALBUM_ID ALBUM_TITLE                                         ARTIST_ID
---------- -------------------------------------------------- ----------
        10 Joshua tree                                                10
        11 Achtung Baby                                               10
        12 Zooropa                                                    10

SQL>

Make a change:

SQL> insert into album (album_id, album_title,artist_id) values(200,'Whiplash Smile', 10);

1 row created.

SQL> commit;

Commit complete.

SQL> select album_id,album_title,artist_id from album where artist_id=10;

  ALBUM_ID ALBUM_TITLE                                         ARTIST_ID
---------- -------------------------------------------------- ----------
       200 Whiplash Smile                                             10
        10 Joshua tree                                                10
        11 Achtung Baby                                               10
        12 Zooropa                                                    10

To see the content of the table album as it was 5 minutes ago:
SQL> execute dbms_flashback.enable_at_time(sysdate-5/1440);

PL/SQL procedure successfully completed.

SQL> select album_id,album_title,artist_id from album where artist_id=10;

  ALBUM_ID ALBUM_TITLE                                         ARTIST_ID
---------- -------------------------------------------------- ----------
        10 Joshua tree                                                10
        11 Achtung Baby                                               10
        12 Zooropa                                                    10

Disable the flashback setting:

SQL> execute dbms_flashback.disable();

PL/SQL procedure successfully completed.

If you want to use flashback queries against other schemas' tables, you need object privileges to do so:
grant flashback on scott.album to jim;
Alternatively, you can grant jim the system privilege flashback any table:
connect / as sysdba
grant flashback any table to jim;
Sources: Oracle Documentation This document from Oracle Support

Friday, September 30, 2016

Missing trailing slash in listener.ora caused ORA-27101 when attempting to connect

Not too long ago, I got an error when connecting to my database using TOAD:
ORA-01034: ORACLE not available
ORA-27101: shared memory realm does not exist
IBM AIX RISC System/6000 Error: 2: No such file or directory

However, when connecting to the same database with sqlplus from the command line from a remote client, I could connect without errors.


Doc ID 1296982.1 "DVCA receives ORA-01034, ORA-27101" pointed me in the right direction:


"The value of ORACLE_HOME (or ORACLE_SID) passed to the DVCA utility does not match the value of ORACLE_HOME (or ORACLE_SID) that was in effect when the instance was started.

The shared memory segment key for an Oracle instance uses a hashed value based on the contents of ORACLE_HOME and ORACLE_SID. So, if one or the other of these values does not match what was used to start the instance, the resulting hash will not match, and one will encounter "ORA-27101: shared memory realm does not exist" when trying to connect to the instance.

This can commonly be caused by the presence (or lack thereof) of trailing slashes in the string for ORACLE_HOME, as passed to the -oh parameter of DVCA."


I changed my listener.ora file to read as follows:
SID_LIST_LISTENER =
  (SID_LIST =
    (SID_DESC =
      (GLOBAL_DBNAME = mydb)
      (ORACLE_HOME = /u01/oracle/product/11204 )
      (SID_NAME = mydb)
    )
  )
to
SID_LIST_LISTENER =
  (SID_LIST =
    (SID_DESC =
      (GLOBAL_DBNAME = mydb)
      (ORACLE_HOME = /u01/oracle/product/11204/) <-- Note the trailing slash character at the end of the ORACLE_HOME path
      (SID_NAME = mydb)
    )
  )
Reload the listener with lsnrctl reload, and the listener once again accepted connections from TOAD.

Tuesday, September 13, 2016

How to list partitions and their sizes


Using an inline view with a correlated subquery:
set lines 200
col partition_name format a40
SELECT P.PARTITION_NAME, (  SELECT ROUND(SUM(BYTES)/1024/1024/1024)
                            FROM DBA_SEGMENTS S
                            WHERE S.PARTITION_NAME = P.PARTITION_NAME
                            AND SEGMENT_NAME='&&TABLE_NAME') "size GB"
FROM DBA_TAB_PARTITIONS P
WHERE P.TABLE_NAME = '&&TABLE_NAME'
ORDER BY P.PARTITION_POSITION ASC;

Example output:
sqlplus / as sysdba @get_size.sql
Enter value for table_name: ARCHIVED_DOCUMENTS


PARTITION_NAME                      size GB
------------------------------   ----------
DOKARCHIVE1                           2.875
DOKARCHIVE2                               3
DOKARCHIVE3                               3
DOKARCHIVE4                               3
DOKARCHIVE5                               3
DOKARCHIVE6                          2.8125
DOKARCHIVE7                            2.75

Or, limit the output to specific partitions:
select s.partition_name,s.tablespace_name, p.compression,p.num_rows,Round(sum(s.bytes)/1024/1024/1024,1) "GB"
from dba_segments S join dba_tab_partitions p
on (s.partition_name = p.partition_name)
where s.segment_name='SALES_ARCHIVE'
and   s.owner='SH'
and s.partition_name in ( 'P_2015_01','P_2016_01','P_2017_01')
group by s.partition_name,s.tablespace_name,p.compression,p.num_rows
order by 1;

Example output:
PARTITION_NAME TABLESPACE_NAME COMPRESSION NUM_ROWS GB
P_2015_01 SALES_2015 ENABLED
22077874
2,3
P_2016_01 SALES_2016 ENABLED
394694450
42,3
P_2017_01 SALES_2017 ENABLED
481708328
48,9
If your partitions involve lob columns, make sure you fetch the sizes of the lob partitions, too.

Wednesday, August 31, 2016

How to list table sizes and their LOB segment sizes


Sometimes a quick sum of the used bytes in DBA_SEGMENTS is not enough to find the total size of a table.
If you have Large Object type columns defined in the table, the total table size can potentially be far greater than what you'd think.

Let's look at an example.

I have two tables in two differen schemas, with the same name. The tables are identical, and they both contain a LOB column:
SQL> desc livedocs.documents
 Name                                      Null?    Type
 ----------------------------------------- -------- ----------------------------
 ID                                        NOT NULL NUMBER(19)
 VERSION                                            NUMBER(19)
 DOC_ID                                             NOT NULL NUMBER(19)
 TYPE                                               NOT NULL VARCHAR2(255 CHAR)
 NUM_PAGES                                          NUMBER(15)
 CREATED                                            TIMESTAMP(6)
 LAST_CHANGED                                       TIMESTAMP(6)
 CONTENTS                                           NOT NULL BLOB

Check the tables' sizes:
SELECT OWNER,SEGMENT_NAME,SUM(BYTES)/1024/1024/1024 "table size (GB)" 
FROM DBA_SEGMENTS 
WHERE SEGMENT_NAME IN ('DOCUMENTS') 
AND OWNER IN ('LIVEDOCS','ARCHIVEDDOCS')
GROUP BY OWNER,SEGMENT_NAME;

Result:
OWNER SEGMENT_NAME table size (GB)
LIVEDOCS DOCUMENTS
2,5
ARCHIVEDDOCS DOCUMENTS
1,875

Let's find the name of the LOB segments and their corresponding indexes:
(The LOB indexes are internal data structures only. See this post.)
SELECT OWNER,TABLE_NAME,SEGMENT_NAME,COLUMN_NAME,INDEX_NAME 
FROM DBA_LOBS 
WHERE TABLE_NAME='DOCUMENTS' 
AND OWNER IN ('LIVEDOCS','ARCHIVEDDOCS');

OWNER TABLE_NAME SEGMENT_NAME COLUMN_NAME INDEX_NAME
ARCHIVEDDOCS DOCUMENTS ARCHIVED_DOCS_LOB CONTENT SYS_IL0000123181C00008$$
LIVEDOCS DOCUMENTS LIVE_DOCS_LOB CONTENT SYS_IL0000194213C00009$$

How much space have been allocated to these LOB segments?
SELECT OWNER,SEGMENT_NAME,ROUND(SUM(BYTES)/1024/1024/1024) "LOB size (GB)" 
FROM DBA_SEGMENTS
WHERE SEGMENT_NAME IN 
    (
    SELECT SEGMENT_NAME 
    FROM DBA_LOBS WHERE TABLE_NAME='DOCUMENTS'
    AND OWNER IN ('LIVEDOCS','ARCHIVEDDOCS')
    )
GROUP BY OWNER,SEGMENT_NAME 
; 
OWNER SEGMENT_NAME LOB size (GB)
LIVEDOCS LIVE_DOCS_LOB
1256
ARCHIVEDDOCS ARCHIVED_DOCS_LOB
3642

Putting it together, you can list both the table and its dependent LOB segments like this:
SELECT S.OWNER "Owner",NVL(S.SEGMENT_NAME, 'TABLE TOTAL SIZE') "Segment name",ROUND(SUM(S.BYTES)/1024/1024/1024,1) "Segment size (GB)"
FROM  DBA_SEGMENTS S 
WHERE S.SEGMENT_NAME IN ('DOCUMENTS')
AND   S.OWNER IN ('LIVEDOCS','ARCHIVEDOCS')
OR    S.SEGMENT_NAME IN (
                    (
                        SELECT L.SEGMENT_NAME FROM DBA_LOBS L 
                        WHERE L.TABLE_NAME = 'DOCUMENTS' 
                        AND L.OWNER = S.OWNER 
                        AND L.OWNER in ('LIVEDOCS','ARCHIVEDOCS')
                    )
)
GROUP BY S.OWNER,ROLLUP(S.SEGMENT_NAME)
ORDER BY 1,2,3; 

The resulting listing is:

Owner Segment name Segment size (GB)
ARCHIVEDDOCS ARCHIVED_DOCS_LOB
3642,4
ARCHIVEDDOCS DOCUMENTS
1,9
ARCHIVEDDOCS TABLE TOTAL SIZE
3644,3
LIVEDOCS DOCUMENTS
2,5
LIVEDOCS LIVE_DOCS_LOB
1255,9
LIVEDOCS TABLE TOTAL SIZE
1258,4



Wednesday, August 24, 2016

How to add minutes, seconds, hours or days to a datetime value

Simply add the desired interval to your timestamp value:

select current_timestamp,
       current_timestamp + interval '3' minute "current_timestamp+3min" 
from dual;


CURRENT_TIMESTAMP                                 current_timestamp+3min
----------------------------------------          ---------------------------------------------------------------------------
24.08.2016 19.44.32,808781 +2:00                  24.08.2016 19.47.32,808781000 +2:00

There are numerous possibilities to add very granular time intervals to an existing datetime value.

Check the examples of Interval literals in the official documentation.

Friday, August 19, 2016

Example on how to use WITH CHECK OPTION in an INSERT statement



The table "ALBUM" has the following columns:
Name         Null     Type                        
------------ -------- --------------------------- 
ALBUM_ID     NOT NULL NUMBER                      
ALBUM_TITLE  NOT NULL VARCHAR2(50 CHAR)           
ARTIST_ID             NUMBER                      
RELEASE_DATE          TIMESTAMP(6) WITH TIME ZONE 
GENRE                 VARCHAR2(30)                
NUM_SOLD              NUMBER                      
COLLECTION            CHAR(1)        

Extract all albums by the band Kiss:
SELECT ARTIST_ID, ALBUM_TITLE
 FROM ALBUM
 WHERE ARTIST_ID = (SELECT ARTIST_ID FROM ARTIST WHERE ARTIST_NAME='Kiss');

 ARTIST_ID ALBUM_TITLE                                      
---------- ------------
        13 Dynasty            

Add another entry to the "ALBUM" table:
SQL> INSERT INTO
  2  (
  3   SELECT album_id, album_title, artist_id,release_date, genre, num_sold
  4   FROM album
  5   WHERE num_sold BETWEEN 10000 AND 50000
  6   AND artist_id=13 WITH CHECK OPTION
  7   )
  8   VALUES( album_seq.nextval, '&album_title', &artist_id, to_date('&release_date','dd.mm.yyyy'), 'HARD ROCK', 20000 );
Enter value for album_title: Love Gun
Enter value for artist_id: 13
Enter value for release_date: 30.06.1977
old   8:  VALUES( album_seq.nextval, '&album_title', &artist_id, to_date('&release_date','dd.mm.yyyy'), 'HARD ROCK', 20000 )
new   8:  VALUES( album_seq.nextval, 'Love Gun', 13, to_date('30.06.1977','dd.mm.yyyy'), 'HARD ROCK', 20000 )

1 row inserted.

COMMIT;
Run the SELECT above once more to confirm the row has been entred:
ARTIST_ID ALBUM_TITLE                                      
---------- -----------------------
        13 Love Gun                             
        13 Dynasty            

Any attempt to enter values that is not included in the subquery would cause an
ORA-01402: view WITH CHECK OPTION where-clause violation
to be raised.
For example, try entering 15 as the artist_id:
Enter value for album_title: Creatures of the Night
Enter value for artist_id: 15
Enter value for release_date: 13.10.1982
old   8:  VALUES( album_seq.nextval, '&album_title', &artist_id, to_date('&release_date','dd.mm.yyyy'), 'HARD ROCK', 20000 )
new   8:  VALUES( album_seq.nextval, 'Creatures of the Night', 15, to_date('13.10.1982','dd.mm.yyyy'), 'HARD ROCK', 20000 )
 VALUES( album_seq.nextval, 'Creatures of the Night', 15, to_date('13.10.1982','dd.mm.yyyy'), 'HARD ROCK', 20000 )
         *
ERROR at line 8:
ORA-01402: view WITH CHECK OPTION where-clause violation

Note that the subquery, when used in an INSERT statement as above, replaces the table name, as well as the column list.

Source: Oracle documentation

Tuesday, August 9, 2016

How to check for active usage of an UNDO tablespace

SELECT a.name,n.status
FROM   v$rollname a inner join v$rollstat n
USING(usn)
WHERE a.name IN (
                  SELECT segment_name
                  FROM dba_segments
                  WHERE tablespace_name = 'UNDOTBS1'
)
;

NAME       STATUS
---------- ---------------
_SYSSMU8$  PENDING OFFLINE


Status can be either ONLINE, OFFLINE, PENDING OFFLINE or FULL.

* ONLINE means that the undo segments in the UNDO tablespace is being actively used
* OFFLINE means that the undo segments are not accessible for usage
* FULL means that the tablespace in which the undo segments reside has reached its limit, and you will most likely have seen the error

ORA-30036: unable to extend segment by  in undo tablespace 'UNDOTBS1'

at this point

* PENDING OFFLINE means there are still uncommitted transactions depending on the information in the undo segments in this particular UNDO tablespace.

Monday, August 8, 2016

How to find the default trace file for the session


Use the v$diag_info view, which "describes the state of Automatic Diagnostic Repository (ADR) functionality"

SELECT value
FROM   v$diag_info
WHERE  name = 'Default Trace File';

Example output:
VALUE
/u01/oracle/diag/rdbms/proddb01/proddb01/trace/proddb01_ora_45082.trc

A real easy way to start a trace of your current session is

exec dbms_monitor.session_trace_enable;
Then execute your statements

When you're done, stop tracing with

exec dbms_monitor.session_trace_disable;

Tuesday, August 2, 2016

How to rename or move a datafile in oracle.

The basic steps in relocating a datafile is as follows


1. Take the tablespace offline:
alter tablespace data offline normal;

2. Move the datafile physically, using an os command like cp:
cp data/oracle/proddb01/tmp/datafiles/data01.dbf /data/oracle/proddb01/datafile/data01.dbf

3. Inform the controlfile about your change:
alter database rename file '/data/oracle/proddb01/tmp/datafiles/data01.dbf' to '/data/oracle/proddb01/datafile/data01.dbf';

4. Make the tablespace available for usage again:
alter tablespace data online;

Thursday, July 14, 2016

INTERVAL conversion functions

There are 4 convertions functions that deal with INTERVAL datatypes, as listed below.
The two first converts strings, the later two converts numbers. All functions returns data of datatype INTERVAL.

TO_DSINTERVAL(sql_format)

* Converts a string into a value of the INTERVAL DAY TO SECOND datatype
* sql_format is a character string in the required format, which is 'DAYS HH24:MI:SS.FF'
* For example, the string '2 8:30:15.444' would equal 2 days, 8 hours, 30 minutes, 15.444 seconds.

Output: A value of the INTERVAL DAY TO SECOND datatype

Example:

SELECT TO_DSINTERVAL(INTERVAL '4' HOUR) "HOUR"
FROM DUAL;

HOUR
+00 04:00:00.000000



TO_YMINTERVAL('y-m')
* Converts y and m into the years and months in a format recognized by the INTERVAL YEAR TO MONTH datatype
* y and m are numbers within a string

Output: A value of the INTERVAL YEAR TO MONTH datatype

Example:
SELECT TO_YMINTERVAL(INTERVAL '36' MONTH) "MONTH"
FROM DUAL;

MONTH
+03-00


NUMTOYMINTERVAL(n, u)

* Converts a number into a value that represents the u unit amount of time
n = number
u = 'YEAR' or 'MONTH'
* Output: a value of the INTERVAL YEAR TO MONTH datatype

Example:

SELECT NUMTOYMINTERVAL(4, 'MONTH') "MONTH"
FROM DUAL;

MONTH
+00-04


NUMTODSINTERVAL(n, u)
Converts a number into a value that represents the u unit amount of time
n = number
u = 'DAY', 'HOUR', 'MINUTE', 'SECOND'
Output: a value of the INTERVAL DAY TO SECOND datatype

Example:

SELECT NUMTODSINTERVAL(2,'MINUTE') "MINUTE"
FROM DUAL;

MINUTE
+00 00:02:00.000000

Wednesday, July 13, 2016

SYSTIMESTAMP and SYSDATE VS. CURRENT_TIMESTAMP and CURRENT_DAY

Most DBAs I've spoken to are familiar with the function SYSDATE, but there are some other functions, too, that may prove to be useful, even for DBAs that aren't directly involved in developing applications.
For example, when querying the data dictionary, it can certainly be an advantage to be familiar with some other built-in date functions that comes with the Oracle database.

The two first functions reflect the datetime settings of the operating system on which the database runs:

* The SYSTIMESTAMP function returns a value of the TIMESTAMP WITH TIME ZONE datatype. It includes fractional seconds and time zone.
* the SYSDATE function returns a value of the DATE datatype.It includes timestamp, but not fractional seconds, nor time stamp.

The three functions below reflects the settings of your session:

* The CURRENT_DATE fuction returns a value of the DATE datatype within the session time zone
* The CURRENT_TIMESTAMP function returns a value of the TIMESTAMP WITH TIME ZONE data type within the session time zone
* The LOCALTIMESTAMP function returns a value of the TIMESTAMP data type, within the session time zone

Sitting in Oslo and querying a database with dbtimezone set to +2:00:
select dbtimezone from dual;
DBTIME
+02:00
I get the following default output when I execute the queries at 11:45:


select sysdate, systimestamp, current_date,current_timestamp(5), localtimestamp(3) from dual

SYSDATE SYSTIMESTAMP CURRENT_DATE CURRENTTIMESTAMP LOCALTIMESTAMP
13.07.2016 11:45:52 13.07.2016 11.45.52,597707 +02:00 13.07.2016 11:45:52 13.07.2016 11.45.52,59771 +02:00 13.07.2016 11.45.52,598

Now I will change my session time zone:
alter session set time_zone='America/New_York';

Session altered.


Execute the functions again, and the difference between the function becomes apparent:
select sysdate, systimestamp, current_date,current_timestamp(5), localtimestamp(3) from dual;

SYSDATE SYSTIMESTAMP CURRENT_DATE CURRENTTIMESTAMP LOCALTIMESTAMP
13.07.2016 11:49:1513.07.2016 11.49.15,381888 +02:0013.07.2016 05:49:1513.07.2016 05.49.15,38189 AMERICA/NEW_YORK13.07.2016 05.49.15,382



Current_date now returns the date and time as it would be in New York
Current_timestamp does the same, but adds the region name as the time zone indicator
Localtimestamp returns the timestamp as it would be in New York

The precision in the current_timestamp and localtimestamp (5 and 3, respectively) are set to override the default precision of 6 for fractional seconds.

Tuesday, July 12, 2016

Overview of role privileges

The following UNION can be handy to get an overview of what system privileges, object privileges and other roles a specific role may contain:
Select  Role || ' contains role:' "role privilege type", Granted_Role "privilege"   From Role_Role_Privs Where Role='ONLINE_USERS'
Union
Select Role || ' contains system priv:', Privilege      From Role_Sys_Privs  Where Role='ONLINE_USERS'
Union
Select Role || ' contains object priv:',  Privilege || ' ON ' || owner || '.' || table_name From Role_Tab_Privs Where Role='ONLINE_USERS'
order by 1;

Output:


role privilege type privilege
ONLINE_USERS contains object priv: UPDATE ON SCOTT.EMP
ONLINE_USERS contains role: RESOURCE
ONLINE_USERS contains system priv: UPDATE ANY TABLE

How does Oracle decide which roles should be enabled in a session?

Oracle uses the concept of default roles to decide whether or not a role should be enabled when a user establishes a session.

CREATE USER ERIC
IDENTIFIED BY pass1w0rd
DEFAULT TABLESPACE USERS
TEMPORARY TABLESPACE TEMP;

ALTER USER ERIC QUOTA UNLIMITED ON USERS;

GRANT CONNECT, RESOURCE TO ERIC; 

Oracle states in its documentation

"When you first create a user, the default user role setting is ALL, which causes all roles subsequently granted to the user to be default roles."

So we should have two default roles enabled for our user ERIC, confirmed below:
SQL> connect eric/pass1w0rd

Connected.

SELECT * FROM SESSION_ROLES;

ROLE
-------
CONNECT
RESOURCE


Let's create a new role:
CREATE ROLE online_users NOT IDENTIFIED;

Role created.
Because the user ERIC was created with ALL roles enabled by default, all subsequent sessions established by ERIC will now have access to the role online_users:
GRANT online_users to ERIC; 

Grant succeeded.

connect eric/Pass1w0rd

Connected.

SELECT * FROM SESSION_ROLES;

ROLE
-------
CONNECT
RESOURCE
ONLINE_USERS

If you want to prevent this behavior, you can alter the user with a limited default setting:
ALTER USER ERIC DEFAULT ROLE CONNECT;

User altered.

connect eric/Pass1w0rd

Connected.

SELECT * FROM SESSION_ROLES;

ROLE
-------
CONNECT

User ERIC must now explicitly enable the required roles from within his session:
set role connect, online_users;

Role set.

SELECT * FROM SESSION_ROLES;

ROLE
-------
CONNECT
ONLINE_USERS

These features can be incorporated into a security-oriented application setup, where specific people should only be granted specific privileges through database roles.

Wednesday, July 6, 2016

A workaround for ORA-02287: sequence number not allowed here when using GROUP BY in a subquery

When you use GROUP BY in the subquery of an INSERT statement, Oracle will throw

ORA-02287: sequence number not allowed here 

if you try to reference a sequence's NEXTVAL pseudocolumn directly in the insert statement, like this:
INSERT INTO mytable
        (
        entry_id,   
        order_date, 
        sum(quantity)
        )
        MYSEQ.NEXTVAL, <-- INCORRECT
        SELECT  
            TRUNC(SYSDATE),
            SUM(quantity)
        FROM    orders
        WHERE   
        GROUP BY order_date
        ORDER BY 2 ASC, 3 DESC;
Solution is to create a trigger that will fire on INSERT statements, before a new row is inserted. Note that I am overriding the default correlation name NEW with NewID, by using the REFERENCING clause of the "CREATE TRIGGER" command:
CREATE OR REPLACE TRIGGER MYTRG
BEFORE INSERT ON mytable REFERENCING NEW as NewID
    FOR EACH ROW
        BEGIN
            SELECT myseq.NEXTVAL
            INTO :NewID.ENTRY_ID
            FROM DUAL;
        END;
Change your insert statement slightly:
INSERT INTO mytable
        (
        order_date, 
        sum(quantity)
        )
        SELECT  
            TRUNC(SYSDATE),
            SUM(quantity)
        FROM    orders
        WHERE   
        GROUP BY order_date
        ORDER BY 2 ASC, 3 DESC;

Thursday, June 30, 2016

How to extract the base tables used by a view

To extract the tables being used in a view, you can query the USER_DEPENDENCIES view, like this:

SELECT NAME, TYPE, REFERENCED_NAME,REFERENCED_TYPE
FROM USER_DEPENDENCIES
Where Type = 'VIEW' 
And Name In ( 'V_FEMALE_ARTIST','VW_ALBUM');

Output:
NAME                           TYPE                 REFERENCED_NAME                REFERENCED_TYPE
----------------------------------------------------------------------------------------------------   
VW_ALBUM                       VIEW                 ALBUM                          TABLE              
VW_ALBUM                       VIEW                 SONGS                          TABLE              
V_FEMALE_ARTIST                VIEW                 ARTIST                         TABLE       

The column REFERENCED_NAME holds the information you are looking for.

I can see that my view VW_ALBUM is based on the tables ALBUM and SONG.
The last view uses no JOIN in its definition, and is based on one single table only.

Note what happens if you create a view based on the data dictionary, for example:
create or replace view mytables (tab_name, created, tabsize) AS
SELECT t.table_name,to_char(o.created, 'dd.mm.yyyy'),sum(s.bytes)
from user_tables t join user_objects o
on t.table_name = o.object_name join user_segments s on s.segment_name = t.table_name
where o.object_type = 'TABLE'
group by t.table_name, o.created
order by 2, 3 desc;

View created.

When you query the USER_DEPENDENCIES again, and consider the newly created view, note that the source of the view are not tables, but synonyms (and since I am using the view USER_SEGMENTS, I am consequently also depending on the DBMS_SPACE_ADMIN package):

SELECT NAME, TYPE, REFERENCED_NAME,REFERENCED_TYPE
FROM USER_DEPENDENCIES
Where Type = 'VIEW' 
And Name In ( 'V_FEMALE_ARTIST','VW_ALBUM','MYTABLES')
ORDER BY NAME;
NAME                           TYPE                 REFERENCED_NAME                REFERENCED_TYPE
----------------------------------------------------------------------------------------------------   
MYTABLES                       VIEW                 USER_OBJECTS                   SYNONYM            
MYTABLES                       VIEW                 USER_TABLES                    SYNONYM            
MYTABLES                       VIEW                 DBMS_SPACE_ADMIN               PACKAGE            
MYTABLES                       VIEW                 USER_SEGMENTS                  SYNONYM            
VW_ALBUM                       VIEW                 ALBUM                          TABLE              
VW_ALBUM                       VIEW                 SONGS                          TABLE              
V_FEMALE_ARTIST                VIEW                 ARTIST                         TABLE              

Monday, June 20, 2016

How to list all users and their number of owned objects


Join dba_users with dba_objects using a correlated subquery, and you'll get all the schemas, both those which own objects and those who don't:
set lines 200
col username format a20
col created format a30
col "Num_obj" format 999999
SELECT u.username,u.created,(SELECT COUNT(*) FROM dba_objects o WHERE o.owner = u.username)"Num_obj"
FROM dba_users u
-- to leave out all oracle maintained schemas, comment out the following
-- WHERE  u.oracle_maintained = 'N'
ORDER BY 3 asc;

Example output:
USERNAME             CREATED                        Num_obj
-------------------- ------------------------------ -------
ANONYMOUS            14.06.2016                           0
ADDM_USER            14.06.2016                           0
GSMUSER              14.06.2016                           0
SYSDG                14.06.2016                           0
DIP                  14.06.2016                           0
SYSBACKUP            14.06.2016                           0
SCOTT                14.06.2016                           0
USER1                20.06.2016                           0
USER2                20.06.2016                           0
SYSKM                14.06.2016                           0
XS$NULL              14.06.2016                           0
GSMCATUSER           14.06.2016                           0
APPQOSSYS            14.06.2016                           5
ORACLE_OCM           14.06.2016                           6
OUTLN                14.06.2016                          10
AUDSYS               14.06.2016                          12
OJVMSYS              14.06.2016                          16
USER3                20.06.2016                          34
DBSNMP               14.06.2016                          55
USER4                20.06.2016                          95
WMSYS                14.06.2016                         389
CTXSYS               14.06.2016                         409
SYSTEM               14.06.2016                         641
XDB                  14.06.2016                         961
SYS                  14.06.2016                       42173

If you have a large number of accounts that own no objects at all, you may want to exclude them. Do so by checking for the existence of the particular account in dba_objects.
If there are any rows at all in there, the account owns at least 1 object. Make sure to select a literal, not a value from the database. This is good practice; performance is not affected:
SELECT u.username,u.created,(SELECT COUNT(*) FROM dba_objects o WHERE o.owner = u.username )"Num_obj"
FROM dba_users u
WHERE EXISTS (SELECT 1 FROM dba_objects o WHERE o.owner = u.username)
ORDER BY 3 asc;