Workarounds for Oracle Restrictions – DZone – Uplaza

When growing an enterprise system — whether or not it’s a utterly new system or just the addition of a brand new function — it’s not unusual to wish to retrieve a major quantity of information (a couple of hundred and even 1000’s) from an underlying relational database.  

A ordinary state of affairs is having an inventory of identifiers (person IDs, for instance) and needing to retrieve a set of knowledge from every of them. Disregarding all the convenience supplied by Object-Relational Mappers, amongst which Hibernate will be underlined as a really dependable choice, a simple method to handle such a scenario is to construct a SQL SELECT question and bind all identifiers as a comma-delimited listing of expressions together with a SQL IN operator. The snippet under reveals the question construction.

SELECT column1, column2, ..., columnM 
FROM desk 
WHERE identifier IN (id1, id2, id3, ..., idN)

Some database administration programs (DBMS), corresponding to PostgreSQL, don’t implement or outline by default any restrictions on executing queries like that. Oracle, nonetheless, doesn’t observe this coverage. Quite the opposite, relying on the variety of components that make up the comma-delimited expression listing used with the SQL IN operator or the variety of information retrieved by the question, two corresponding errors could also be triggered by the DBMS:

As indicated by the ORA-01795 error message, a comma-delimited listing of expressions can include not more than 1000 expressions. Then, a SQL question like described above can have as much as 1,000 expressions with the IN operator. 

This higher certain might fluctuate relying on the Oracle model. For instance, the restrict of 1,000 expressions is current as much as the Oracle 21c model. As of the 23ai model, this worth has been elevated to 65,535. 

All assessments and implementations on this article are based mostly on the Oracle 19c model.

As talked about in ORA-00913 documentation, this error sometimes happens in two eventualities: 

  • When a subquery in a WHERE or HAVING clause returns too many columns
  • When a VALUES or SELECT clause returns extra columns than are listed within the INSERT

Nevertheless, the ORA-00913 error additionally seems when SELECT queries include greater than 65,535 hard-coded values. Within the context of comma-delimited expressions, though the documentation states that “a comma-delimited list of sets of expressions can contain any number of sets, but each set can contain no more than 1000 expressions,” such an higher certain is empirically verified.

Provided that the ORA-01795 and the ORA-00913 errors might represent hindrances for programs needing to fetch extra information than the bounds outlined by Oracle, this text presents 5 methods to work round these restrictions.  

As all methods depend on exhausting coding SQL queries as Java Strings, two caveats about this method are value noting.

  • Caveat 1: Chris Saxon, an Oracle Developer Advocate and energetic member of The Oracle Mentors (or AskTOM, for brief), suggested:

Don’t dyamically create a press release with exhausting wired IN listing values or OR’ed expressions. [Oracle database]’ll spend extra time parsing queries than really executing them.

  • Caveat 2: In line with Billy Verreynne, an Oracle APEX professional and Oracle ACE, this method:

impacts on the quantity of reminiscence utilized by the Shared Pool, contributes to Shared Pool fragmentation, will increase exhausting parsing, burns a load of CPU cycles, and degrades efficiency.

QSplitter

Earlier than diving into every technique, you will need to introduce a software that shall be utilized in virtually all of them. It has been known as QSplitter: a Java class chargeable for splitting a set into subcollections whose most dimension can also be handed as a parameter.  The splitCollection technique returns an inventory wherein every factor is a subcollection extracted from the unique assortment. Its implementation is introduced under.

public class QSplitter {
  
  	public Listing> splitCollection(Assortment assortment, int maxCollectionSize) {
		
		var collectionArray = assortment.toArray();
		var splitCollection = new ArrayList>();
		
		var partitions = (assortment.dimension() / maxCollectionSize)
				+ (assortment.dimension() % maxCollectionSize == 0 ? 0 : 1);
		
		Spliterator[] spliterators = new Spliterator[partitions];
		
		IntStream.vary(0, partitions).forEach(n -> {
			var fromIndex = n * maxCollectionSize;
			var toIndex = fromIndex + maxCollectionSize > assortment.dimension()
					? fromIndex + assortment.dimension() % maxCollectionSize 
              					: fromIndex + maxCollectionSize;
			spliterators[n] = Spliterators
				.spliterator(collectionArray, fromIndex, toIndex, Spliterator.SIZED);
			splitCollection.add(new ArrayList());
		});
		
		IntStream.vary(0, partitions)
			.forEach(n -> spliterators[n].forEachRemaining(splitCollection.get(n)::add));
		
		return splitCollection;
	}
}

One other factor to explain is the database used for testing. Just one desk was created and the SQL code used is proven under. 

CREATE TABLE worker (
    ID NUMBER GENERATED ALWAYS as IDENTITY(START with 1 INCREMENT by 1),
    NAME VARCHAR2(500),
    EMAIL VARCHAR2(500),
    STREET_NAME VARCHAR2(500),
    CITY VARCHAR2(500),
    COUNTRY VARCHAR2(500));
    
CREATE UNIQUE INDEX PK_EMPLOYEE ON EMPLOYEE ("ID");

1. N Question Technique

The primary technique is the only: cut up the gathering of IDs into subcollections, contemplating Oracle’s higher restrict (L = 1,000) for the comma-delimited listing of expressions. Every subcollection is then used to construct a question that fetches the corresponding subset of all desired information.  The subsequent script illustrates the generic queries generated for a set of three,000 IDs. 

  • Word: This technique implies executing not less than N = assortment dimension / 1,000 queries.
SELECT column1, column2, ..., columnM FROM desk WHERE id IN (id11, id12, id13, ..., id1L);
SELECT column1, column2, ..., columnM FROM desk WHERE id IN (id21, id22, id23, ..., id2L);
SELECT column1, column2, ..., columnM FROM desk WHERE id IN (id31, id32, id33, ..., id3L);

To implement all methods, two lessons have been carried out: UserService and UserDao. The service makes use of QSplitter to separate the unique assortment after which cross every generated subcollection to the DAO. It builds and executes every respective question. The implementation is introduced under. To maintain the deal with the technique particulars, some strategies have been hidden: getConnection for getting an Oracle java.sql.Connection and resultSetToUsers which creates a brand new Person occasion for every file within the java.sql.ResultSet.

public class UserService {
  
  personal last QSplitter qsplitter = new QSplitter();
  personal last UserDao dao = new UserDao();
  
  public Listing findUsersByNQuery(Listing ids) throws SQLException {
      var customers = new ArrayList();
      this.qsplitter.splitCollection(ids).stream().map(this.dao::findByIds).forEach(customers::addAll);
      return customers;
  }
}

public class UserDao {
  
  personal Operate, String> buildSimpleSelectIn = ids -> 
	new StringBuilder()
	  .append("SELECT id, name, email, street_name, city, country FROM employee WHERE id IN (")
	  .append(ids.stream().map(Object::toString).gather(Collectors.becoming a member of(",")))
	  .append(")").toString();
  
  public Listing findByIds(Assortment ids) throws SQLException {
		
	strive (var rs = this.getConnection().prepareStatement(buildSimpleSelectIn.apply(ids)).executeQuery()) {
	    var customers = new ArrayList();
	    this.resultSetToUsers(rs, customers);
	    return customers;
	}
  }
}

2. Disjunctions of Expression Lists Technique

The second technique takes benefit of the function said within the Oracle Expression Lists documentation: “A comma-delimited list of sets of expressions can contain any number of sets.” On this case, to keep away from executing the N queries proposed by the earlier method, this technique proposes to construct a question composed of lists of expressions interrelated by the OR operator. The code under describes the brand new question.

  • Word: If the variety of IDs exhausting coded within the question exceeds the higher restrict of 65,535, a second question have to be created to keep away from the ORA-00913 error.
SELECT column1, column2, ..., columnM 
FROM desk 
WHERE id IN (id11, id12, id13, ..., id1N) OR 
      id IN (id21, id22, id23, ..., id2N) OR 
      id IN (id31, id32, id33, ..., id3N);

The QSplitter software should obtain a brand new technique chargeable for grouping the subcollections (cut up from the unique assortment) in response to the utmost variety of components allowed by Oracle.

public class QSplitter {
  
  public static last int MAX_ORACLE_IN_CLAUSE_ELEMENTS = 1000;
  public static last int MAX_ORACLE_RETRIEVE_ELEMENTS = 65535;
  
  public Listing>> splitAndGroupCollection(Assortment assortment) {
    var groupedCollection = new ArrayList>>();
    var splitCollection = this.splitCollection(assortment, MAX_ORACLE_IN_CLAUSE_ELEMENTS);
		
    if (assortment.dimension() ());	
    splitCollection.forEach(partition -> {
      if (groupedCollection.getLast().dimension() * MAX_ORACLE_IN_CLAUSE_ELEMENTS + partition.dimension() 
          > MAX_ORACLE_RETRIEVE_ELEMENTS) {
        groupedCollection.add(new ArrayList());
      } 
      groupedCollection.getLast().add(partition);
    });
		
    return groupedCollection;
  }
}
  

The subsequent code snippet reveals the UserService utilizing the brand new QSplitter technique to separate and group the ID assortment. The UserDao has a java.util.perform.Operate that builds the question utilizing this second technique.

public class UserService {
  public Listing findUsersByDisjunctionsOfExpressionLists(Listing ids) throws SQLException {
    return this.dao.findByDisjunctionsOfExpressionLists(this.qsplitter.splitAndGroupCollection(ids));
  }
}

public class UserDao {
  
  personal Operate>, String> buildSelectDisjunctions = idsList -> 
    new StringBuilder("SELECT id, name, email, street_name, city, country FROM employee WHERE ")
      .append(idsList.stream().map(ids -> new StringBuilder()
                     .append("id IN (").append(ids.stream().map(Object::toString)
                                           .gather(Collectors.becoming a member of(","))).append(")"))
                 .gather(Collectors.becoming a member of(" OR "))).toString();
  
  public Listing findByDisjunctionsOfExpressionLists(Listing>> idsList) throws SQLException {
		
    var customers = new ArrayList();
		
    strive (var conn = this.getConnection()) {
      for (var ids : idsList) {	
        strive (var rs = conn.prepareStatement(buildSelectDisjunctions.apply(ids)).executeQuery()) {
          this.resultSetToUsers(rs, customers);
        } 
      }
    } 
		
    return customers;		
  }
}

3. Multivalued Expression Lists Technique

Another trick to get across the ORA-00913 error is to rewrite the expression listing of IDs into an inventory of multivalued expressions. Any second worth can be utilized as a second column to assemble tuples. This straightforward change will increase the capability of the listing of expressions from 1,000 to 65,535. After all, if the scale of the unique assortment exceeds the restrict of 65,535, one other question have to be created. The construction of the question is described under.

SELECT column1, column2, ..., columnM 
FROM desk 
WHERE (id, 0) IN ((id1, 0), (id2, 0), (id3, 0), ..., (id65535, 0))

Because the Java code is similar to the earlier technique, will probably be omitted right here.

4. Union All Technique

Another choice is to create a sequence of UNION ALL queries with the construction introduced within the first technique.

SELECT column1, column2, ..., columnM FROM desk WHERE id IN (id1, id2, id3, ..., idL)
UNION ALL
SELECT column1, column2, ..., columnM FROM desk WHERE id IN (idL+1, idL+2, idL+3, ..., id2L)
UNION ALL
SELECT column1, column2, ..., columnM FROM desk WHERE id IN (id2L+1, id2L+2, id2L+3, ..., id3L)

The implementation can reuse the code from the buildSimpleSelectIn perform created for the primary technique. On this case, all generated SELECTs are joined by UNION ALL operators. It’s value noting that there isn’t a restriction on the variety of expression lists. So just one question is executed. The code is introduced under.

public class UserService {
  public Listing findUsersByUnionAll(Listing ids) throws SQLException {
    return this.dao.findByUnionAll(this.qsplitter.splitCollection(ids));
  }
}

public class UserDao {
  
  public Listing findByUnionAll(Listing> idsList) throws SQLException {
		
    var question = idsList.stream().map(buildSimpleSelectIn).gather(Collectors.becoming a member of(" UNION ALL "));
		
    strive (var rs = this.getConnection().prepareStatement(question).executeQuery()) {
        var customers = new ArrayList();
        this.resultSetToUsers(rs, customers);
        return customers;
    } 
  }
}

5. Temp Desk Technique

The final technique includes creating a brief desk into which all IDs ought to be inserted. The values inserted into the desk should exist after the tip of the transaction. To attain this, the ON COMMIT PRESERVE ROWS clause have to be a part of the SQL assertion for creating the desk. The entire command is introduced under.

CREATE GLOBAL TEMPORARY TABLE employee_id (emp_id NUMBER) ON COMMIT PRESERVE ROWS;

The implementation does not use the QSplitter software since there isn’t a want to separate the unique assortment of IDs. On the facet of UserDao, it first inserts all IDs into the temp desk (the JDBC batch API is employed). Subsequent, a question with a JOIN clause for relating all IDs from the temp desk is executed. The code is detailed under.

public class UserService {
  public Listing findUsersByTempTable(Listing ids) throws SQLException {
    return this.dao.findByTemporaryTable(ids);
  }
}

public class UserDao {
  
  public Listing findByTemporaryTable(Listing ids) throws SQLException {
		
    var queryInsertTempTable = "INSERT INTO employee_id (emp_id) VALUES (?)";
    var querySelectUsers = """
        SELECT id, identify, e mail, street_name, metropolis, nation 
        FROM worker JOIN employee_id ON id = emp_id ORDER BY id
        """;
		
    strive (var conn = this.getConnection()) {
      strive (var pstmt = conn.prepareStatement(queryInsertTempTable)) {
        
        for (var id : ids) {
            pstmt.setLong(1, id);
            pstmt.addBatch();
        }
				
        pstmt.executeBatch();
      }
			
      var customers = new ArrayList();
			
      strive (var rs = conn.prepareStatement(querySelectUsers).executeQuery()) {
        this.resultSetToUsers(rs, customers);
      }
			
      return customers;			
    } 
  }
}

Efficiency

The efficiency of the 5 methods was in contrast contemplating collections of IDs ranging in dimension from 1,000 to 100,000. The code was carried out utilizing Oracle JDK 21.0.4 and java.time.* API was used to depend the wall time of every technique. All assessments have been carried out on the Home windows 11 Professional for Workstations working system put in on a machine with an Intel(R) Xeon(R) W-1290 CPU 3.20 GHz and 64 GB of RAM. 

As proven within the graph under, wall time for collections as much as 10,000 IDs was very related for all methods. Nevertheless, from this threshold onwards the efficiency of the N Queries technique degraded significantly in comparison with the others. Such a habits generally is a results of the amount of I/O operations and exterior delays brought on by executing a number of database queries.

By eradicating the curve of the slowest technique from the graph, the habits of essentially the most environment friendly ones will be higher analyzed. Though there’s not an enormous efficiency distinction between them, it’s seen that the Temp Desk technique dominates with decrease occasions in all experiments. Despite the fact that it performs an extra step of inserting all IDs into the short-term desk, this technique doesn’t want to make use of the QSplitter software to generate subcollections and takes benefit of using the JOIN clause to keep away from indicating hard-coded IDs within the SELECT. This tradeoff might have benefited the technique’s efficiency.

Conclusion

The necessity to retrieve a big quantity of information from the Oracle database based mostly on a big listing of identifiers just isn’t an unusual process in the course of the improvement of enterprise programs. This state of affairs can result in Oracle errors ORA-01795 and ORA-00913. How troublesome it’s to work round these errors is dependent upon numerous particularities of the supply code and the system structure. So, the 5 methods introduced represent a toolkit for builders to keep away from these errors and incorporate what most closely fits the system’s wants. The entire supply code is accessible on a GitHub repository.

Share This Article
Leave a comment

Leave a Reply

Your email address will not be published. Required fields are marked *

Exit mobile version