Quantcast
Channel: Database – ViralPatel.net
Viewing all 25 articles
Browse latest View live

Oracle 11G new feature: Virtual Column

$
0
0

Oracle 11g introduced the concept of ‘Virtual Column’ within a table. Virtual Columns are similar to normal table’s columns but with the following differences:

  • They are defined by an expression. The result of evaluation of this expression becomes the value of the column.
  • The values of the virtual column are not stored in the database. Rather, it’s computed at run-time when you query the data.
  • You can’t update (in SET clause of update statement) the values of virtual column. These are read only values, that are computed dynamically and any attempt to modify them will result into oracle error.

The syntax for defining a virtual column is:

column_name [datatype] [GENERATED ALWAYS] AS [expression] [VIRTUAL]

where the parameters within [] are optional and can be omitted. If you don’t mention the datatype, Oracle will decide it based on the result of the expression.

Excepting the above points, a virtual column, exists just like any other column of a normal table and the following points apply to it:

  1. Virtual columns can be used in the WHERE clause of UPDATE and DELETE statement but they can’t be modified by DML.
  2. Statistics can be collected on them.
  3. They can be used as a partition key in virtual column based partitioning.
  4. Indexes can be created on them. As you might have guessed, oracle would create function based indexes as we create on normal tables.
  5. Constraints can be created on them.

Create table with Virtual Column

For creating a virtual column, use the syntax mentioned above. Consider the following example:

CREATE TABLE EMPLOYEE
(
	empl_id        NUMBER,
	empl_nm        VARCHAR2(50),
	monthly_sal    NUMBER(10,2),
	bonus          NUMBER(10,2),
	total_sal      NUMBER(10,2) GENERATED ALWAYS AS (monthly_sal*12 + bonus)
);

Here we have defined a virtual column “total_sal” whose value would be dynamically calculated using the expression provided after the “generated always as” clause. Please note that this declaration is different than using “default” clause for a normal column as you can’t refer column names with “default” clause.

Lets check the data dictionary view:

SELECT column_name, data_type, data_length, data_default, virtual_column
  FROM user_tab_cols
 WHERE table_name = 'EMPLOYEE';

COLUMN_NAME | DATA_TYPE | DATA_LENGTH | DATA_DEFAULT             | VIRTUAL_COLUMN
EMPL_ID     | NUMBER    | 22          | null                     | NO            
EMPL_NM     | VARCHAR2  | 50          | null                     | NO            
MONTHLY_SAL | NUMBER    | 22          | null                     | NO            
BONUS       | NUMBER    | 22          | null                     | NO            
TOTAL_SAL   | NUMBER    | 22          | "MONTHLY_SAL"*12+"BONUS" | YES             

The value “YES” for the column “virtual_column” tells us that this is a virtual column. Another optional keyword “VIRTUAL” can also be added to make it syntactically complete.

DROP TABLE EMPLOYEE PURGE;

CREATE OR REPLACE FUNCTION get_empl_total_sal ( p_monthly_sal   NUMBER,
                                                p_bonus         NUMBER)
   RETURN NUMBER
DETERMINISTIC
IS
BEGIN
   RETURN p_monthly_sal * 12 + p_bonus;
END;

CREATE TABLE EMPLOYEE
(empl_id     NUMBER,
 empl_nm     VARCHAR2(50),
 monthly_sal NUMBER(10,2),
 bonus       NUMBER(10,2),
 total_sal   NUMBER(10,2) AS (get_empl_total_sal(monthly_sal, bonus)) VIRTUAL
);

We have included the “VIRTUAL” clause in the table definition. Please note that instead of using an expression, I have used a deterministic function. A deterministic function, when passed certain inputs, will always return the exact same output. “DETERMINISTIC” keyword is needed in order to mark a function as a candidate to be used in a function based index.

You can also create indexes on the virtual columns. Here is an example:

CREATE INDEX idx_total_sal ON employee(total_sal);

SELECT index_name, index_type 
  FROM user_indexes
 WHERE table_name = 'EMPLOYEE';

INDEX_NAME     INDEX_TYPE                 
IDX_TOTAL_SAL  FUNCTION-BASED NORMAL

Note that even this function is used as part of table definition, you can still drop it. But this in turn will make the table inaccessible.

DROP FUNCTION get_empl_total_sal;

SELECT * FROM employee;
*
Error at line 0
ORA-00904: "schema"."GET_EMPL_TOTAL_SAL": invalid identifier

You can alter the table with virtual column as you would modify a table with normal columns. Lets add the same column using the ALTER command:

DROP TABLE EMPLOYEE PURGE;

CREATE TABLE EMPLOYEE
(empl_id     NUMBER,
 empl_nm     VARCHAR2(50),
 monthly_sal NUMBER(10,2),
 bonus       NUMBER(10,2)
);

ALTER TABLE EMPLOYEE ADD (total_sal AS (monthly_sal * 12 + bonus));

Note that the datatype of the new column is not declared. It will be assigned a datatype based on the result of the expression (in this case, it would be NUMBER). Now let’s insert some data in the table:

INSERT INTO employee (empl_id, empl_nm, monthly_sal, bonus)
   WITH DATA AS
        (SELECT 100 empl_id, 'AAA' empl_nm, 20000 monthly_sal, 3000 bonus
           FROM DUAL
         UNION
         SELECT 200, 'BBB', 12000, 2000
           FROM DUAL
         UNION
         SELECT 300, 'CCC', 32100, 1000
           FROM DUAL
         UNION
         SELECT 400, 'DDD', 24300, 5000
           FROM DUAL
         UNION
         SELECT 500, 'EEE', 12300, 8000
           FROM DUAL)
   SELECT *
     FROM DATA;

SELECT * FROM employee;

EMPL_ID | EMPL_NM | MONTHLY_SAL | BONUS | TOTAL_SAL
100     | AAA     | 20000       | 3000  | 243000
200     | BBB     | 12000       | 2000  | 146000
300     | CCC     | 32100       | 1000  | 386200
400     | DDD     | 24300       | 5000  | 296600
500     | EEE     | 12300       | 8000  | 155600

Here we have populated the table columns except the virtual column with some values. Upon selecting the data, we get the value for “total_sal”. Remember that this data is not actually stored in the database but evaluated dynamically. Lets try updating this value of this virtual column:

UPDATE employee
   SET total_sal = 2000;

ORA-54017: UPDATE operation disallowed on virtual columns

As mentioned before, the statistics can also be gathered for the virtual columns.

EXEC DBMS_STATS.GATHER_TABLE_STATS(user, 'EMPLOYEE');

SELECT column_name, num_distinct, 
       display_raw (low_value, data_type)  low_value, 
       display_raw (high_value, data_type) high_value
  FROM dba_tab_cols
 WHERE table_name = 'EMPLOYEE';

COLUMN_NAME | NUM_DISTINCT | LOW_VALUE | HIGH_VALUE
TOTAL_SAL   | 5            | 146000    | 386200
BONUS       | 5            | 1000      | 8000
MONTHLY_SAL | 5            | 12000     | 32100
EMPL_NM     | 5            | AAA       | EEE
EMPL_ID     | 5            | 100       | 500

Limitations on Virtual Columns

**The query above uses a wonderful function “display_raw” by “Greg Rahn” to display the high/low values. Please check the references at the last to see it’s definition.

  1. A virtual column can only be of scalar datatype or XMLDATATYE. It can’t be a user defined type, LOB or RAW.
  2. All columns mentioned as part of the virtual column expression should belong to the same table.
  3. No DMLs are allowed on the virtual columns.
  4. The virtual column expression can’t reference any other virtual column.
  5. Virtual columns can only be created on ordinary tables. They can’t be created on index-organized, external, object, cluster or temporary tables.
  6. If a deterministic function is used as virtual column expression, that virtual column can’t be used as a partitioning key for virtual column-based partitioning.

Virtual Column-Based Partitioning

Prior releases of Oracle only allowed a table to be partitioned based on a physical column. Oracle 11g, with the addition of virtual columns, now allows a partition key based on an expression, using one or more existing columns of the table. A virtual column can now be used as a partitioning key. Lets partition our table based on the virtual column “total_sal”:

DROP TABLE EMPLOYEE PURGE;

CREATE TABLE employee
(empl_id     NUMBER,
 empl_nm     VARCHAR2(50),
 monthly_sal NUMBER(10,2),
 bonus       NUMBER(10,2),
 total_sal   NUMBER(10,2) AS (monthly_sal*12 + bonus)
)
PARTITION BY RANGE (total_sal)
    (PARTITION sal_200000 VALUES LESS THAN (200000),
     PARTITION sal_400000 VALUES LESS THAN (400000),
     PARTITION sal_600000 VALUES LESS THAN (600000),
     PARTITION sal_800000 VALUES LESS THAN (800000),
     PARTITION sal_default VALUES LESS THAN (MAXVALUE));

INSERT INTO employee (empl_id, empl_nm, monthly_sal, bonus)
   WITH DATA AS
        (SELECT 100 empl_id, 'AAA' empl_nm, 20000 monthly_sal, 3000 bonus
           FROM DUAL
         UNION
         SELECT 200, 'BBB', 12000, 2000
           FROM DUAL
         UNION
         SELECT 300, 'CCC', 32100, 1000
           FROM DUAL
         UNION
         SELECT 400, 'DDD', 24300, 5000
           FROM DUAL
         UNION
         SELECT 500, 'EEE', 12300, 8000
           FROM DUAL)
   SELECT *
     FROM DATA;

EXEC DBMS_STATS.GATHER_TABLE_STATS(user,'EMPLOYEE',granularity => 'PARTITION');

SELECT   table_name, partition_name, num_rows
    FROM user_tab_partitions
   WHERE table_name = 'EMPLOYEE'
ORDER BY partition_name;

TABLE_NAME | PARTITION_NAME | NUM_ROWS
EMPLOYEE   | SAL_200000     | 2
EMPLOYEE   | SAL_400000     | 3
EMPLOYEE   | SAL_600000     | 0
EMPLOYEE   | SAL_800000     | 0
EMPLOYEE   | SAL_DEFAULT    | 0

So far, everything looks fine, lets now try to update monthly salary of one employee and in turn the value of total_sal.

UPDATE employee
   SET monthly_sal = 30000
 WHERE empl_id = 500;

ORA-14402: updating partition key column would cause a partition change

What happened? The reason is simple, updating the “monthly_sal” would result into change in “total_sal” of the employee and thus a partition change is required. This can be handled by enabling the row movement in the current definition of the table.

ALTER TABLE employee ENABLE ROW MOVEMENT;

UPDATE employee
   SET monthly_sal = 80000
 WHERE empl_id = 500;

1 row updated.

The update works fine. As mentioned before, a deterministic function can’t be used as virtual column expression which is to be used as a partitioning key. It has to be an expression defined on the columns of the table as done in the previous example. The following syntax will result in oracle error:

CREATE TABLE employee_new
(empl_id     NUMBER,
 empl_nm     VARCHAR2(50),
 monthly_sal NUMBER(10,2),
 bonus       NUMBER(10,2),
 total_sal   NUMBER(10,2) AS (get_empl_total_sal(monthly_sal, bonus))
)
PARTITION BY RANGE (total_sal)
    (PARTITION sal_200000 VALUES LESS THAN (200000),
     PARTITION sal_400000 VALUES LESS THAN (400000),
     PARTITION sal_600000 VALUES LESS THAN (600000),
     PARTITION sal_800000 VALUES LESS THAN (800000),
     PARTITION sal_default VALUES LESS THAN (MAXVALUE));

ORA-54021: Cannot use PL/SQL expressions in partitioning or subpartitioning columns

References:

Greg Rahn’s display_raw function
Oracle Documentation

Related Posts


Hibernate Maven MySQL Hello World example (XML Mapping)

$
0
0

In this tutorial, we will try to write a small hello world program using Hibernate, MySQL and Maven. We will create a Java project using Maven and will then try to add Hibernate on it.

Following are the tools and technologies used in this project.

  1. Java JDK 5 or above
  2. Eclipse IDE 3.2 or above
  3. Maven 3.0 or above
  4. Hibernate 3.0 or above
  5. MySQL 5.0 or above

1. Database Creation

For this tutorial, we will create a simple table “employe” in MySQL. Use following script to create the table.

CREATE TABLE `employee` (
	`id` BIGINT(10) NOT NULL AUTO_INCREMENT,
	`firstname` VARCHAR(50) NULL DEFAULT NULL,
	`lastname` VARCHAR(50) NULL DEFAULT NULL,
	`birth_date` DATE NOT NULL,
	`cell_phone` VARCHAR(15) NOT NULL,
	PRIMARY KEY (`id`)
)
COLLATE='latin1_swedish_ci'
ENGINE=InnoDB
ROW_FORMAT=DEFAULT
AUTO_INCREMENT=606

Thus, following will be the connection string to connect with MySQL.

Connection URL:	jdbc:mysql://localhost:3306/tutorial
DB Username:	root
DB Password:	<your mysql password>

2. Generate Maven Java project compatible with Eclipse

Open a command prompt and execute following code to generate a Maven Java project.

mvn archetype:generate \
	-DgroupId=net.viralpatel.hibernate \
	-DartifactId=HibernateHelloWorldXML \
	-DarchetypeArtifactId=maven-archetype-quickstart \
	-DinteractiveMode=false

Once you execute above command, a project “HibernateHelloWorldXML” is created. Note that this is a Java project which already has folder structures compatible to Maven.

Once this is done, we will convert it to be compatible with Eclipse. For that executes following command on command prompt.

mvn eclipse:eclipse

3. Import project in Eclipse

Open Eclipse and import the above Java project.

File > Import… > General > Existing Projects into Workspace > Choose above Java project

Once imported, the folder structure will look like following:

hibernate-maven-blank-folder-structure

Important:

In case you get following error after importing project in Eclipse:
hibernate-maven-repo-error

If you are getting this Hibernate Maven Repo error, then you need to define M2_REPO environment variable in eclipse pointing to your local maven repository.

Goto Window > Preferences… > Java > Build Path > Classpath Variables > Define new variable M2_REPO pointing to your local maven repository.
eclipse-maven-repo-environment-variable

Once you will define this variable and rebuild the project, the above error will go.

4. Add Hibernate Dependency to Maven pom.xml

Add following dependencies to Maven pom.xml.

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>net.viralpatel.hibernate</groupId>
  <artifactId>HibernateHelloWorldXML</artifactId>
  <packaging>jar</packaging>
  <version>1.0-SNAPSHOT</version>
  <name>HibernateHelloWorldXML</name>
  <url>http://maven.apache.org</url>
  <dependencies>
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>5.1.10</version>
    </dependency>
    <dependency>
      <groupId>org.hibernate</groupId>
      <artifactId>hibernate</artifactId>
      <version>3.2.6.ga</version>
    </dependency>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>3.8.1</version>
      <scope>test</scope>
    </dependency>
  </dependencies>
</project>

Once we have added these dependencies, we will execute following command so that maven will download required Jar files and add the same to eclipse classpath.

mvn eclipse:eclipse

The above step is not mandatory. If you have Maven plugin for Eclipse installed in your IDE (Latest eclipse comes with this plugin built-in) you can just enable Maven dependencies by doing
Right click on project > Maven > Enable Dependency Management.

5. Add Hibernate Configuration XML

Now our project setup is ready. We will add following Hibernate configuration xml file in /src/main/resources folder. Note that this folder is not present in your project structure.

Create a source folder:
Right click on Project > New > Source Folder > Give folder name “/src/main/resources/” and click Finish.

Copy following file in your project.

File: /src/main/resources/hibernate.cfg.xml

<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-configuration PUBLIC
        "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
        "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
 
<hibernate-configuration>
    <session-factory>
        <!-- Database connection settings -->
        <property name="connection.driver_class">com.mysql.jdbc.Driver</property>
        <property name="connection.url">jdbc:mysql://localhost:3306/tutorial</property>
        <property name="connection.username">root</property>
        <property name="connection.password"></property>
        
        <!-- JDBC connection pool (use the built-in) -->
        <property name="connection.pool_size">1</property>
        
        <!-- SQL dialect -->
        <property name="dialect">org.hibernate.dialect.MySQLDialect</property>

        <!-- Enable Hibernate's automatic session context management -->
        <property name="current_session_context_class">thread</property>

        <!-- Disable the second-level cache  -->
        <property name="cache.provider_class">org.hibernate.cache.NoCacheProvider</property>
        
        <!-- Echo all executed SQL to stdout -->
        <property name="show_sql">false</property>
        
        <property name="hbm2ddl.auto">validate</property>
 
        <mapping resource="net/viralpatel/hibernate/Employee.hbm.xml"/>
 		
 		 
    </session-factory>
</hibernate-configuration>

One thing we need to make sure in Eclipse is that the Java build path must reflect **/*.xml.
Right click on project > Properties > Java Build Path > Source > Add **/*.xml as follow:
hibernate-java-build-path-include-xml

6. Add Java source code

File: /src/main/java/net/viralpatel/hibernate/Employee.java

package net.viralpatel.hibernate;

import java.sql.Date;

public class Employee {

	private Long id;
	
	private String firstname;
	
	private String lastname;
	
	private Date birthDate;
	
	private String cellphone;

	public Employee() {
		
	}
	
	public Employee(String firstname, String lastname, Date birthdate, String phone) {
		this.firstname = firstname;
		this.lastname = lastname;
		this.birthDate = birthdate;
		this.cellphone = phone;
		
	}
	
	// Getter and Setter methods

}

Above is the Employee POJO class that we will use to fetch data from Employee table. This Java class will be mapped by Hiberate with Employee table. Note that we have define two constructors first default and second with arguements. The second constructor is optional. But note that you must define a default (No-arguement) constructor in your mapping class.

File: /src/main/java/net/viralpatel/hibernate/HibernateUtil.java

package net.viralpatel.hibernate;
 
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
 
public class HibernateUtil {
 
    private static final SessionFactory sessionFactory = buildSessionFactory();
 
    private static SessionFactory buildSessionFactory() {
        try {
            // Create the SessionFactory from hibernate.cfg.xml
            return new Configuration()
            		.configure()
                    .buildSessionFactory();
        } catch (Throwable ex) {
            System.err.println("Initial SessionFactory creation failed." + ex);
            throw new ExceptionInInitializerError(ex);
        }
    }
 
    public static SessionFactory getSessionFactory() {
        return sessionFactory;
    }
}

As discussed in previous article Introduction to Hibernate framework, we have to create SessionFactory instance in order to communicate with Database in Hibernate. We will use a Utility class called HibernateUtil to instantiate SessionFactory.

File: /src/main/java/net/viralpatel/hibernate/Employee.hbm.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC
        "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
        "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">

<hibernate-mapping package="net.viralpatel.hibernate">

    <class name="Employee" table="EMPLOYEE">
 		<id name="id" column="ID">
            <generator class="native"/>
        </id>
        <property name="firstname" />
        <property name="lastname" column="lastname"/>
        <property name="birthDate" type="date" column="birth_date"/>
        <property name="cellphone" column="cell_phone"/>
	</class>

</hibernate-mapping>

7. CRUD operations in Hibernate

Let us see the basic CRUD operation in Hibernate. We will use Employee table for this.

7.1 Writing (Create) data in Hibernate

Following is the code snippet that insert a row in database.

private static Employee save(Employee employee) {
	SessionFactory sf = HibernateUtil.getSessionFactory();
	Session session = sf.openSession();
	session.beginTransaction();

	Long id = (Long) session.save(employee);
	employee.setId(id);
		
	session.getTransaction().commit();
		
	session.close();

	return employee;
}

...
...
...

// Call the save() method to insert a record in database.
System.out.println("******* WRITE *******");
Employee empl = new Employee("Jack", "Bauer", new Date(System.currentTimeMillis()), "911");
empl = save(empl);

7.2 Reading (Read) data in Hibernate

Below code snippet demos read functionality in Hibernate. Note that there are two methods one to list all the employees and other to read details about just one.

private static List list() {
	SessionFactory sf = HibernateUtil.getSessionFactory();
	Session session = sf.openSession();

	List employees = session.createQuery("from Employee").list();
	session.close();
	return employees;
}
private static Employee read(Long id) {
	SessionFactory sf = HibernateUtil.getSessionFactory();
	Session session = sf.openSession();

	Employee employee = (Employee) session.get(Employee.class, id);
	session.close();
	return employee;
}

7.3 Updating (Update) data in Hibernate

Following is the code snippet to update a record in Hibernate.

private static Employee update(Employee employee) {
	SessionFactory sf = HibernateUtil.getSessionFactory();
	Session session = sf.openSession();

	session.beginTransaction();

	session.merge(employee);

	session.getTransaction().commit();

	session.close();
	return employee;

}

7.4 Remove (Delete) data in Hibernate

Following is code snippet to remove a record in Hibernate.

private static void delete(Employee employee) {
	SessionFactory sf = HibernateUtil.getSessionFactory();
	Session session = sf.openSession();

	session.beginTransaction();

	session.delete(employee);

	session.getTransaction().commit();

	session.close();
}

We have created a file Main.java which will test all these functionality. Following is the complete source code of Main.java.
File: /src/main/java/net/viralpatel/hibernate/Main.java

package net.viralpatel.hibernate;

import java.sql.Date;
import java.util.List;

import org.hibernate.Session;
import org.hibernate.SessionFactory;


public class Main {

	public static void main(String[] args) {
		
		
		// Read
		System.out.println("******* READ *******");
		List employees = list();
		System.out.println("Total Employees: " + employees.size());
		
		
		// Write
		System.out.println("******* WRITE *******");
		Employee empl = new Employee("Jack", "Bauer", new Date(System.currentTimeMillis()), "911");
		empl = save(empl);
		empl = read(empl.getId());
		System.out.printf("%d %s %s \n", empl.getId(), empl.getFirstname(), empl.getLastname());
		
		
		
		// Update
		System.out.println("******* UPDATE *******");
		Employee empl2 = read(1l); // read employee with id 1
		System.out.println("Name Before Update:" + empl2.getFirstname());
		empl2.setFirstname("James");
		update(empl2);	// save the updated employee details
		
		empl2 = read(1l); // read again employee with id 1
		System.out.println("Name Aftere Update:" + empl2.getFirstname());
		
		
		// Delete
		System.out.println("******* DELETE *******");
		delete(empl); 
		Employee empl3 = read(empl.getId());
		System.out.println("Object:" + empl3);
	}
	
	

	private static List list() {
		SessionFactory sf = HibernateUtil.getSessionFactory();
		Session session = sf.openSession();

		List employees = session.createQuery("from Employee").list();
		session.close();
		return employees;
	}
	private static Employee read(Long id) {
		SessionFactory sf = HibernateUtil.getSessionFactory();
		Session session = sf.openSession();

		Employee employee = (Employee) session.get(Employee.class, id);
		session.close();
		return employee;
	}
	private static Employee save(Employee employee) {
		SessionFactory sf = HibernateUtil.getSessionFactory();
		Session session = sf.openSession();

		session.beginTransaction();

		Long id = (Long) session.save(employee);
		employee.setId(id);
		
		session.getTransaction().commit();
		
		session.close();

		return employee;
	}

	private static Employee update(Employee employee) {
		SessionFactory sf = HibernateUtil.getSessionFactory();
		Session session = sf.openSession();

		session.beginTransaction();

		session.merge(employee);
		
		session.getTransaction().commit();
		
		session.close();
		return employee;

	}

	private static void delete(Employee employee) {
		SessionFactory sf = HibernateUtil.getSessionFactory();
		Session session = sf.openSession();
		
		session.beginTransaction();
		
		session.delete(employee);
		
		session.getTransaction().commit();
		
		session.close();
	}
	
}

8. Final project structure

Once you have created all these source files, your project structure should look like following. Note that we have removed the default App.java and AppTest.java generated by Maven as we dont these files.
hibernate-maven-final-project-structure

9. Execute project

Execute the Main.java class and see output.

******* READ *******
Total Employees: 200
******* WRITE *******
201 Jack Bauer 
******* UPDATE *******
Name Before Update:Paula
Name Aftere Update:James
******* DELETE *******
Object:null

That’s All Folks

Today we saw how to write our first Hibernate hello world example using XML Configuration. We used Maven to generate Java project and added Hibernate dependencies into it. Also we saw how to do different CRUD operations in Hibernate.

Download Source

HibernateHelloWorldXML.zip (8 kb)

Related Posts

Hibernate One To One Mapping Tutorial (XML Mapping)

$
0
0

Let us understand how One-to-one mapping works in Hibernate. Following is a simply yet concept building example where we will understand One-to-one mapping in Hibernate framework using XML Mappings. We will use two tables “employee” and “employeedetail” which exhibits one-to-one relationship. Using Hibernate we will implement this relationship.

Tools and technologies used in this article:

  1. Java JDK 1.5 above
  2. MySQL 5 above
  3. Eclipse 3.2 above
  4. Hibernate 3 above
  5. Maven 3 above

1. Create Database

We will use MySQL in our example. Create two tables “employee” and “employeedetail” with One-to-one relationship. Following is the SQL script for same.

/* EMPLOYEE table */
CREATE TABLE `employee` (
	`employee_id` BIGINT(10) NOT NULL AUTO_INCREMENT,
	`firstname` VARCHAR(50) NULL DEFAULT NULL,
	`lastname` VARCHAR(50) NULL DEFAULT NULL,
	`birth_date` DATE NOT NULL,
	`cell_phone` VARCHAR(15) NOT NULL,
	PRIMARY KEY (`employee_id`)
)
COLLATE='latin1_swedish_ci'
ENGINE=InnoDB
ROW_FORMAT=DEFAULT
AUTO_INCREMENT=216



/* EMPLOYEEDETAIL table */
CREATE TABLE `employeedetail` (
	`employee_id` BIGINT(20) NOT NULL AUTO_INCREMENT,
	`street` VARCHAR(50) NULL DEFAULT NULL,
	`city` VARCHAR(50) NULL DEFAULT NULL,
	`state` VARCHAR(50) NULL DEFAULT NULL,
	`country` VARCHAR(50) NULL DEFAULT NULL,
	PRIMARY KEY (`employee_id`),
	CONSTRAINT `FKEMPL` FOREIGN KEY (`employee_id`) REFERENCES `employee` (`employee_id`)
)
COLLATE='latin1_swedish_ci'
ENGINE=InnoDB
ROW_FORMAT=DEFAULT
AUTO_INCREMENT=216

Note that a one-to-one relationships occurs when one entity is related to exactly one occurrence in another entity. Thus, there will be one primary key which will be mapped with both the entities.

2. Hibernate Maven dependency

Following dependencies we will use in Maven which will add Hibernate support.

File: /pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>net.viralpatel.hibernate</groupId>
	<artifactId>HibernateHelloWorldXML</artifactId>
	<packaging>jar</packaging>
	<version>1.0-SNAPSHOT</version>
	<name>HibernateHelloWorldXML</name>
	<url>http://maven.apache.org</url>
	<dependencies>
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<version>5.1.10</version>
		</dependency>
		<dependency>
			<groupId>org.hibernate</groupId>
			<artifactId>hibernate</artifactId>
			<version>3.2.6.ga</version>
		</dependency>
	</dependencies>
</project>


3. Hibernate Model class

For this example we will create two Model class: Employee.java and EmployeeDetail.java. Add following in your project.

File: /src/main/java/net/viralpatel/hibernate/Employee.java

package net.viralpatel.hibernate;

import java.sql.Date;

public class Employee {

	private Long employeeId;
	private String firstname;
	private String lastname;
	private Date birthDate;
	private String cellphone;
	private EmployeeDetail employeeDetail;

	public Employee() {

	}

	public Employee(String firstname, String lastname, Date birthdate,
			String phone) {
		this.firstname = firstname;
		this.lastname = lastname;
		this.birthDate = birthdate;
		this.cellphone = phone;
	}

	// Getter and Setter methods	
}



File: /src/main/java/net/viralpatel/hibernate/EmployeeDetail.java

package net.viralpatel.hibernate;

public class EmployeeDetail {

	private Long employeeId;
	private String street;
	private String city;
	private String state;
	private String country;

	private Employee employee;

	public EmployeeDetail() {

	}

	public EmployeeDetail(String street, String city, String state, String country) {
		this.street = street;
		this.city = city;
		this.state = state;
		this.country = country;
	}
	
	// Getter and Setter methods	

}

Note that in above model classes, employeeId is common. This is the primary key of Employee table that exhibits One-to-one relationship with EmployeeDetail table.

4. Hibernate XML Mapping (HBM)

Add following hibernate mapping (hbm) files Employee.hbm.xml and EmployeeDetail.hbm.xml for the model classes created in above steps.

File: /src/main/java/net/viralpatel/hibernate/Employee.hbm.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC
        "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
        "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">

<hibernate-mapping package="net.viralpatel.hibernate">

	<class name="Employee" table="EMPLOYEE">
		<id name="employeeId" column="EMPLOYEE_ID">
			<generator class="native" />
		</id>
		<one-to-one name="employeeDetail" class="net.viralpatel.hibernate.EmployeeDetail"
			cascade="save-update"></one-to-one>
		
		<property name="firstname" />
		<property name="lastname" column="lastname" />
		<property name="birthDate" type="date" column="birth_date" />
		<property name="cellphone" column="cell_phone" />

	</class>
</hibernate-mapping>

File: /src/main/java/net/viralpatel/hibernate/EmployeeDetail.hbm.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC
        "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
        "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">

<hibernate-mapping package="net.viralpatel.hibernate">

    <class name="EmployeeDetail" table="EMPLOYEEDETAIL">

 		<id name="employeeId" type="java.lang.Long">
			<column name="EMPLOYEE_ID" />
			<generator class="foreign">
				<param name="property">employee</param>
			</generator>
		</id>
		<one-to-one name="employee" class="net.viralpatel.hibernate.Employee"
			constrained="true"></one-to-one>
		
        <property name="street" column="STREET"/>
        <property name="city" column="CITY"/>
        <property name="state" column="STATE"/>
        <property name="country" column="COUNTRY"/>
	</class>

</hibernate-mapping>

Note that in above hibernate mapping we are implementing One-to-one relationship. For both the model classes we are using a single primary key EmployeeId. In EmployeeDetail hbm file we have defined a foreign identifier generator so that primary it uses primary key of Employee table.

5. Hibernate Configuration

Following is the hibernate configuration hibernate.cfg.xml file. Add this in your project.

File: /src/main/resources/hibernate.cfg.xml

<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-configuration PUBLIC
        "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
        "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
 
<hibernate-configuration>
    <session-factory>
        <property name="connection.driver_class">com.mysql.jdbc.Driver</property>
        <property name="connection.url">jdbc:mysql://localhost:3306/tutorial</property>
        <property name="connection.username">root</property>
        <property name="connection.password"></property>
        
        <property name="connection.pool_size">1</property>
        <property name="dialect">org.hibernate.dialect.MySQLDialect</property>
        <property name="current_session_context_class">thread</property>
        <property name="cache.provider_class">org.hibernate.cache.NoCacheProvider</property>
        <property name="show_sql">true</property>
        <property name="hbm2ddl.auto">validate</property>
 
        <mapping resource="net/viralpatel/hibernate/EmployeeDetail.hbm.xml"/>
        <mapping resource="net/viralpatel/hibernate/Employee.hbm.xml"/>
 		 
    </session-factory>
</hibernate-configuration>


6. Hibernate Utility class

File:/src/main/java/net/viralpatel/hibernate/HibernateUtil.java

package net.viralpatel.hibernate;
 
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
 
public class HibernateUtil {
 
    private static final SessionFactory sessionFactory = buildSessionFactory();
 
    private static SessionFactory buildSessionFactory() {
        try {
            // Create the SessionFactory from hibernate.cfg.xml
            return new Configuration()
            		.configure()
                    .buildSessionFactory();
        } catch (Throwable ex) {
            System.err.println("Initial SessionFactory creation failed." + ex);
            throw new ExceptionInInitializerError(ex);
        }
    }
 
    public static SessionFactory getSessionFactory() {
        return sessionFactory;
    }
}

7. Main class to test One-to-one mapping

Add following Main.java class in your project to test One-to-one relationship mapping functionality.

File:/src/main/java/net/viralpatel/hibernate/Main.java

package net.viralpatel.hibernate;

import java.sql.Date;
import java.util.List;

import org.hibernate.Session;
import org.hibernate.SessionFactory;

public class Main {

	public static void main(String[] args) {

		SessionFactory sf = HibernateUtil.getSessionFactory();
		Session session = sf.openSession();
		session.beginTransaction();

		EmployeeDetail employeeDetail = new EmployeeDetail("10th Street", "LA", "San Francisco", "U.S.");
		
		Employee employee = new Employee("Nina", "Mayers", new Date(121212),
				"114-857-965");
		employee.setEmployeeDetail(employeeDetail);
		employeeDetail.setEmployee(employee);
		
		
		session.save(employee);

		
		List<Employee> employees = session.createQuery("from Employee").list();
		for (Employee employee1 : employees) {
			System.out.println(employee1.getFirstname() + " , "
					+ employee1.getLastname() + ", "
					+ employee1.getEmployeeDetail().getState());
		}

		session.getTransaction().commit();
		session.close();

	}
}


8. Review Project Structure

Below is the final project structure with all the source files.

hibernate-one-to-one-example-project-structure


8. Execute the example

Execute the Main class. Hibernate will insert a row in Employee and EmployeeDetail table.

Output:

Hibernate: insert into EMPLOYEE (firstname, lastname, birth_date, cell_phone) values (?, ?, ?, ?)
Hibernate: insert into EMPLOYEEDETAIL (STREET, CITY, STATE, COUNTRY, EMPLOYEE_ID) values (?, ?, ?, ?, ?)
Hibernate: select employee0_.EMPLOYEE_ID as EMPLOYEE1_1_, employee0_.firstname as firstname1_, employee0_.lastname as lastname1_, employee0_.birth_date as birth4_1_, employee0_.cell_phone as cell5_1_ from EMPLOYEE employee0_
Hibernate: select employeede0_.EMPLOYEE_ID as EMPLOYEE1_0_0_, employeede0_.STREET as STREET0_0_, employeede0_.CITY as CITY0_0_, employeede0_.STATE as STATE0_0_, employeede0_.COUNTRY as COUNTRY0_0_ from EMPLOYEEDETAIL employeede0_ where employeede0_.EMPLOYEE_ID=?
Nina , Mayers, San Francisco

That’s All Folks

Today we saw how to write Hibernate program and implements One-to-one relationship mapping using XML mappings. We used Maven to generate Java project and added Hibernate dependencies into it.

Download Source

Hibernate-One-to-one-tutorial-XML-Mapping.zip (9 kb)

Related Posts

How To Reset MySQL Autoincrement Column

$
0
0

mysql-logoMySQL database provides a wonderful feature of Autoincrement Column index. Your database table can define its primary key as Autoincrement number and MySQL will take care of its unique value while inserting new rows.

Each time you add a new row, MySQL increments the value automatically and persist it to table. But sometime you may want to reset the Autoincrement column value to 1. Say you writing a sample application and you have inserted few rows already in the table. Now you want to delete these rows and reset the autoincrement column to 1 so that new row which you insert will have primary key value 1.

There are few methods to achieve this.

1. Directly Reset Autoincrement Value

Alter table syntax provides a way to reset autoincrement column. Take a look at following example.

ALTER TABLE table_name AUTO_INCREMENT = 1;

Note that you cannot reset the counter to a value less than or equal to any that have already been used. For MyISAM, if the value is less than or equal to the maximum value currently in the AUTO_INCREMENT column, the value is reset to the current maximum plus one. For InnoDB, if the value is less than the current maximum value in the column, no error occurs and the current sequence value is not changed.

2. Truncate Table

Truncate table automatically reset the Autoincrement values to 0.

TRUNCATE TABLE table_name;

Use this with caution. When Truncation is used, it resets any AUTO_INCREMENT counter to zero. From MySQL 5.0.13 on, the AUTO_INCREMENT counter is reset to zero by TRUNCATE TABLE, regardless of whether there is a foreign key constraint.

Once TRUNCATE is fired, the table handler does not remember the last used AUTO_INCREMENT value, but starts counting from the beginning. This is true even for MyISAM and InnoDB, which normally do not reuse sequence values.

3. Drop & Recreate Table

This is another way of reseting autoincrement index. Although not very desirable.

DROP TABLE table_name;
CREATE TABLE table_name { ... };

All these techniques are value techniques to reset autoincrement column number. Use whatever suits your requirement.

Disclaimer: The above commands can delete all your data! Be very very cautious.

Related Posts

How To Convert Number into Words using Oracle SQL Query

$
0
0

number How can you convert a number into words using Oracle Sql Query? What I mean by Number to Word is:

12 = Twelve
102 = One Hundred Two
1020 = One Thousand Twenty

Here’s a classy query which will convert number into words.Please see the query below:

select to_char(to_date(:number,'j'),'jsp') from dual;

If I pass 234 in number, then the output will : two hundred thirty-four

SELECT TO_CHAR (TO_DATE (234, 'j'), 'jsp') FROM DUAL;
//Output: two hundred thirty-four

SELECT TO_CHAR (TO_DATE (24834, 'j'), 'jsp') FROM DUAL;
//Output: twenty-four thousand eight hundred thirty-four

SELECT TO_CHAR (TO_DATE (2447834, 'j'), 'jsp') FROM DUAL;
//Output: two million four hundred forty-seven thousand eight hundred thirty-four

So how the query works? Well here’s why:

If you look into the inner most part of the query to_date(:number,'j') the ‘j’ or J is the Julian Date (January 1, 4713 BC), basically this date is been used for astronomical studies.

So to_date(:number,'j') it take the number represented by number and pretend it is a julian date, convert into a date.

If you pass 3 to number, so it will convert date to 3rd Jan 4713 BC, it means 3 is added to the Julian date.

Now to_char(to_date(:number,'j'),'jsp'), jsp = Now; take that date(to_date(:number,'j')) and spell the julian number it represents

Limitation & workaround

There is a limitation while using Julian dates ,It ranges from 1 to 5373484. That’s why if you put the values after 5373484, it will throw you an error as shown below:

ORA-01854: julian date must be between 1 and 5373484

To cater the above problem ,create a function ,and with little trick with j->jsp ,you can fetch the desired result.

CREATE OR REPLACE FUNCTION spell_number (p_number IN NUMBER)
   RETURN VARCHAR2
AS
   TYPE myArray IS TABLE OF VARCHAR2 (255);

   l_str myArray
         := myArray ('',
                     ' thousand ',
                     ' million ',
                     ' billion ',
                     ' trillion ',
                     ' quadrillion ',
                     ' quintillion ',
                     ' sextillion ',
                     ' septillion ',
                     ' octillion ',
                     ' nonillion ',
                     ' decillion ',
                     ' undecillion ',
                     ' duodecillion ');

   l_num      VARCHAR2 (50) DEFAULT TRUNC (p_number);
   l_return   VARCHAR2 (4000);
BEGIN
   FOR i IN 1 .. l_str.COUNT
   LOOP
      EXIT WHEN l_num IS NULL;

      IF (SUBSTR (l_num, LENGTH (l_num) - 2, 3) <> 0)
      THEN
         l_return :=
            TO_CHAR (TO_DATE (SUBSTR (l_num, LENGTH (l_num) - 2, 3), 'J'),
                     'Jsp')
            || l_str (i)
            || l_return;
      END IF;

      l_num := SUBSTR (l_num, 1, LENGTH (l_num) - 3);
   END LOOP;

   RETURN l_return;
END;
/


SELECT spell_number (53734555555585) FROM DUAL;

Output:

Fifty-Three trillion Seven Hundred Thirty-Four billion Five Hundred Fifty-Five million Five Hundred Fifty-Five thousand Five Hundred Eighty-Five

Hope this helps :)

Related Posts

Index usage with LIKE operator in Oracle & Domain Indexes

$
0
0

A lot of developers might be confused about index selectivity while using %LIKE% operator. So please find below how index worked when you use LIKE operator.

Problem Statement

  • While optimizing high CPU consuming queries on 3rd party application, verified that most of the queries are using '%LIKE%' operator.
  • Interestingly enough, while some of these queries are going for "INDEX RANGE" while others are going for "FULL TABLE SCAN" ??

Brief about LIKE operator

  • 'LIKE' Determines whether a specific character string matches a specified pattern.
  • % allows you to match any string of any length (including zero length)

Before starting this you must know that :

  • Only the part before the first wildcard serves as access predicate.
  • The remaining characters do not narrow the scanned index range they just discard non-matching results.

We can use LIKE operator in 4 ways in our queries:

  1. SEARCH-STRING%
  2. %SEARCH-STRING
  3. %SEARCH-STRING%
  4. SEARCH%STRING

1. SEARCH-STRING%

The SEARCH-STRING% will perform INDEX RANGE SCAN data in least possible time.

set autotrace traceonly;
select * from sac where object_type like 'TAB%';

Execution Plan
----------------------------------------------------------
   0       SELECT STATEMENT Optimizer Mode=ALL_ROWS (Cost=85 Card=3 K Bytes=543 K)
   1    0    TABLE ACCESS BY INDEX ROWID EMPL101.SAC (Cost=85 Card=3 K Bytes=543 K)
   2    1      INDEX RANGE SCAN EMPL101.SAC_INDX (Cost=11 Card=3 K)

Here the optimizer knows ,where the string gets started (means it know the predicate),so It used Index Range Scan .

2. %SEARCH-STRING

When using %SEARCH-STRING it’s access the FULL table.

set autotrace traceonly;
select * from sac where object_type like '%TAB';

Execution Plan
----------------------------------------------------------
   0       SELECT STATEMENT Optimizer Mode=ALL_ROWS (Cost=398 Card=16 Bytes=2 K)
   1    0    TABLE ACCESS FULL EMPL101.SAC (Cost=398 Card=16 Bytes=2 K)

The opposite case is also possible: a LIKE expression that starts with a wildcard. Such a LIKE expression cannot serve as access predicate.

The database has to scan the entire table, if the where clause does not provide another access path.

3. %SEARCH-STRING%

When using %SEARCH-STRING% it’s access the FULL table.

set autotrace traceonly;
select * from sac where object_type like '%TAB%';

Execution Plan
----------------------------------------------------------
   0       SELECT STATEMENT Optimizer Mode=ALL_ROWS (Cost=398 Card=3 K Bytes=543 K)
   1    0    TABLE ACCESS FULL EMPL101.SAC (Cost=398 Card=3 K Bytes=543 K)

Here also the optimizer doesn’t know from which letter the String get started ,so it will scan the whole table.

4. SEARCH%STRING

The SEARCH%STRING will perform INDEX RANGE SCAN and generate an initial result set, containing the values that match first string i.e. SEARCH%. Next it will scan through the values to get second string i.e. %STRING

set autotrace traceonly;
select * from sac where object_type like 'TA%BLE';

Execution Plan
----------------------------------------------------------
   0       SELECT STATEMENT Optimizer Mode=ALL_ROWS (Cost=85 Card=3 K Bytes=543 K)
   1    0    TABLE ACCESS BY INDEX ROWID EMPL101.SAC (Cost=85 Card=3 K Bytes=543 K)
   2    1      INDEX RANGE SCAN EMPL101.SAC_INDX (Cost=11 Card=3 K)

Lets see the chart below for various search using wildcard character:

The more selective the part before the first wildcard is, the smaller the scanned index range becomes.
That, in turn, makes the index lookup more efficient.

oracle-like-query-result

  • The first expression has two characters before the wildcard.
  • They limits the scanned index range to 18 rows. Only one of them matches the entire LIKE expression—the other 17 are discarded.
  • The second expression has a longer prefix, which narrows the scanned index range to two rows.
  • With this expression, the database just reads one extra row that is not relevant for the result.
  • The last expression does not have a filter predicate at all.
  • The database just reads the entry that matches the entire LIKE expression.

Now, this is how our normal LIKE operator works, but what happen when you want to use index in 2nd and 3rd case of the example above.

This is when Oracle*Text Utility comes in picture :)

Oracle*Text utility

oracle-text-utilityThe Oracle*Text utility (formally called Oracle ConText and Oracle Intermedia) allows us to parse through a large text column and index on the words within the column.

Unlike ordinary b-tree or bitmap indexes, Oracle context, ctxcat and ctxrule indexes can be set not to update as content is changed.

Since most standard Oracle databases will use the ctxcat index with standard relational tables, you must decide on a refresh interval.

Oracle provides the SYNC operator for this. The default is SY^NC=MANUAL and you must manually synchronize the index with CTX_DDL.SYNC_INDEX.

SYNC (MANUAL | EVERY "interval-string" | ON COMMIT)

Hence, Oracle Text indexes are only useful for removing full-table scans when the tables are largely read-only and/or the end-users don’t mind not having 100% search recall:

  • The target table is relatively static (e.g. nightly batch updates)
  • Your end-users would not mind “missing” the latest row data

Lets take up this with an example:

SQL> CREATE TABLE sac AS SELECT * FROM all_objects;
   
   Table created.


SQL> CREATE INDEX sac_indx ON sac(object_type);
   
   Index created.


SQL> set autotrace trace explain

SQL> select * from sac where object_type LIKE 'TAB%';

        Execution Plan
     ----------------------------------------------------------
     0 SELECT STATEMENT Optimizer=ALL_ROWS (Cost=1 Card=1 Bytes=128
     )

     1 0 TABLE ACCESS (BY INDEX ROWID) OF 'SAC' (TABLE) (Cost=1 Car
     d=1 Bytes=128)

     2 1 INDEX (RANGE SCAN) OF 'SAC_INDX' (INDEX) (Cost=1 Card=1)

Above example shows that using % wild card character towards end probe an Index search.

But if it is used towards start, it will not be used. And sensibly so, because Oracle doesn’t know which data to search, it can start from ‘A to Z’ or ‘a to z’ or even 1 to any number.

See this.

SQL> SELECT *
     FROM sac
     WHERE object_type LIKE '%ABLE';


	Execution Plan
	----------------------------------------------------------
	0 SELECT STATEMENT Optimizer=ALL_ROWS (Cost=148 Card=1004 Byte
	s=128512)

	1 0 TABLE ACCESS (FULL) OF 'SAC' (TABLE) (Cost=148 Card=1004 B
	ytes=128512)

Now how to use the index if you are using Like operator searches. The answer is Domain Indexes.


SQL> connect / as sysdba
Connected.

SQL> grant execute on ctx_ddl to public;
Grant succeeded.


SQL> connect sac;
Connected.

SQL> begin
2 ctx_ddl.create_preference('SUBSTRING_PREF',
3 'BASIC_WORDLIST');
4 ctx_ddl.set_attribute('SUBSTRING_PREF',
5 'SUBSTRING_INDEX','TRUE');
6 end;
7
8 /

PL/SQL procedure successfully completed.
  • ctx_ddl.create_preference: Creates a preference in the Text data dictionary.
  • ctx_ddl.set_attribute : Sets a preference attribute. Use this procedure after you have created a preference with CTX_DDL.CREATE_PREFERENCE.
SQL> drop index sac_indx;
Index dropped.

SQL> create index sac_indx on sac(object_type) indextype is ctxsys.context parameters ('wordlist SUBSTRING_PREF memory 50m');
Index created.

SQL> set autotrace trace exp
SQL> select * from sac where contains (OBJECT_TYPE,'%PACK%') > 0

	Execution Plan
 	----------------------------------------------------------
	0 SELECT STATEMENT Optimizer=ALL_ROWS (Cost=8 Card=19 Bytes=17
	86)
	1 0 TABLE ACCESS (BY INDEX ROWID) OF 'SAC' (TABLE) (Cost=8 Car
	d=19 Bytes=1786)
	2 1 DOMAIN INDEX OF 'SAC_INDX' (INDEX (DOMAIN)) (Cost=4)

In this case the index is getting used.

Index re-synchronization

Because rebuilding an Oracle Text index (context, ctxcat, ctxrule) requires a full-table scan and lots of internal parsing, it is not practical to use triggers for instantaneous index updates. 

Updating Oracle Text indexes is easy and they can be schedules using dbms_job or the Oracle 10g dbms_scheduler utility package:  Oracle text provides a CTX_DDL package with the sync_index and optimize_index procedures:

SQL> EXEC CTX_DDL.SYNC_INDEX('sac_indx');
SQL> EXEC CTX_DDL.OPTIMIZE_INDEX('sac_indx','FULL');

For example, if you create a nightly dbms_scheduler job to call sync_index, your index will be refreshed, but the structure will become sub-optimal over time.  Oracle recommends that you periodically use the optimize_index package to periodically re-build the whole index from scratch.  Index optimization can be performed in three modes (FAST, FULL or TOKEN).

In sum, the Oracle Text indexes are great for removing unnecessary full-table scans from static Oracle tables and they can reduce I/O by several orders of magnitude, greatly improving overall SQL performance

Conclusion

For proximity, soundex and fuzzy searches, use domain indexes.

References

Oracle Documentation

Related Posts

Pagination in Oracle using ROWNUM and Limiting Result Set

$
0
0

paginationROWNUM is a magic column in Oracle Database that gets many people into trouble. When you learn what it is and how it works, however, it can be very useful. I use it for two main things:

  • To perform top- N processing. This is similar to using the LIMIT clause, available in some other databases.
  • To paginate through a query, typically in a stateless environment such as the Web. We can use this.

How ROWNUM Works?

ROWNUM is a pseudocolumn (not a real column) that is available in a query. ROWNUM will be assigned the numbers 1, 2, 3, 4, … N , where N is the number of rows in the set ROWNUM is used with. A ROWNUM value is not assigned permanently to a row (this is a common misconception). A row in a table does not have a number; you cannot ask for row 5 from a table—there is no such thing.

Also confusing to many people is when a ROWNUM value is actually assigned. A ROWNUM value is assigned to a row after it passes the predicate phase of the query but before the query does any sorting or aggregation. Also, a ROWNUM value is incremented only after it is assigned, which is why the following query will never return a row:

SELECT *
  FROM t
 WHERE ROWNUM > 1;

Because ROWNUM > 1 is not true for the first row, ROWNUM does not advance to 2. Hence, no ROWNUM value ever gets to be greater than 1. Consider a query with this structure:

SELECT ..., ROWNUM
     FROM T
   WHERE <WHERE CLAUSE>
   GROUP BY <COLUMNS>
   HAVING <HAVING CLAUSE>
   ORDER BY <COLUMNS>;

Think of it as being processed in this order:

  1. The FROM/WHERE clause goes first. 
  2. ROWNUM is assigned and incremented to each output row from the FROM/WHERE clause. 
  3. SELECT is applied. 
  4. GROUP BY is applied. 
  5. HAVING is applied. 
  6. ORDER BY is applied.

That is why a query in the following form is almost certainly an error:

 SELECT *
    FROM emp
    WHERE ROWNUM <= 5
    ORDER BY sal DESC;

The intention was most likely to get the five highest-paid people—a top- N query. What the query will return is five random records (the first five the query happens to hit), sorted by salary. The procedural pseudocode for this query is as follows:

ROWNUM = 1
FOR x in

(SELECT * FROM emp)
LOOP
    exit when NOT(ROWNUM <= 5)
    OUTPUT record to temp
    ROWNUM = ROWNUM+1
end loop
SORT TEMP

It gets the first five records and then sorts them. A query with WHERE ROWNUM = 5 or WHERE ROWNUM > 5 doesn’t make sense. This is because a ROWNUM value is assigned to a row during the predicate evaluation and gets incremented only after a row passes the WHERE clause.

Here is the correct version of this query:

SELECT *
  FROM (SELECT *
            FROM emp
        ORDER BY sal DESC)
 WHERE ROWNUM <= 5;

This version will sort EMP by salary descending and then return the first five records it encounters (the top-five records). As you’ll see in the top- N discussion coming up shortly, Oracle Database doesn’t really sort the entire result set—it is smarter than that—but conceptually that is what takes place.

For pagination, if you want the 5 -10 records of the employee order by hiredate asc then go for this.

SELECT outer.*
  FROM (SELECT ROWNUM rn, inner.*
          FROM (  SELECT e.*
                    FROM employee e
                ORDER BY hiredate) inner) outer
 WHERE outer.rn >= 5 AND outer.rn <= 10

References

Oracle ROWNUM Documentation

Related Posts

Index Skip Scan in Oracle

$
0
0

index-skip-scan-oracleWith Oracle 9i, the Cost-Based Optimizer (CBO) is equipped with many useful features, one of them is “Index skip scan“. In previous releases a composite index could only be used if the first column, the leading edge, of the index was referenced in the WHERE clause of a statement. In Oracle 9i this restriction is removed because the optimizer can perform skip scans to retrieve rowids for values that do not use the prefix. This means even if you have a composite index on more than one column and you use the non-prefix column alone in your SQL, it may still use index. (Earlier I use to think that it will not use index :) )

Its not always guaranteed that the Index Skip Scan will be used, this is because Cost-Based Optimizer (CBO) will calculate the cost of using the index and if it is more than that of full table scan, then it may not use index.

This approach is advantageous because:

  • It reduces the number of indexes needed to support a range of queries. This increases performance by reducing index maintenance and decreases wasted space associated with multiple indexes.
  • The prefix column should be the most discriminating and the most widely used in queries. These two conditions do not always go hand in hand which makes the decision difficult. In these situations skip scanning reduces the impact of making the “wrong” decision.

Index skip scan works differently from a normal index (range) scan. A normal range scan works from top to bottom first and then move horizontal. But a Skip scan includes several range scans in it. Since the query lacks the leading column it will rewrite the query into smaller queries and each doing a range scan.

Consider following example where we create a test table and create index on first two columns a and b. Also we put some dummy data inside test table. See how Index is getting selected when we execute select statement with column b in where clause.

Step 1:

CREATE TABLE test (a NUMBER, b NUMBER, c NUMBER);

Table created.

Step 2:

CREATE INDEX test_i
   ON test (a, b);

Index created.

Step 3:

BEGIN
   FOR i IN 1 .. 100000
   LOOP
      INSERT INTO test
          VALUES (MOD (i, 5), i, 100);
   END LOOP;

   COMMIT;
END;
/

PL/SQL procedure successfully completed.

Step 4:

exec dbms_stats.gather_table_stats (
        ownname => 'gauravsoni', 
        tabname => 'test', 
        cascade => true
     );

PL/SQL procedure successfully completed.

Step 5:

set autotrace trace exp

Step 6:

SELECT *
  FROM test
 WHERE b = 95267;

Execution Plan

0
SELECT STATEMENT Optimizer=ALL_ROWS (Cost=22 Card=1 Bytes=10)


1 0
TABLE ACCESS (BY INDEX ROWID) OF 'TEST' (TABLE) (Cost=22 Card=1 Bytes=10)


2 1
INDEX (SKIP SCAN) OF 'TEST_I' (INDEX) (Cost=21 Card=1)

I above example, "select * from test where b=95267" was broken down to several small range scan queries. It was effectively equivalent to following:

SELECT *
  FROM test
 WHERE a = 0 AND b = 95267
UNION
SELECT *
  FROM test
 WHERE a = 1 AND b = 95267
UNION
SELECT *
  FROM test
 WHERE a = 2 AND b = 95267
UNION
SELECT *
  FROM test
 WHERE a = 3 AND b = 95267
UNION
SELECT *
  FROM test
 WHERE a = 4 AND b = 95267;

In concrete, saying that skip scan is not as efficient as normal “single range scan” is correct. But yet saves some disk space and overhead of maintaining another index.

Reference

Oracle Documentation on Index Skip Scan

Related Posts


Oracle Skip Locked

$
0
0

Oracle 11g introduced SKIP LOCKED clause to query the records from the table which are not locked in any other active session of the database.

This looks quite similar to exclusive mode of locking.

oracle-skip-locked

The select for update statement has always been problematic for large updates because it the default is to wait for locks and using select for update other tasks can abort waiting on access with the ORA-300036 error:

ORA-30006: resource busy; acquire with WAIT timeout expired

In other cases using select for update with the nowait clause you your own update may abort with the ORA-00054 error:

ORA-00054 resource busy and NOWAIT specified

Even worse, if a select for update task aborts, a zombie process may hold the row locks long term, requiring DBA intervention.

To illustrate, we open two sessions. In the first session, we lock the row with deptno as 10 using FOR UPDATE NOWAIT.

SELECT *
  FROM dept  WHERE
 deptno = 10
FOR UPDATE NOWAIT;

Output:

DEPTNO     DNAME          LOC
---------- -------------- -------------
10         ACCOUNTING     NEW YORK

In the second session, we try to lock two rows (deptno 10 and 20) from the table dept using FOR UPDATE NOWAIT. An exception is thrown after executing the following statement because one of the row (i.e. deptno 10) out of the selected list is already locked by session 1.

SELECT * FROM dept
 WHERE deptno IN (10,20)
FOR UPDATE NOWAIT;

Output:

SELECT * FROM dept WHERE deptno IN (10,20)
FOR UPDATE NOWAIT
ERROR at line 1:
ORA-00054: resource busy and acquire with NOWAIT specified

Now we again try to lock two rows (deptno(s) 10 and 20) from the table dept but using the clause FOR UPDATE SKIP LOCKED instead of FOR UPDATE NOWAIT. As you can see the following statement has:

  1. returned the control without throwing an exception
  2. acquired lock on the row (i.e. deptno 20) which is available for locking
  3. skipped the row (i.e. deptno 10) that has been locked already by session 1
SELECT * FROM dept
 WHERE deptno IN (10,20)
FOR UPDATE SKIP LOCKED;

Output:

DEPTNO     DNAME          LOC
---------- -------------- -------------
20         RESEARCH       DALLAS

Related Posts

Java: Passing Array to Oracle Stored Procedure

$
0
0

This tutorial guides us on how to pass Array objects from Java to stored procedures in Oracle and also, how to retrieve an array object in Java.

All PLSQL arrays can not be called from java. An array needs to be created as TYPE, at SCHEMA level in the database and then it can be used with ArrayDescriptor in Java, as oracle.sql.ArrayDescriptor class in Java can not access at package level.

Database Code

First, Create an array, at SCHEMA level. An example is shown below:

CREATE TYPE array_table AS TABLE OF VARCHAR2 (50); -- Array of String

CREATE TYPE array_int AS TABLE OF NUMBER;          -- Array of integers

Next, Create a procedure which takes an array as an input parameter and returns an array as its OUT parameter.

An example of one such procedure is shown below, which has 2 parameters -

  1. an array of String as its IN parameter – p_array
  2. an array of Integers as OUT parameter – p_arr_int
CREATE OR REPLACE PROCEDURE SchemaName.proc1 (p_array     IN     array_table,
                                              len            OUT NUMBER,
                                              p_arr_int      OUT array_int)
AS
   v_count   NUMBER;
BEGIN
   p_arr_int := NEW array_int ();
   p_arr_int.EXTEND (10);
   len := p_array.COUNT;
   v_count := 0;

   FOR i IN 1 .. p_array.COUNT
   LOOP
      DBMS_OUTPUT.put_line (p_array (i));
      p_arr_int (i) := v_count;
      v_count := v_count + 1;
   END LOOP;
END;
/

After this, Execution permission would be required to execute the procedure created by you:

GRANT EXECUTE ON SchemaNAme.proc1 TO UserName;

Java Code

Create a java class which makes a call to the procedure proc1, created before.

Below is an example which contains the whole flow from creating a connection with the database, to making a call to the stored procedure, passing an array to Oracle procedure, retrieving an array from an Oracle procedure and displaying the result.

import java.math.BigDecimal;
import java.sql.CallableStatement;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.Types;
import oracle.jdbc.OracleCallableStatement;
import oracle.jdbc.internal.OracleTypes;
import oracle.sql.ARRAY;
import oracle.sql.ArrayDescriptor; 

public class TestDatabase {
	
	public static void passArray()
	{
		try{
		
			Class.forName("oracle.jdbc.OracleDriver");			
			Connection con = DriverManager.getConnection("jdbc:oracle:thin:url ","UserName","Password");;
			
			String array[] = {"one", "two", "three","four"}; 
			
			ArrayDescriptor des = ArrayDescriptor.createDescriptor("SchemaName.ARRAY_TABLE", con);
			ARRAY array_to_pass = new ARRAY(des,con,array);
			
			CallableStatement st = con.prepareCall("call SchemaName.proc1(?,?,?)");

			// Passing an array to the procedure - 
			st.setArray(1, array_to_pass);

			st.registerOutParameter(2, Types.INTEGER);
			st.registerOutParameter(3,OracleTypes.ARRAY,"SchemaName.ARRAY_INT");
			st.execute();
			
			System.out.println("size : "+st.getInt(2));

			// Retrieving array from the resultset of the procedure after execution -
			ARRAY arr = ((OracleCallableStatement)st).getARRAY(3);
			 BigDecimal[] recievedArray = (BigDecimal[])(arr.getArray());

			for(int i=0;i<recievedArray.length;i++)
				System.out.println("element" + i + ":" + recievedArray[i] + "\n");
			
		} catch(Exception e) {
			System.out.println(e);
		}
	}

	public static void main(String args[]){
		passArray();
	}
}

Brief Explanations:

  1. Class.forName() – Returns the Class object associated with the class or interface with the given string name.
  2. DriverManager.getConnection() – Attempts to establish a connection to the given database URL.
  3. oracle.sql.ArrayDescriptor – Describes an array class
  4. ArrayDescriptor.createDescriptor() – Descriptor factory. Lookup the name in the database, and determine the characteristics of this array.
  5. oracle.sql.ARRAY – An Oracle implementation for generic JDBC Array interface.
  6. CallableStatement – The interface used to execute SQL stored procedures.

References

Related Posts

Oracle XMLTable Tutorial with Example

$
0
0

oracle-xmltable-example
Since Oracle 10g, Oracle has added new functions XQuery and XMLTable to its arsenal of XML processing APIs. XMLQuery lets you construct XML data and query XML and relational data using the XQuery language. XMLTable lets you create relational tables and columns from XQuery query results.
Oracle XMLTable Tutorial

In this post we will learn about Oracle XMLTable function. The best way to learn is to learn by example. This way you can quickly understand different aspect of the API.

So lets start with our example. Consider a table EMPLOYEES which holds some XML data. Below is the create statement this table.

CREATE TABLE EMPLOYEES
(
   id     NUMBER,
   data   XMLTYPE
);

Now the table is ready. Lets insert a record in it. Below INSERT statement add one record having some XML content in it.

INSERT INTO EMPLOYEES
     VALUES (1, xmltype ('<Employees>
    <Employee emplid="1111" type="admin">
        <firstname>John</firstname>
        <lastname>Watson</lastname>
        <age>30</age>
        <email>johnwatson@sh.com</email>
    </Employee>
    <Employee emplid="2222" type="admin">
        <firstname>Sherlock</firstname>
        <lastname>Homes</lastname>
        <age>32</age>
        <email>sherlock@sh.com</email>
    </Employee>
    <Employee emplid="3333" type="user">
        <firstname>Jim</firstname>
        <lastname>Moriarty</lastname>
        <age>52</age>
        <email>jim@sh.com</email>
    </Employee>
    <Employee emplid="4444" type="user">
        <firstname>Mycroft</firstname>
        <lastname>Holmes</lastname>
        <age>41</age>
        <email>mycroft@sh.com</email>
    </Employee>
</Employees>'));

Notice the XML contains employee related data. Before we start lets check few facts from above xml.

  1. There are 4 employees in our xml file
  2. Each employee has a unique employee id defined by attribute emplid
  3. Each employee also has an attribute type which defines whether an employee is admin or user.
  4. Each employee has four child nodes: firstname, lastname, age and email
  5. Age is a number

Now we can use Oracle XMLTable function to retrieve different information from this XML.

Let’s get started…

1. Learning XPath Expressions

Before we start with Oracle XMLTable function it is good to know a bit about XPath. XPath uses a path expression to select nodes or list of node from a xml document. Heres a list of useful paths and expression that can be used to select any node/nodelist from a xml document.

Expression Description
nodename Selects all nodes with the name “nodename”
/ Selects from the root node
// Selects nodes in the document from the current node that match the selection no matter where they are
. Selects the current node
.. Selects the parent of the current node
@ Selects attributes
employee Selects all nodes with the name “employee”
employees/employee Selects all employee elements that are children of employees
//employee Selects all employee elements no matter where they are in the document

Below list of expressions are called Predicates. The Predicates are defined in square brackets [ ... ]. They are used to find a specific node or a node that contains a specific value.

Path Expression Result
/employees/employee[1] Selects the first employee element that is the child of the employees element.
/employees/employee[last()] Selects the last employee element that is the child of the employees element
/employees/employee[last()-1] Selects the last but one employee element that is the child of the employees element
//employee[@type='admin'] Selects all the employee elements that have an attribute named type with a value of ‘admin’

There are other useful expressions that you can use to query the data.

Read this w3school page for more details: http://www.w3schools.com/xpath/xpath_syntax.asp

2. Learning Basics of Oracle XMLTable function

Lets get started with Oracle XMLTable function. Below are few examples of using different expressions of XPath to fetch some information from xml document.

2.1 Read firstname and lastname of all employees

In this query, we using XMLTable function to parse the XML content from Employees table.

--print firstname and lastname of all employees 
   SELECT t.id, x.*
     FROM employees t,
          XMLTABLE ('/Employees/Employee'
                    PASSING t.data
                    COLUMNS firstname VARCHAR2(30) PATH 'firstname', 
                            lastname VARCHAR2(30) PATH 'lastname') x
    WHERE t.id = 1;

Note the syntax of XMLTable function:

XMLTable('<XQuery>' 
         PASSING <xml column>
         COLUMNS <new column name> <column type> PATH <XQuery path>)

The XMLTABLE function contains one row-generating XQuery expression and, in the COLUMNS clause, one or multiple column-generating expressions. In Listing 1, the row-generating expression is the XPath /Employees/Employee. The passing clause defines that the emp.data refers to the XML column data of the table Employees emp.

The COLUMNS clause is used to transform XML data into relational data. Each of the entries in this clause defines a column with a column name and a SQL data type. In above query we defined two columns firstname and lastname that points to PATH firstname and lastname or selected XML node.

Output:
oracle-xmltables-query2

2.2 Read node value using text()

In above example we read the content of node firstname / lastname. Sometimes you may want to fetch the text value of currently selected node item. In below example we will select path /Employees/Employee/firstname. And then use text() expression to get the value of this selected node.
Below query will read firstname of all the employees.

--print firstname of all employees
   SELECT t.id, x.*
     FROM employees t,
          XMLTABLE ('/Employees/Employee/firstname'
                    PASSING t.data
                    COLUMNS firstname VARCHAR2 (30) PATH 'text()') x
    WHERE t.id = 1;

Output:
oracle-xmltables-query1
Along with text() expression, Oracle provides various other useful expressions. For example item(), node(), attribute(), element(), document-node(), namespace(), text(), xs:integer, xs:string.

2.3 Read Attribute value of selected node

We can select an attribute value in our query. The attribute can be defined in XML node. In below query we select attribute type from the employee node.

--print employee type of all employees
   SELECT emp.id, x.*
     FROM employees emp,
          XMLTABLE ('/Employees/Employee'
                    PASSING emp.data
                    COLUMNS firstname VARCHAR2(30) PATH 'firstname',
                            type VARCHAR2(30) PATH '@type') x;

Output:
oracle xmltable attributes

2.3 Read specific employee record using employee id

--print firstname and lastname of employee with id 2222 
   SELECT t.id, x.*
     FROM employees t,
          XMLTABLE ('/Employees/Employee[@emplid=2222]'
                    PASSING t.data
                    COLUMNS firstname VARCHAR2(30) PATH 'firstname', 
                            lastname VARCHAR2(30) PATH 'lastname') x
    WHERE t.id = 1;

Output:
oracle-xmltables-query3

2.4 Read firstname lastname of all employees who are admins

--print firstname and lastname of employees who are admins 
   SELECT t.id, x.*
     FROM employees t,
          XMLTABLE ('/Employees/Employee[@type="admin"]'
                    PASSING t.data
                    COLUMNS firstname VARCHAR2(30) PATH 'firstname', 
                            lastname VARCHAR2(30) PATH 'lastname') x
    WHERE t.id = 1;

Output:
oracle-xmltables-query4

2.5 Read firstname lastname of all employees who are older than 40 year

--print firstname and lastname of employees having age > 40
   SELECT t.id, x.*
     FROM employees t,
          XMLTABLE ('/Employees/Employee[age>40]'
                    PASSING t.data
                    COLUMNS firstname VARCHAR2(30) PATH 'firstname', 
                            lastname VARCHAR2(30) PATH 'lastname',
                            age VARCHAR2(30) PATH 'age') x
    WHERE t.id = 1;

Output:
oracle-xmltables-query5

Reference

The post Oracle XMLTable Tutorial with Example appeared first on ViralPatel.net.

45 Useful Oracle Queries

$
0
0

useful-oracle-queries
Here’s a list of 40+ Useful Oracle queries that every Oracle developer must bookmark. These queries range from date manipulation, getting server info, get execution status, calculate database size etc.

Date / Time related queries

  1. Get the first day of the month

    Quickly returns the first day of current month. Instead of current month you want to find first day of month where a date falls, replace SYSDATE with any date column/value.

    SELECT TRUNC (SYSDATE, 'MONTH') "First day of current month" 
        FROM DUAL;
    
  2. Get the last day of the month

    This query is similar to above but returns last day of current month. One thing worth noting is that it automatically takes care of leap year. So if you have 29 days in Feb, it will return 29/2. Also similar to above query replace SYSDATE with any other date column/value to find last day of that particular month.

    SELECT TRUNC (LAST_DAY (SYSDATE)) "Last day of current month" 
        FROM DUAL;
    
  3. Get the first day of the Year

    First day of year is always 1-Jan. This query can be use in stored procedure where you quickly want first day of year for some calculation.

    SELECT TRUNC (SYSDATE, 'YEAR') "Year First Day" FROM DUAL;
    
  4. Get the last day of the year

    Similar to above query. Instead of first day this query returns last day of current year.

    SELECT ADD_MONTHS (TRUNC (SYSDATE, 'YEAR'), 12) - 1 "Year Last Day" FROM DUAL
    
  5. Get number of days in current month

    Now this is useful. This query returns number of days in current month. You can change SYSDATE with any date/value to know number of days in that month.

    SELECT CAST (TO_CHAR (LAST_DAY (SYSDATE), 'dd') AS INT) number_of_days
      FROM DUAL;
    
  6. Get number of days left in current month

    Below query calculates number of days left in current month.

    SELECT SYSDATE,
           LAST_DAY (SYSDATE) "Last",
           LAST_DAY (SYSDATE) - SYSDATE "Days left"
      FROM DUAL;
    
  7. Get number of days between two dates

    Use this query to get difference between two dates in number of days.

    SELECT ROUND ( (MONTHS_BETWEEN ('01-Feb-2014', '01-Mar-2012') * 30), 0)
              num_of_days
      FROM DUAL;
    
    OR
    
    SELECT TRUNC(sysdate) - TRUNC(e.hire_date) FROM employees;
    

    Use second query if you need to find number of days since some specific date. In this example number of days since any employee is hired.

  8. Display each months start and end date upto last month of the year

    This clever query displays start date and end date of each month in current year. You might want to use this for certain types of calculations.

    SELECT ADD_MONTHS (TRUNC (SYSDATE, 'MONTH'), i) start_date,
           TRUNC (LAST_DAY (ADD_MONTHS (SYSDATE, i))) end_date
      FROM XMLTABLE (
              'for $i in 0 to xs:int(D) return $i'
              PASSING XMLELEMENT (
                         d,
                         FLOOR (
                            MONTHS_BETWEEN (
                               ADD_MONTHS (TRUNC (SYSDATE, 'YEAR') - 1, 12),
                               SYSDATE)))
              COLUMNS i INTEGER PATH '.');
    
  9. Get number of seconds passed since today (since 00:00 hr)

    SELECT (SYSDATE - TRUNC (SYSDATE)) * 24 * 60 * 60 num_of_sec_since_morning
      FROM DUAL;
    
  10. Get number of seconds left today (till 23:59:59 hr)

    SELECT (TRUNC (SYSDATE+1) - SYSDATE) * 24 * 60 * 60 num_of_sec_left
      FROM DUAL;
    

    Data dictionary queries

  11. Check if a table exists in the current database schema

    A simple query that can be used to check if a table exists before you create it. This way you can make your create table script rerunnable. Just replace table_name with actual table you want to check. This query will check if table exists for current user (from where the query is executed).

    SELECT table_name
      FROM user_tables
     WHERE table_name = 'TABLE_NAME';
    
  12. Check if a column exists in a table

    Simple query to check if a particular column exists in table. Useful when you tries to add new column in table using ALTER TABLE statement, you might wanna check if column already exists before adding one.

    SELECT column_name AS FOUND
      FROM user_tab_cols
     WHERE table_name = 'TABLE_NAME' AND column_name = 'COLUMN_NAME';
    
  13. Showing the table structure

    This query gives you the DDL statement for any table. Notice we have pass ‘TABLE’ as first parameter. This query can be generalized to get DDL statement of any database object. For example to get DDL for a view just replace first argument with ‘VIEW’ and second with your view name and so.

    SELECT DBMS_METADATA.get_ddl ('TABLE', 'TABLE_NAME', 'USER_NAME') FROM DUAL;
    
  14. Getting current schema

    Yet another query to get current schema name.

    SELECT SYS_CONTEXT ('userenv', 'current_schema') FROM DUAL;
    
  15. Changing current schema

    Yet another query to change the current schema. Useful when your script is expected to run under certain user but is actually executed by other user. It is always safe to set the current user to what your script expects.

    ALTER SESSION SET CURRENT_SCHEMA = new_schema;
    

    Database administration queries

  16. Database version information

    Returns the Oracle database version.

    SELECT * FROM v$version;
    
  17. Database default information

    Some system default information.

    SELECT username,
           profile,
           default_tablespace,
           temporary_tablespace
      FROM dba_users;
    
  18. Database Character Set information

    Display the character set information of database.

    SELECT * FROM nls_database_parameters;
    
  19. Get Oracle version

    SELECT VALUE
      FROM v$system_parameter
     WHERE name = 'compatible';
    
  20. Store data case sensitive but to index it case insensitive

    Now this ones tricky. Sometime you might querying database on some value independent of case. In your query you might do UPPER(..) = UPPER(..) on both sides to make it case insensitive. Now in such cases, you might want to make your index case insensitive so that they don’t occupy more space. Feel free to experiment with this one.

    CREATE TABLE tab (col1 VARCHAR2 (10));
    
    CREATE INDEX idx1
       ON tab (UPPER (col1));
    
    ANALYZE TABLE a COMPUTE STATISTICS;
    
  21. Resizing Tablespace without adding datafile

    Yet another DDL query to resize table space.

    ALTER DATABASE DATAFILE '/work/oradata/STARTST/STAR02D.dbf' resize 2000M;
    
  22. Checking autoextend on/off for Tablespaces

    Query to check if autoextend is on or off for a given tablespace.

    SELECT SUBSTR (file_name, 1, 50), AUTOEXTENSIBLE FROM dba_data_files;
    
    (OR)
    
    SELECT tablespace_name, AUTOEXTENSIBLE FROM dba_data_files;
    
  23. Adding datafile to a tablespace

    Query to add datafile in a tablespace.

    ALTER TABLESPACE data01 ADD DATAFILE '/work/oradata/STARTST/data01.dbf'
        SIZE 1000M AUTOEXTEND OFF;
    
  24. Increasing datafile size

    Yet another query to increase the datafile size of a given datafile.

    ALTER DATABASE DATAFILE '/u01/app/Test_data_01.dbf' RESIZE 2G;
    
  25. Find the Actual size of a Database

    Gives the actual database size in GB.

    SELECT SUM (bytes) / 1024 / 1024 / 1024 AS GB FROM dba_data_files;
    
  26. Find the size occupied by Data in a Database or Database usage details

    Gives the size occupied by data in this database.

    SELECT SUM (bytes) / 1024 / 1024 / 1024 AS GB FROM dba_segments;
    
  27. Find the size of the SCHEMA/USER

    Give the size of user in MBs.

    SELECT SUM (bytes / 1024 / 1024) "size"
      FROM dba_segments
     WHERE owner = '&owner';
    
  28. Last SQL fired by the User on Database

    This query will display last SQL query fired by each user in this database. Notice how this query display last SQL per each session.

    SELECT S.USERNAME || '(' || s.sid || ')-' || s.osuser UNAME,
             s.program || '-' || s.terminal || '(' || s.machine || ')' PROG,
             s.sid || '/' || s.serial# sid,
             s.status "Status",
             p.spid,
             sql_text sqltext
        FROM v$sqltext_with_newlines t, V$SESSION s, v$process p
       WHERE     t.address = s.sql_address
             AND p.addr = s.paddr(+)
             AND t.hash_value = s.sql_hash_value
    ORDER BY s.sid, t.piece;
    

    Performance related queries

  29. CPU usage of the USER

    Displays CPU usage for each User. Useful to understand database load by user.

    SELECT ss.username, se.SID, VALUE / 100 cpu_usage_seconds
        FROM v$session ss, v$sesstat se, v$statname sn
       WHERE     se.STATISTIC# = sn.STATISTIC#
             AND NAME LIKE '%CPU used by this session%'
             AND se.SID = ss.SID
             AND ss.status = 'ACTIVE'
             AND ss.username IS NOT NULL
    ORDER BY VALUE DESC;
    
  30. Long Query progress in database

    Show the progress of long running queries.

    SELECT a.sid,
             a.serial#,
             b.username,
             opname OPERATION,
             target OBJECT,
             TRUNC (elapsed_seconds, 5) "ET (s)",
             TO_CHAR (start_time, 'HH24:MI:SS') start_time,
             ROUND ( (sofar / totalwork) * 100, 2) "COMPLETE (%)"
        FROM v$session_longops a, v$session b
       WHERE     a.sid = b.sid
             AND b.username NOT IN ('SYS', 'SYSTEM')
             AND totalwork > 0
    ORDER BY elapsed_seconds;
    
  31. Get current session id, process id, client process id?

    This is for those who wants to do some voodoo magic using process ids and session ids.

    SELECT b.sid,
           b.serial#,
           a.spid processid,
           b.process clientpid
      FROM v$process a, v$session b
     WHERE a.addr = b.paddr AND b.audsid = USERENV ('sessionid');
    
    • V$SESSION.SID AND V$SESSION.SERIAL# is database process id
    • V$PROCESS.SPID is shadow process id on this database server
    • V$SESSION.PROCESS is client PROCESS ID, ON windows it IS : separated THE FIRST # IS THE PROCESS ID ON THE client AND 2nd one IS THE THREAD id.
  32. Last SQL Fired from particular Schema or Table:

    SELECT CREATED, TIMESTAMP, last_ddl_time
      FROM all_objects
     WHERE     OWNER = 'MYSCHEMA'
           AND OBJECT_TYPE = 'TABLE'
           AND OBJECT_NAME = 'EMPLOYEE_TABLE';
    
  33. Find Top 10 SQL by reads per execution

    SELECT *
      FROM (  SELECT ROWNUM,
                     SUBSTR (a.sql_text, 1, 200) sql_text,
                     TRUNC (
                        a.disk_reads / DECODE (a.executions, 0, 1, a.executions))
                        reads_per_execution,
                     a.buffer_gets,
                     a.disk_reads,
                     a.executions,
                     a.sorts,
                     a.address
                FROM v$sqlarea a
            ORDER BY 3 DESC)
     WHERE ROWNUM < 10;
    
  34. Oracle SQL query over the view that shows actual Oracle connections.

    SELECT osuser,
             username,
             machine,
             program
        FROM v$session
    ORDER BY osuser;
    
  35. Oracle SQL query that show the opened connections group by the program that opens the connection.

    SELECT program application, COUNT (program) Numero_Sesiones
        FROM v$session
    GROUP BY program
    ORDER BY Numero_Sesiones DESC;
    
  36. Oracle SQL query that shows Oracle users connected and the sessions number for user

    SELECT username Usuario_Oracle, COUNT (username) Numero_Sesiones
        FROM v$session
    GROUP BY username
    ORDER BY Numero_Sesiones DESC;
    
  37. Get number of objects per owner

    SELECT owner, COUNT (owner) number_of_objects
        FROM dba_objects
    GROUP BY owner
    ORDER BY number_of_objects DESC;
    

    Utility / Math related queries

  38. Convert number to words

    More info: Converting number into words in Oracle

    SELECT TO_CHAR (TO_DATE (1526, 'j'), 'jsp') FROM DUAL;
    

    Output:

    one thousand five hundred twenty-six
    
  39. Find string in package source code

    Below query will search for string ‘FOO_SOMETHING’ in all package source. This query comes handy when you want to find a particular procedure or function call from all the source code.

    --search a string foo_something in package source code
    SELECT *
      FROM dba_source
     WHERE UPPER (text) LIKE '%FOO_SOMETHING%' 
    AND owner = 'USER_NAME';
    
  40. Convert Comma Separated Values into Table

    The query can come quite handy when you have comma separated data string that you need to convert into table so that you can use other SQL queries like IN or NOT IN. Here we are converting ‘AA,BB,CC,DD,EE,FF’ string to table containing AA, BB, CC etc. as each row. Once you have this table you can join it with other table to quickly do some useful stuffs.

    WITH csv
         AS (SELECT 'AA,BB,CC,DD,EE,FF'
                       AS csvdata
               FROM DUAL)
        SELECT REGEXP_SUBSTR (csv.csvdata, '[^,]+', 1, LEVEL) pivot_char
          FROM DUAL, csv
    CONNECT BY REGEXP_SUBSTR (csv.csvdata,'[^,]+', 1, LEVEL) IS NOT NULL;
    
  41. Find the last record from a table

    This ones straight forward. Use this when your table does not have primary key or you cannot be sure if record having max primary key is the latest one.

    SELECT *
      FROM employees
     WHERE ROWID IN (SELECT MAX (ROWID) FROM employees);
    
    (OR)
    
    SELECT * FROM employees
    MINUS
    SELECT *
      FROM employees
     WHERE ROWNUM < (SELECT COUNT (*) FROM employees);
    
  42. Row Data Multiplication in Oracle

    This query use some tricky math functions to multiply values from each row. Read below article for more details.
    More info: Row Data Multiplication In Oracle

    WITH tbl
         AS (SELECT -2 num FROM DUAL
             UNION
             SELECT -3 num FROM DUAL
             UNION
             SELECT -4 num FROM DUAL),
         sign_val
         AS (SELECT CASE MOD (COUNT (*), 2) WHEN 0 THEN 1 ELSE -1 END val
               FROM tbl
              WHERE num < 0)
      SELECT EXP (SUM (LN (ABS (num)))) * val
        FROM tbl, sign_val
    GROUP BY val;
    
  43. Generating Random Data In Oracle

    You might want to generate some random data to quickly insert in table for testing. Below query help you do that. Read this article for more details.
    More info: Random Data in Oracle

    SELECT LEVEL empl_id,
               MOD (ROWNUM, 50000) dept_id,
               TRUNC (DBMS_RANDOM.VALUE (1000, 500000), 2) salary,
               DECODE (ROUND (DBMS_RANDOM.VALUE (1, 2)),  1, 'M',  2, 'F') gender,
               TO_DATE (
                     ROUND (DBMS_RANDOM.VALUE (1, 28))
                  || '-'
                  || ROUND (DBMS_RANDOM.VALUE (1, 12))
                  || '-'
                  || ROUND (DBMS_RANDOM.VALUE (1900, 2010)),
                  'DD-MM-YYYY')
                  dob,
               DBMS_RANDOM.STRING ('x', DBMS_RANDOM.VALUE (20, 50)) address
          FROM DUAL
    CONNECT BY LEVEL < 10000;
    
  44. Random number generator in Oracle

    Plain old random number generator in Oracle. This ones generate a random number between 0 and 100. Change the multiplier to number that you want to set limit for.

    --generate random number between 0 and 100
    SELECT ROUND (DBMS_RANDOM.VALUE () * 100) + 1 AS random_num FROM DUAL;
    
  45. Check if table contains any data

    This one can be written in multiple ways. You can create count(*) on a table to know number of rows. But this query is more efficient given the fact that we are only interested in knowing if table has any data.

    SELECT 1
      FROM TABLE_NAME
     WHERE ROWNUM = 1;
    

If you have some cool query that can make life of other Oracle developers easy, do share in comment section.

The post 45 Useful Oracle Queries appeared first on ViralPatel.net.

How to pass CLOB argument in EXECUTE IMMEDIATE

$
0
0

clob argument in execute immediate oracle 11gWe know that Oracle EXECUTE IMMEDIATE statement implements Dynamic SQL in Oracle. It provides end-to-end support when executing a dynamic SQL statement or an anonymous PL/SQL block. Before Oracle 11g, EXECUTE IMMEDIATE supported SQL string statements up to 32K in length.

Oracle 11g allows the usage of CLOB datatypes as an argument which eradicates the constraint we faced on the length of strings when passed as an argument to Execute immediate.

Lets take an example to show how execute immediate failed for strings of size > 32K

Example 1:

 
DECLARE
   var   VARCHAR2 (32767);
BEGIN
   var                          := 'create table temp_a(a number(10))';
-- to make a string of length > 32767 
   WHILE (LENGTH (var) < 33000)
   LOOP
      var                          := var || CHR (10) || '--comment';
   END LOOP;

   DBMS_OUTPUT.put_line (LENGTH (var));

   EXECUTE IMMEDIATE var;
END;

It will throw an obvious error : ORA-06502: PL/SQL: numeric or value error: character string buffer too small

Lets start with how these scenarios were handled prior to introduction of CLOB argument in Execute Immediate

DBMS_SQL was used with its inbuilt functions to take care of Dynamic SQL. Its inbuilt function PARSE was used to take care of dynamic SQL of 64k size.

But it had certain drawbacks:

  • DBMS_SQL.PARSE() could not handle CLOB argument
  • A REF CURSOR can not be converted to a DBMS_SQL cursor and vice versa to support interoperability
  • DBMS_SQL did not supports the full range of data types (including collections and object types)
  • DBMS_SQL did not allows bulk binds using user-define (sic) collection types

A Simple example to show how DBMS_SQL was used to take care of long strings :

Example 2:

 
DECLARE
   vara           VARCHAR2 (32767);
   varb           VARCHAR2 (32767);
   ln_cursor   NUMBER;
   ln_result   NUMBER;
   ln_sql_id   NUMBER           := 1;
BEGIN
   ln_cursor                  := DBMS_SQL.open_cursor;
   
   vara                          := 'create table testa( a number(10),';
   -- to make length  32 k
   while length(vara) <32000 
   loop
   vara := vara || chr(10) || '--comment';
   end loop;
   
   varb                          := ' b number(10))';
   -- to make length  32 k
   while length(varb) <32000 loop
   varb := varb || chr(10) || '--comment';
   end loop;
   dbms_output.put_line (length(vara)||'and'||length(varb));
   DBMS_SQL.parse (ln_cursor, vara ||chr(10)|| varb, DBMS_SQL.native);
   ln_result                  := DBMS_SQL.EXECUTE (ln_cursor);
   DBMS_SQL.close_cursor (ln_cursor);
END;

CLOB argument in Oracle 11g

Oracle Database 11g removes DBMS_SQL limitations restrictions to make the support of dynamic SQL from PL/SQL functionally complete.

Lets see it through an Example.

Example 3: The only difference in Example 3 as compared to Example 2 is Use of CLOB for the declaration of vara variable.

 

DECLARE
   vara        CLOB;
   ln_cursor   NUMBER;
   ln_result   NUMBER;
   ln_sql_id   NUMBER           := 1;
BEGIN
   ln_cursor                  := DBMS_SQL.open_cursor;
   
   vara                          := 'create table testa( a number(10))';
   -- to make length  32 k
   while length(vara) <70000 
   loop
   vara := vara || chr(10) || '--comment';
   end loop;
   
   
   dbms_output.put_line (length(vara));
   DBMS_SQL.parse (ln_cursor, vara, DBMS_SQL.native);
   ln_result                  := DBMS_SQL.EXECUTE (ln_cursor);
   DBMS_SQL.close_cursor (ln_cursor);
END;

Now Both Native Dynamic SQL and DBMS_SQL support SQL strings stored in CLOBs. But using DBMS_SQL has an overload of PARSE that accepts a collection of SQL string fragments.

This is not ideal and the CLOB implementation in EXECUTE IMMEDIATE solves any issues we might have had with the previous alternatives.

Example 4:

 
DECLARE
   vara           CLOB;
   
BEGIN
   vara                          := 'create table testa( a number(10))';
   -- to make length  64k k
   while length(vara) <70000 
   loop
   vara := vara || chr(10) || '--comment';
   end loop;
   
   dbms_output.put_line (length(vara));
   EXECUTE IMMEDIATE vara;
   
END; 

So now don’t worry about the size of strings, Oracle 11g has solved the limitations we had for Dynamic SQL executions.

The post How to pass CLOB argument in EXECUTE IMMEDIATE appeared first on ViralPatel.net.

Compound Triggers in Oracle 11g – Tutorial with example

$
0
0

Compound Triggers in Oracle 11gIn Oracle 11g, the concept of compound trigger was introduced. A compound trigger is a single trigger on a table that enables you to specify actions for each of four timing points:

  1. Before the firing statement
  2. Before each row that the firing statement affects
  3. After each row that the firing statement affects
  4. After the firing statement

With the compound trigger, both the statement-level and row-level action can be put up in a single trigger. Plus there is an added advantage: it allows sharing of common state between all the trigger-points using variable. This is because compound trigger in oracle 11g has a declarative section where one can declare variable to be used within trigger. This common state is established at the start of triggering statement and is destroyed after completion of trigger (regardless of trigger being in error or not). If same had to be done without compound-trigger, it might have been required to share data using packages.

When to use Compound Triggers

The compound trigger is useful when you want to accumulate facts that characterize the “for each row” changes and then act on them as a body at “after statement” time. Two popular reasons to use compound trigger are:

  1. To accumulate rows for bulk-insertion. We will later see an example for this.
  2. To avoid the infamous ORA-04091: mutating-table error.

Details of Syntax

CREATE OR REPLACE TRIGGER compound_trigger_name
FOR [INSERT|DELETE]UPDATE [OF column] ON table
COMPOUND TRIGGER
   -- Declarative Section (optional)
   -- Variables declared here have firing-statement duration.
     
     --Executed before DML statement
     BEFORE STATEMENT IS
     BEGIN
       NULL;
     END BEFORE STATEMENT;
   
     --Executed before each row change- :NEW, :OLD are available
     BEFORE EACH ROW IS
     BEGIN
       NULL;
     END BEFORE EACH ROW;
   
     --Executed aftereach row change- :NEW, :OLD are available
     AFTER EACH ROW IS
     BEGIN
       NULL;
     END AFTER EACH ROW;
   
     --Executed after DML statement
     AFTER STATEMENT IS
     BEGIN
       NULL;
     END AFTER STATEMENT;

END compound_trigger_name;

Note the ‘COMPOUND TRIGGER’ keyword above.

Some Restriction/Catches to note

  1. The body of a compound trigger must be a compound trigger block.
  2. A compound trigger must be a DML trigger.
  3. A compound trigger must be defined on either a table or a view.
  4. The declarative part cannot include PRAGMA AUTONOMOUS_TRANSACTION.
  5. A compound trigger body cannot have an initialization block; therefore, it cannot have an exception section. This is not a problem, because the BEFORE STATEMENT section always executes exactly once before any other timing-point section executes.
  6. An exception that occurs in one section must be handled in that section. It cannot transfer control to another section.
  7. If a section includes a GOTO statement, the target of the GOTO statement must be in the same section.
  8. OLD, :NEW, and :PARENT cannot appear in the declarative part, the BEFORE STATEMENT section, or the AFTER STATEMENT section.
  9. Only the BEFORE EACH ROW section can change the value of :NEW.
  10. If, after the compound trigger fires, the triggering statement rolls back due to a DML exception:
    • Local variables declared in the compound trigger sections are re-initialized, and any values computed thus far are lost.
    • Side effects from firing the compound trigger are not rolled back.
  11. The firing order of compound triggers is not guaranteed. Their firing can be interleaved with the firing of simple triggers.
  12. If compound triggers are ordered using the FOLLOWS option, and if the target of FOLLOWS does not contain the corresponding section as source code, the ordering is ignored.

Example: Using Compound Triggers in Table Auditing

Hopefully this example with make things more clear. Lets create a compound trigger for auditing a large table called ‘employees’. Any changes made in any field of ‘employees’ table needs to be logged in as a separate row in audit table ‘aud_empl’.
Since each row update in employees table needs to make multiple inserts in the audit table, we should consider using a compound trigger so that batching of inserts can be performed.

But before that we need to create our Tables:

--Target Table
CREATE TABLE employees(
    emp_id  varchar2(50) NOT NULL PRIMARY KEY,
    name    varchar2(50) NOT NULL, 
    salary  number NOT NULL
);

--Audit Table
CREATE TABLE aud_emp(
    upd_by    varchar2(50) NOT NULL, 
    upd_dt    date NOT NULL,
    field     varchar2(50) NOT NULL, 
    old_value varchar2(50) NOT NULL,
    new_value varchar2(50) NOT NULL);

Now the trigger…
On update of each row instead of performing an insert operation for each field, we store (buffer) the required attributes in a Arrays of type aud_emp. Once a threshold is reached (say 1000 records), we flush the buffered data into audit table and reset the counter for further buffering.
And at last, as part of AFTER STATEMENT we flush any remaining data left in buffer.

--Trigger
CREATE OR REPLACE TRIGGER aud_emp
FOR INSERT OR UPDATE
ON employees
COMPOUND TRIGGER
  
  TYPE t_emp_changes       IS TABLE OF aud_emp%ROWTYPE INDEX BY SIMPLE_INTEGER;
  v_emp_changes            t_emp_changes;
  
  v_index                  SIMPLE_INTEGER       := 0;
  v_threshhold    CONSTANT SIMPLE_INTEGER       := 1000; --maximum number of rows to write in one go.
  v_user          VARCHAR2(50); --logged in user
  
  PROCEDURE flush_logs
  IS
    v_updates       CONSTANT SIMPLE_INTEGER := v_emp_changes.count();
  BEGIN

    FORALL v_count IN 1..v_updates
        INSERT INTO aud_emp
             VALUES v_emp_changes(v_count);

    v_emp_changes.delete();
    v_index := 0; --resetting threshold for next bulk-insert.

  END flush_logs;

  AFTER EACH ROW
  IS
  BEGIN
        
    IF INSERTING THEN
        v_index := v_index + 1;
        v_emp_changes(v_index).upd_dt       := SYSDATE;
        v_emp_changes(v_index).upd_by       := SYS_CONTEXT ('USERENV', 'SESSION_USER');
        v_emp_changes(v_index).emp_id       := :NEW.emp_id;
        v_emp_changes(v_index).action       := 'Create';
        v_emp_changes(v_index).field        := '*';
        v_emp_changes(v_index).from_value   := 'NULL';
        v_emp_changes(v_index).to_value     := '*';

    ELSIF UPDATING THEN
        IF (   (:OLD.EMP_ID <> :NEW.EMP_ID)
                OR (:OLD.EMP_ID IS     NULL AND :NEW.EMP_ID IS NOT NULL)
                OR (:OLD.EMP_ID IS NOT NULL AND :NEW.EMP_ID IS     NULL)
                  )
             THEN
                v_index := v_index + 1;
                v_emp_changes(v_index).upd_dt       := SYSDATE;
                v_emp_changes(v_index).upd_by       := SYS_CONTEXT ('USERENV', 'SESSION_USER');
                v_emp_changes(v_index).emp_id       := :NEW.emp_id;
                v_emp_changes(v_index).field        := 'EMP_ID';
                v_emp_changes(v_index).from_value   := to_char(:OLD.EMP_ID);
                v_emp_changes(v_index).to_value     := to_char(:NEW.EMP_ID);
                v_emp_changes(v_index).action       := 'Update';
          END IF;
        
        IF (   (:OLD.NAME <> :NEW.NAME)
                OR (:OLD.NAME IS     NULL AND :NEW.NAME IS NOT NULL)
                OR (:OLD.NAME IS NOT NULL AND :NEW.NAME IS     NULL)
                  )
             THEN
                v_index := v_index + 1;
                v_emp_changes(v_index).upd_dt       := SYSDATE;
                v_emp_changes(v_index).upd_by       := SYS_CONTEXT ('USERENV', 'SESSION_USER');
                v_emp_changes(v_index).emp_id       := :NEW.emp_id;
                v_emp_changes(v_index).field        := 'NAME';
                v_emp_changes(v_index).from_value   := to_char(:OLD.NAME);
                v_emp_changes(v_index).to_value     := to_char(:NEW.NAME);
                v_emp_changes(v_index).action       := 'Update';
          END IF;
                       
        IF (   (:OLD.SALARY <> :NEW.SALARY)
                OR (:OLD.SALARY IS     NULL AND :NEW.SALARY IS NOT NULL)
                OR (:OLD.SALARY IS NOT NULL AND :NEW.SALARY IS     NULL)
                  )
             THEN
                v_index := v_index + 1;
                v_emp_changes(v_index).upd_dt      := SYSDATE;
                v_emp_changes(v_index).upd_by      := SYS_CONTEXT ('USERENV', 'SESSION_USER');
                v_emp_changes(v_index).emp_id      := :NEW.emp_id;
                v_emp_changes(v_index).field       := 'SALARY';
                v_emp_changes(v_index).from_value  := to_char(:OLD.SALARY);
                v_emp_changes(v_index).to_value    := to_char(:NEW.SALARY);
                v_emp_changes(v_index).action      := 'Update';
          END IF;
                       
    END IF;

    IF v_index >= v_threshhold THEN
      flush_logs();
    END IF;

   END AFTER EACH ROW;

  -- AFTER STATEMENT Section:
  AFTER STATEMENT IS
  BEGIN
     flush_logs();
  END AFTER STATEMENT;

END aud_emp;
/

INSERT INTO employees VALUES (1, 'emp1', 10000);
INSERT INTO employees VALUES (2, 'emp2', 20000);
INSERT INTO employees VALUES (3, 'emp3', 16000);

UPDATE employees 
   SET salary = 2000
 WHERE salary > 15000;

SELECT * FROM aud_emp;

Result:

EMP_ID,UPD_BY,UPD_DT,ACTION,FIELD,FROM_VALUE,TO_VALUE
1,Aditya,1/22/2014 10:59:33 AM,Create,*,NULL,*
2,Aditya,1/22/2014 10:59:34 AM,Create,*,NULL,*
3,Aditya,1/22/2014 10:59:35 AM,Create,*,NULL,*
2,Aditya,1/22/2014 10:59:42 AM,Update,SALARY,20000,2000
3,Aditya,1/22/2014 10:59:42 AM,Update,SALARY,16000,2000

Now any changes in any field of employees will to be written in aud_emp table. A beauty of this approach is we were able to access same data ‘v_emp_changes’ between statement and row triggering events.

With this in mind, one can see that it make sense to move v_emp_changes(v_index).upd_by := SYS_CONTEXT ('USERENV', 'SESSION_USER'); inside declarative(or BEFORE STATEMENT if complex computation) section as a pre-processing step. To do so, v_user variable declared in trigger body can be used and assigned value of logged in user in the declarative section itself. So that same computation is not made during after-each-row section, and is computed and stored in a variable just once before row-level execution begins.

--declarative section
v_user          VARCHAR2(50) := SYS_CONTEXT ('USERENV', 'SESSION_USER');

Similarly any such pre-processing if required can be performed on that source table (mutating table), doing so will avoid any possible mutating-error. For e.g., consider the same example with another restriction, “Any update of salary should be such that it is not less than 1/12th of maximum salary of any employee, or else an error is raised”. To do this, it will be needed to get the maximum value of salary in the ‘employees’ table, and such calculation can be made in BEFORE STATEMENT section and stored in variable.

Hope that helped to have a better understanding of Compound Triggers in Oracle.
The End :-)

The post Compound Triggers in Oracle 11g – Tutorial with example appeared first on ViralPatel.net.

Auditing DML changes in Oracle

$
0
0

auditing sql in oracleWe are often faced with a situation when every DML change (Inserts/Updates/Deletes) made in Oracle/SQL tables must be audited. Banking Softwares and other similar applications have a strict requirement to maintain the audit trail of every single change made to the database.

The DML changes must be audited irrespective of whether it was made from the Front End, during a release, or directly by a production support person while serving a production ticket. Ever wondered how an audit trail of such large numbers tables in your database can be created. Especially when your application is ever-changing with new columns getting added, dropped or modified often.

Triggers in oracle often come handy when fulfilling audit requirements for your database. An audit trigger can be created on the table which will compare the old and new values of all the columns and in case of a difference will log the old record into an audit table. The audit table will have a similar structure to the main table with 3 additional columns AUDIT_BY, AUDIT_AT and AUDIT_ACTION.

Triggers will ensure that the audit trail is maintained irrespective of from where the database change was initiated. However creating such large number of audit tables and triggers manually can be a huge effort. In this article I will demonstrate how easily we can create audit tables and triggers in oracle for database of any size very easily and with very less effort.

Step 1 – Create some tables

Create some sample tables for which you would like to maintain the audit trail.

CREATE TABLE EMPLOYEE
(
   EID     NUMBER,
   ENAME   VARCHAR2 (40)
);

CREATE TABLE DEPARTMENT
(
   DID     NUMBER,
   DNAME   VARCHAR2 (40)
);

CREATE TABLE SALARY
(
   EID   	NUMBER,
   SALARY	NUMBER
);

Step 2 – Create an exclude table

There will be always some tables which we would like to exclude from the audit. For example if the table is very huge, contains blob or images, or if the table is rarely modified we might not want to audit it. The exclude table will contain a list of such table which we would like to exclude from the audit.

CREATE TABLE EXAUDIT
(
   TNAME VARCHAR2 (30) NOT NULL
);

In our example let us assume that we want to exclude the department table from the audit. We simply make an entry of this table in our exclude table.

INSERT INTO EXAUDIT (TNAME)
     VALUES ('DEPARTMENT');

Step 3 – Create audit tables

Now comes the interesting part. We want to create audit tables that will hold the audit trail of all the tables in our database. This can be achieved with a simple procedure like below.

CREATE OR REPLACE PROCEDURE create_audit_tables (table_owner VARCHAR2)
IS
   CURSOR c_tables (
      table_owner VARCHAR2)
   IS
SELECT ot.owner AS owner, ot.table_name AS table_name
        FROM all_tables ot
       WHERE     ot.owner = table_owner
             AND ot.table_name NOT LIKE 'AUDIT_%'
             AND ot.table_name <> 'EXAUDIT'
             AND NOT EXISTS
                    (SELECT 1
                       FROM EXAUDIT efa
                      WHERE ot.table_name = efa.tname)
             AND NOT EXISTS
                        (SELECT 1
                           FROM all_tables at
                          WHERE at.table_name = 'AUDIT_'||ot.table_name);

   v_sql     VARCHAR2 (8000);
   v_count   NUMBER := 0;
   v_aud     VARCHAR2 (30);
BEGIN
   FOR r_table IN c_tables (table_owner)
   LOOP
      BEGIN
         v_aud := 'AUDIT_'||r_table.table_name;
         v_sql :=
               'create table '
            || v_aud
            || ' as select * from '
            || r_table.owner
            || '.'
            || r_table.table_name
            || ' where 0 = 1';

         DBMS_OUTPUT.put_line ('Info: ' || v_sql);

         EXECUTE IMMEDIATE v_sql;

         v_sql :=
               'alter table '
            || v_aud
            || ' add ( AUDIT_ACTION char(1), AUDIT_BY varchar2(50), AUDIT_AT TIMESTAMP)';

         EXECUTE IMMEDIATE v_sql;

         v_count := c_tables%ROWCOUNT;
      EXCEPTION
         WHEN OTHERS
         THEN
            DBMS_OUTPUT.put_line (
                  'Failed to create table '
               || v_aud
               || ' due to '
               || SQLERRM);
      END;
   END LOOP;

   IF v_count = 0
   THEN
      DBMS_OUTPUT.put_line ('No audit tables created');
   ELSE
      DBMS_OUTPUT.put_line (v_count || ' audit tables created.');
   END IF;
END;
/

After the above procedure is created execute it by passing the schema name (owner) of the schema where your main tables were created.

execute create_audit_tables('SCHEMANAME');

This will create audit tables corresponding to all main tables and with the additional columns like audit_on,audit_by and audit_action. The tables in the exclude table will be excluded.

Step 4 – Create audit triggers

I will first create a small helper function that will give me a comma separated list of columns of a given table (with a prefix if required)

create or replace FUNCTION get_columns_for_table (
     table_owner   VARCHAR2,
     t_name   VARCHAR2,
     prefix  VARCHAR2
  ) RETURN  CLOB
  IS
     v_text CLOB;
  BEGIN
     FOR getrec IN (SELECT column_name
                      FROM all_tab_columns
                     WHERE table_name = t_name
        AND owner = table_owner
        AND data_type<>'BLOB')
     LOOP
       v_text := v_text
          || ','
          || prefix
          || getrec.column_name
          || CHR (10)
          || '                             ';
     END LOOP;

     RETURN ltrim(v_text,',');
  END;

Next create a helper function that will give us a comparison between the columns in case of table updates

create or replace function get_column_comparasion (
     table_owner   VARCHAR2,
     t_name   VARCHAR2
  ) RETURN CLOB
  IS
    v_text CLOB;
  BEGIN
    FOR getrec IN (SELECT column_name
                     FROM all_tab_columns
                    WHERE table_name = t_name
                      AND owner = table_owner
                      AND data_type<>'BLOB')
   LOOP
      v_text := v_text
         || ' or( (:old.'
         || getrec.column_name
         || ' <> :new.'
         || getrec.column_name
         || ') or (:old.'
         || getrec.column_name
         || ' IS NULL and  :new.'
         || getrec.column_name
         || ' IS NOT NULL)  or (:old.'
         || getrec.column_name
         || ' IS NOT NULL and  :new.'
         || getrec.column_name
         || ' IS NULL))'
         || CHR (10)
         || '                ';
   END LOOP;

   v_text := LTRIM (v_text, ' or');
   RETURN v_text;
  END;

Next create the procedure that will create our audit triggers

CREATE OR REPLACE PROCEDURE create_audit_triggers (table_owner VARCHAR2)
IS
   CURSOR c_tab_inc (
      table_owner VARCHAR2)
   IS
      SELECT ot.owner AS owner, ot.table_name AS table_name
        FROM all_tables ot
       WHERE     ot.owner = table_owner
             AND ot.table_name NOT LIKE 'AUDIT_%'
             AND ot.table_name <> 'EXAUDIT'
             AND ot.table_name NOT IN (SELECT tname FROM EXAUDIT);

   v_query   VARCHAR2 (32767);
   v_count   NUMBER := 0;
BEGIN
   FOR r_tab_inc IN c_tab_inc (table_owner)
   LOOP
      BEGIN

         v_query :=
               'CREATE OR REPLACE TRIGGER TRIGGER_'
            || r_tab_inc.table_name
            || ' AFTER INSERT OR UPDATE OR DELETE ON '
            || r_tab_inc.owner
            || '.'
            || r_tab_inc.table_name
            || ' FOR EACH ROW'
            || CHR (10)
            || 'DECLARE '
            || CHR (10)
            || ' v_user varchar2(30):=null;'
            || CHR (10)
            || ' v_action varchar2(15);'
            || CHR (10)
            || 'BEGIN'
            || CHR (10)
            || '   SELECT SYS_CONTEXT (''USERENV'', ''session_user'') session_user'
            || CHR (10)
            || '   INTO v_user'
            || CHR (10)
            || '   FROM DUAL;'
            || CHR (10)
            || ' if inserting then '
            || CHR (10)
            || ' v_action:=''INSERT'';'
            || CHR (10)
            || '      insert into AUDIT_'
            || r_tab_inc.table_name
            || '('
            || get_columns_for_table (r_tab_inc.owner,
                                      r_tab_inc.table_name,
                                      NULL)
            || '      ,AUDIT_ACTION,AUDIT_BY,AUDIT_AT)'
            || CHR (10)
            || '      values ('
            || get_columns_for_table (r_tab_inc.owner,
                                      r_tab_inc.table_name,
                                      ':new.')
            || '      ,''I'',v_user,SYSDATE);'
            || CHR (10)
            || ' elsif updating then '
            || CHR (10)
            || ' v_action:=''UPDATE'';'
            || CHR (10)
            || '   if '
            || get_column_comparasion (r_tab_inc.owner, r_tab_inc.table_name)
            || ' then '
            || CHR (10)
            || '      insert into AUDIT_'
            || r_tab_inc.table_name
            || '('
            || get_columns_for_table (r_tab_inc.owner,
                                      r_tab_inc.table_name,
                                      NULL)
            || '      ,AUDIT_ACTION,AUDIT_BY,AUDIT_AT)'
            || CHR (10)
            || '      values ('
            || get_columns_for_table (r_tab_inc.owner,
                                      r_tab_inc.table_name,
                                      ':new.')
            || '      ,''U'',v_user,SYSDATE);'
            || CHR (10)
            || '   end if;'
            || ' elsif deleting then'
            || CHR (10)
            || ' v_action:=''DELETING'';'
            || CHR (10)
            || '      insert into AUDIT_'
            || r_tab_inc.table_name
            || '('
            || get_columns_for_table (r_tab_inc.owner,
                                      r_tab_inc.table_name,
                                      NULL)
            || '      ,AUDIT_ACTION,AUDIT_BY,AUDIT_AT)'
            || CHR (10)
            || '      values ('
            || get_columns_for_table (r_tab_inc.owner,
                                      r_tab_inc.table_name,
                                      ':old.')
            || '      ,''D'',v_user,SYSDATE);'
            || CHR (10)
            || '   end if;'
            || CHR (10)
            || 'END;';

         DBMS_OUTPUT.put_line (
               'CREATE TRIGGER '
            || REPLACE (r_tab_inc.table_name, 'TABLE_', 'TRIGGER_'));

         EXECUTE IMMEDIATE v_query;

         DBMS_OUTPUT.put_line (
               'Audit trigger '
            || REPLACE (r_tab_inc.table_name, 'TABLE_', 'TRIGGER_')
            || ' created.');

         v_count := c_tab_inc%ROWCOUNT;
      EXCEPTION
         WHEN OTHERS
         THEN
            DBMS_OUTPUT.put_line (
                  'Failed to create audit trigger for '
               || r_tab_inc.owner
               || '.'
               || r_tab_inc.table_name
               || ' due to '
               || SQLERRM);
      END;
   END LOOP;

   IF v_count = 0
   THEN
      DBMS_OUTPUT.put_line ('No audit triggers created');
   END IF;
END;

Finally execute the procedure. This will create all the audit triggers.

EXECUTE CREATE_AUDIT_TRIGGERS('SCHEMANAME');

Step 5 – Test the auditing

Now execute a few DML scripts and notice that all changes made to our main tables get audited with appropriate action in the audit tables. Changes to department table will not be audited as we have excluded it.

insert into employee values(1,'John');

insert into employee values(2,'Smith');

insert into department values(1,'Sales');

insert into department values(2,'Purchase');

insert into salary values(1,5000);

insert into salary values(2,10000);

delete from employee where eid = 1;

update employee set ename = 'Raj' where eid = 2;

All tables will have a primary key which never changes. Using the primary key we can query our audit tables and get the entire audit trail when required. Instead of session user we can also set the user from the middle tier in the SYS_CONTEXT.

Here I demonstrated how with few simple procedures you can fulfil the audit requirement of your application. The concepts and scripts here are very small but quite powerful and can be used to create audit trail for any number of tables in your database.

The post Auditing DML changes in Oracle appeared first on ViralPatel.net.


Index usage with LIKE operator in Oracle & Domain Indexes

$
0
0
A lot of developers might be confused about index selectivity while using %LIKE% operator. So please find below how index worked when you use LIKE operator.

Problem Statement

  • While optimizing high CPU consuming queries on 3rd party application, verified that most of the queries are using '%LIKE%' operator.
  • Interestingly enough, while some of these queries are going for "INDEX RANGE" while others are going for "FULL TABLE SCAN" ??

Brief about LIKE operator

  • 'LIKE' Determines whether a specific character string matches a specified pattern.
  • % allows you to match any string of any length (including zero length)
Before starting this you must know that :
  • Only the part before the first wildcard serves as access predicate.
  • The remaining characters do not narrow the scanned index range they just discard non-matching results.
We can use LIKE operator in 4 ways in our queries:
  1. SEARCH-STRING%
  2. %SEARCH-STRING
  3. %SEARCH-STRING%
  4. SEARCH%STRING

1. SEARCH-STRING%

The SEARCH-STRING% will perform INDEX RANGE SCAN data in least possible time.
set autotrace traceonly;
select * from sac where object_type like 'TAB%';

Execution Plan
----------------------------------------------------------
   0       SELECT STATEMENT Optimizer Mode=ALL_ROWS (Cost=85 Card=3 K Bytes=543 K)
   1    0    TABLE ACCESS BY INDEX ROWID EMPL101.SAC (Cost=85 Card=3 K Bytes=543 K)
   2    1      INDEX RANGE SCAN EMPL101.SAC_INDX (Cost=11 Card=3 K)
Here the optimizer knows ,where the string gets started (means it know the predicate),so It used Index Range Scan .

2. %SEARCH-STRING

When using %SEARCH-STRING it’s access the FULL table.
set autotrace traceonly;
select * from sac where object_type like '%TAB';

Execution Plan
----------------------------------------------------------
   0       SELECT STATEMENT Optimizer Mode=ALL_ROWS (Cost=398 Card=16 Bytes=2 K)
   1    0    TABLE ACCESS FULL EMPL101.SAC (Cost=398 Card=16 Bytes=2 K)
The opposite case is also possible: a LIKE expression that starts with a wildcard. Such a LIKE expression cannot serve as access predicate. The database has to scan the entire table, if the where clause does not provide another access path.

3. %SEARCH-STRING%

When using %SEARCH-STRING% it’s access the FULL table.
set autotrace traceonly;
select * from sac where object_type like '%TAB%';

Execution Plan
----------------------------------------------------------
   0       SELECT STATEMENT Optimizer Mode=ALL_ROWS (Cost=398 Card=3 K Bytes=543 K)
   1    0    TABLE ACCESS FULL EMPL101.SAC (Cost=398 Card=3 K Bytes=543 K)
Here also the optimizer doesn’t know from which letter the String get started ,so it will scan the whole table.

4. SEARCH%STRING

The SEARCH%STRING will perform INDEX RANGE SCAN and generate an initial result set, containing the values that match first string i.e. SEARCH%. Next it will scan through the values to get second string i.e. %STRING
set autotrace traceonly;
select * from sac where object_type like 'TA%BLE';

Execution Plan
----------------------------------------------------------
   0       SELECT STATEMENT Optimizer Mode=ALL_ROWS (Cost=85 Card=3 K Bytes=543 K)
   1    0    TABLE ACCESS BY INDEX ROWID EMPL101.SAC (Cost=85 Card=3 K Bytes=543 K)
   2    1      INDEX RANGE SCAN EMPL101.SAC_INDX (Cost=11 Card=3 K)
Lets see the chart below for various search using wildcard character: The more selective the part before the first wildcard is, the smaller the scanned index range becomes. That, in turn, makes the index lookup more efficient. oracle-like-query-result
  • The first expression has two characters before the wildcard.
  • They limits the scanned index range to 18 rows. Only one of them matches the entire LIKE expression—the other 17 are discarded.
  • The second expression has a longer prefix, which narrows the scanned index range to two rows.
  • With this expression, the database just reads one extra row that is not relevant for the result.
  • The last expression does not have a filter predicate at all.
  • The database just reads the entry that matches the entire LIKE expression.
Now, this is how our normal LIKE operator works, but what happen when you want to use index in 2nd and 3rd case of the example above. This is when Oracle*Text Utility comes in picture :)

Oracle*Text utility

oracle-text-utilityThe Oracle*Text utility (formally called Oracle ConText and Oracle Intermedia) allows us to parse through a large text column and index on the words within the column. Unlike ordinary b-tree or bitmap indexes, Oracle context, ctxcat and ctxrule indexes can be set not to update as content is changed. Since most standard Oracle databases will use the ctxcat index with standard relational tables, you must decide on a refresh interval. Oracle provides the SYNC operator for this. The default is SY^NC=MANUAL and you must manually synchronize the index with CTX_DDL.SYNC_INDEX.
SYNC (MANUAL | EVERY "interval-string" | ON COMMIT)
Hence, Oracle Text indexes are only useful for removing full-table scans when the tables are largely read-only and/or the end-users don’t mind not having 100% search recall:
  • The target table is relatively static (e.g. nightly batch updates)
  • Your end-users would not mind “missing” the latest row data
Lets take up this with an example:
SQL> CREATE TABLE sac AS SELECT * FROM all_objects;
   
   Table created.


SQL> CREATE INDEX sac_indx ON sac(object_type);
   
   Index created.


SQL> set autotrace trace explain

SQL> select * from sac where object_type LIKE 'TAB%';

        Execution Plan
     ----------------------------------------------------------
     0 SELECT STATEMENT Optimizer=ALL_ROWS (Cost=1 Card=1 Bytes=128
     )

     1 0 TABLE ACCESS (BY INDEX ROWID) OF 'SAC' (TABLE) (Cost=1 Car
     d=1 Bytes=128)

     2 1 INDEX (RANGE SCAN) OF 'SAC_INDX' (INDEX) (Cost=1 Card=1)
Above example shows that using % wild card character towards end probe an Index search. But if it is used towards start, it will not be used. And sensibly so, because Oracle doesn’t know which data to search, it can start from ‘A to Z’ or ‘a to z’ or even 1 to any number. See this.
SQL> SELECT *
     FROM sac
     WHERE object_type LIKE '%ABLE';


	Execution Plan
	----------------------------------------------------------
	0 SELECT STATEMENT Optimizer=ALL_ROWS (Cost=148 Card=1004 Byte
	s=128512)

	1 0 TABLE ACCESS (FULL) OF 'SAC' (TABLE) (Cost=148 Card=1004 B
	ytes=128512)

Now how to use the index if you are using Like operator searches. The answer is Domain Indexes.


SQL> connect / as sysdba
Connected.

SQL> grant execute on ctx_ddl to public;
Grant succeeded.


SQL> connect sac;
Connected.

SQL> begin
2 ctx_ddl.create_preference('SUBSTRING_PREF',
3 'BASIC_WORDLIST');
4 ctx_ddl.set_attribute('SUBSTRING_PREF',
5 'SUBSTRING_INDEX','TRUE');
6 end;
7
8 /

PL/SQL procedure successfully completed.
  • ctx_ddl.create_preference: Creates a preference in the Text data dictionary.
  • ctx_ddl.set_attribute : Sets a preference attribute. Use this procedure after you have created a preference with CTX_DDL.CREATE_PREFERENCE.
SQL> drop index sac_indx;
Index dropped.

SQL> create index sac_indx on sac(object_type) indextype is ctxsys.context parameters ('wordlist SUBSTRING_PREF memory 50m');
Index created.

SQL> set autotrace trace exp
SQL> select * from sac where contains (OBJECT_TYPE,'%PACK%') > 0

	Execution Plan
 	----------------------------------------------------------
	0 SELECT STATEMENT Optimizer=ALL_ROWS (Cost=8 Card=19 Bytes=17
	86)
	1 0 TABLE ACCESS (BY INDEX ROWID) OF 'SAC' (TABLE) (Cost=8 Car
	d=19 Bytes=1786)
	2 1 DOMAIN INDEX OF 'SAC_INDX' (INDEX (DOMAIN)) (Cost=4)
In this case the index is getting used.

Index re-synchronization

Because rebuilding an Oracle Text index (context, ctxcat, ctxrule) requires a full-table scan and lots of internal parsing, it is not practical to use triggers for instantaneous index updates.  Updating Oracle Text indexes is easy and they can be schedules using dbms_job or the Oracle 10g dbms_scheduler utility package:  Oracle text provides a CTX_DDL package with the sync_index and optimize_index procedures:
SQL> EXEC CTX_DDL.SYNC_INDEX('sac_indx');
SQL> EXEC CTX_DDL.OPTIMIZE_INDEX('sac_indx','FULL');
For example, if you create a nightly dbms_scheduler job to call sync_index, your index will be refreshed, but the structure will become sub-optimal over time.  Oracle recommends that you periodically use the optimize_index package to periodically re-build the whole index from scratch.  Index optimization can be performed in three modes (FAST, FULL or TOKEN). In sum, the Oracle Text indexes are great for removing unnecessary full-table scans from static Oracle tables and they can reduce I/O by several orders of magnitude, greatly improving overall SQL performance

Conclusion

For proximity, soundex and fuzzy searches, use domain indexes.

References

Oracle Documentation

Pagination in Oracle using ROWNUM and Limiting Result Set

$
0
0
paginationROWNUM is a magic column in Oracle Database that gets many people into trouble. When you learn what it is and how it works, however, it can be very useful. I use it for two main things:
  • To perform top- N processing. This is similar to using the LIMIT clause, available in some other databases.
  • To paginate through a query, typically in a stateless environment such as the Web. We can use this.

How ROWNUM Works?

ROWNUM is a pseudocolumn (not a real column) that is available in a query. ROWNUM will be assigned the numbers 1, 2, 3, 4, … N , where N is the number of rows in the set ROWNUM is used with. A ROWNUM value is not assigned permanently to a row (this is a common misconception). A row in a table does not have a number; you cannot ask for row 5 from a table—there is no such thing. Also confusing to many people is when a ROWNUM value is actually assigned. A ROWNUM value is assigned to a row after it passes the predicate phase of the query but before the query does any sorting or aggregation. Also, a ROWNUM value is incremented only after it is assigned, which is why the following query will never return a row:
SELECT *
  FROM t
 WHERE ROWNUM > 1;
Because ROWNUM > 1 is not true for the first row, ROWNUM does not advance to 2. Hence, no ROWNUM value ever gets to be greater than 1. Consider a query with this structure:
SELECT ..., ROWNUM
     FROM T
   WHERE <WHERE CLAUSE>
   GROUP BY <COLUMNS>
   HAVING <HAVING CLAUSE>
   ORDER BY <COLUMNS>;
Think of it as being processed in this order:
  1. The FROM/WHERE clause goes first. 
  2. ROWNUM is assigned and incremented to each output row from the FROM/WHERE clause. 
  3. SELECT is applied. 
  4. GROUP BY is applied. 
  5. HAVING is applied. 
  6. ORDER BY is applied.
That is why a query in the following form is almost certainly an error:
SELECT *
    FROM emp
    WHERE ROWNUM <= 5
    ORDER BY sal DESC;
The intention was most likely to get the five highest-paid people—a top- N query. What the query will return is five random records (the first five the query happens to hit), sorted by salary. The procedural pseudocode for this query is as follows:
ROWNUM = 1
FOR x in

(SELECT * FROM emp)
LOOP
    exit when NOT(ROWNUM <= 5)
    OUTPUT record to temp
    ROWNUM = ROWNUM+1
end loop
SORT TEMP
It gets the first five records and then sorts them. A query with WHERE ROWNUM = 5 or WHERE ROWNUM > 5 doesn’t make sense. This is because a ROWNUM value is assigned to a row during the predicate evaluation and gets incremented only after a row passes the WHERE clause. Here is the correct version of this query:
SELECT *
  FROM (SELECT *
            FROM emp
        ORDER BY sal DESC)
 WHERE ROWNUM <= 5;
This version will sort EMP by salary descending and then return the first five records it encounters (the top-five records). As you’ll see in the top- N discussion coming up shortly, Oracle Database doesn’t really sort the entire result set—it is smarter than that—but conceptually that is what takes place. For pagination, if you want the 5 -10 records of the employee order by hiredate asc then go for this.
SELECT outer.*
  FROM (SELECT ROWNUM rn, inner.*
          FROM (  SELECT e.*
                    FROM employee e
                ORDER BY hiredate) inner) outer
 WHERE outer.rn >= 5 AND outer.rn <= 10

References

Oracle ROWNUM Documentation

Index Skip Scan in Oracle

$
0
0
index-skip-scan-oracleWith Oracle 9i, the Cost-Based Optimizer (CBO) is equipped with many useful features, one of them is “Index skip scan“. In previous releases a composite index could only be used if the first column, the leading edge, of the index was referenced in the WHERE clause of a statement. In Oracle 9i this restriction is removed because the optimizer can perform skip scans to retrieve rowids for values that do not use the prefix. This means even if you have a composite index on more than one column and you use the non-prefix column alone in your SQL, it may still use index. (Earlier I use to think that it will not use index :)) Its not always guaranteed that the Index Skip Scan will be used, this is because Cost-Based Optimizer (CBO) will calculate the cost of using the index and if it is more than that of full table scan, then it may not use index. This approach is advantageous because:
  • It reduces the number of indexes needed to support a range of queries. This increases performance by reducing index maintenance and decreases wasted space associated with multiple indexes.
  • The prefix column should be the most discriminating and the most widely used in queries. These two conditions do not always go hand in hand which makes the decision difficult. In these situations skip scanning reduces the impact of making the “wrong” decision.
Index skip scan works differently from a normal index (range) scan. A normal range scan works from top to bottom first and then move horizontal. But a Skip scan includes several range scans in it. Since the query lacks the leading column it will rewrite the query into smaller queries and each doing a range scan. Consider following example where we create a test table and create index on first two columns a and b. Also we put some dummy data inside test table. See how Index is getting selected when we execute select statement with column b in where clause.

Step 1:

CREATE TABLE test (a NUMBER, b NUMBER, c NUMBER);
Table created.

Step 2:

CREATE INDEX test_i
   ON test (a, b);
Index created.

Step 3:

BEGIN
   FOR i IN 1 .. 100000
   LOOP
      INSERT INTO test
          VALUES (MOD (i, 5), i, 100);
   END LOOP;

   COMMIT;
END;
/
PL/SQL procedure successfully completed.

Step 4:

exec dbms_stats.gather_table_stats (
        ownname => 'gauravsoni', 
        tabname => 'test', 
        cascade => true
     );
PL/SQL procedure successfully completed.

Step 5:

set autotrace trace exp

Step 6:

SELECT *
  FROM test
 WHERE b = 95267;

Execution Plan

0
SELECT STATEMENT Optimizer=ALL_ROWS (Cost=22 Card=1 Bytes=10)


1 0
TABLE ACCESS (BY INDEX ROWID) OF 'TEST' (TABLE) (Cost=22 Card=1 Bytes=10)


2 1
INDEX (SKIP SCAN) OF 'TEST_I' (INDEX) (Cost=21 Card=1)
I above example, "select * from test where b=95267" was broken down to several small range scan queries. It was effectively equivalent to following:
SELECT *
  FROM test
 WHERE a = 0 AND b = 95267
UNION
SELECT *
  FROM test
 WHERE a = 1 AND b = 95267
UNION
SELECT *
  FROM test
 WHERE a = 2 AND b = 95267
UNION
SELECT *
  FROM test
 WHERE a = 3 AND b = 95267
UNION
SELECT *
  FROM test
 WHERE a = 4 AND b = 95267;
In concrete, saying that skip scan is not as efficient as normal “single range scan” is correct. But yet saves some disk space and overhead of maintaining another index.

Reference

Oracle Documentation on Index Skip Scan

Oracle Skip Locked

$
0
0
Oracle 11g introduced SKIP LOCKED clause to query the records from the table which are not locked in any other active session of the database. This looks quite similar to exclusive mode of locking. oracle-skip-locked The select for update statement has always been problematic for large updates because it the default is to wait for locks and using select for update other tasks can abort waiting on access with the ORA-300036 error: ORA-30006: resource busy; acquire with WAIT timeout expired In other cases using select for update with the nowait clause you your own update may abort with the ORA-00054 error: ORA-00054 resource busy and NOWAIT specified Even worse, if a select for update task aborts, a zombie process may hold the row locks long term, requiring DBA intervention. To illustrate, we open two sessions. In the first session, we lock the row with deptno as 10 using FOR UPDATE NOWAIT.
SELECT *
  FROM dept  WHERE
 deptno = 10
FOR UPDATE NOWAIT;
Output:
DEPTNO     DNAME          LOC
---------- -------------- -------------
10         ACCOUNTING     NEW YORK
In the second session, we try to lock two rows (deptno 10 and 20) from the table dept using FOR UPDATE NOWAIT. An exception is thrown after executing the following statement because one of the row (i.e. deptno 10) out of the selected list is already locked by session 1.
SELECT * FROM dept
 WHERE deptno IN (10,20)
FOR UPDATE NOWAIT;
Output:
SELECT * FROM dept WHERE deptno IN (10,20)
FOR UPDATE NOWAIT
ERROR at line 1:
ORA-00054: resource busy and acquire with NOWAIT specified
Now we again try to lock two rows (deptno(s) 10 and 20) from the table dept but using the clause FOR UPDATE SKIP LOCKED instead of FOR UPDATE NOWAIT. As you can see the following statement has:
  1. returned the control without throwing an exception
  2. acquired lock on the row (i.e. deptno 20) which is available for locking
  3. skipped the row (i.e. deptno 10) that has been locked already by session 1
SELECT * FROM dept
 WHERE deptno IN (10,20)
FOR UPDATE SKIP LOCKED;
Output:
DEPTNO     DNAME          LOC
---------- -------------- -------------
20         RESEARCH       DALLAS

Java: Passing Array to Oracle Stored Procedure

$
0
0
This tutorial guides us on how to pass Array objects from Java to stored procedures in Oracle and also, how to retrieve an array object in Java. All PLSQL arrays can not be called from java. An array needs to be created as TYPE, at SCHEMA level in the database and then it can be used with ArrayDescriptor in Java, as oracle.sql.ArrayDescriptor class in Java can not access at package level.

Database Code

First, Create an array, at SCHEMA level. An example is shown below:
CREATE TYPE array_table AS TABLE OF VARCHAR2 (50); -- Array of String

CREATE TYPE array_int AS TABLE OF NUMBER;          -- Array of integers
Next, Create a procedure which takes an array as an input parameter and returns an array as its OUT parameter. An example of one such procedure is shown below, which has 2 parameters –
  1. an array of String as its IN parameter – p_array
  2. an array of Integers as OUT parameter – p_arr_int
CREATE OR REPLACE PROCEDURE SchemaName.proc1 (p_array     IN     array_table,
                                              len            OUT NUMBER,
                                              p_arr_int      OUT array_int)
AS
   v_count   NUMBER;
BEGIN
   p_arr_int := NEW array_int ();
   p_arr_int.EXTEND (10);
   len := p_array.COUNT;
   v_count := 0;

   FOR i IN 1 .. p_array.COUNT
   LOOP
      DBMS_OUTPUT.put_line (p_array (i));
      p_arr_int (i) := v_count;
      v_count := v_count + 1;
   END LOOP;
END;
/
After this, Execution permission would be required to execute the procedure created by you:
GRANT EXECUTE ON SchemaNAme.proc1 TO UserName;

Java Code

Create a java class which makes a call to the procedure proc1, created before. Below is an example which contains the whole flow from creating a connection with the database, to making a call to the stored procedure, passing an array to Oracle procedure, retrieving an array from an Oracle procedure and displaying the result.
import java.math.BigDecimal;
import java.sql.CallableStatement;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.Types;
import oracle.jdbc.OracleCallableStatement;
import oracle.jdbc.internal.OracleTypes;
import oracle.sql.ARRAY;
import oracle.sql.ArrayDescriptor; 

public class TestDatabase {
	
	public static void passArray()
	{
		try{
		
			Class.forName("oracle.jdbc.OracleDriver");			
			Connection con = DriverManager.getConnection("jdbc:oracle:thin:url ","UserName","Password");;
			
			String array[] = {"one", "two", "three","four"}; 
			
			ArrayDescriptor des = ArrayDescriptor.createDescriptor("SchemaName.ARRAY_TABLE", con);
			ARRAY array_to_pass = new ARRAY(des,con,array);
			
			CallableStatement st = con.prepareCall("call SchemaName.proc1(?,?,?)");

			// Passing an array to the procedure - 
			st.setArray(1, array_to_pass);

			st.registerOutParameter(2, Types.INTEGER);
			st.registerOutParameter(3,OracleTypes.ARRAY,"SchemaName.ARRAY_INT");
			st.execute();
			
			System.out.println("size : "+st.getInt(2));

			// Retrieving array from the resultset of the procedure after execution -
			ARRAY arr = ((OracleCallableStatement)st).getARRAY(3);
			 BigDecimal[] recievedArray = (BigDecimal[])(arr.getArray());

			for(int i=0;i<recievedArray.length;i++)
				System.out.println("element" + i + ":" + recievedArray[i] + "\n");
			
		} catch(Exception e) {
			System.out.println(e);
		}
	}

	public static void main(String args[]){
		passArray();
	}
}

Brief Explanations:

  1. Class.forName() – Returns the Class object associated with the class or interface with the given string name.
  2. DriverManager.getConnection() – Attempts to establish a connection to the given database URL.
  3. oracle.sql.ArrayDescriptor – Describes an array class
  4. ArrayDescriptor.createDescriptor() – Descriptor factory. Lookup the name in the database, and determine the characteristics of this array.
  5. oracle.sql.ARRAY – An Oracle implementation for generic JDBC Array interface.
  6. CallableStatement – The interface used to execute SQL stored procedures.

References

Viewing all 25 articles
Browse latest View live