Workarounds for Oracle Restrictions – DZone – Uplaza

When creating 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 standard 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 benefit supplied by Object-Relational Mappers, amongst which Hibernate will be underlined as a really dependable possibility, an easy strategy to deal with such a scenario is to construct a SQL SELECT question and bind all identifiers as a comma-delimited checklist of expressions together with a SQL IN operator. The snippet under exhibits 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 checklist 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 checklist 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 could 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 exams and implementations on this article are primarily based on the Oracle 19c model.

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

  • 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.

On condition that the ORA-01795 and the ORA-00913 errors could 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 strategy are price 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 checklist values or OR’ed expressions. [Oracle database]’ll spend extra time parsing queries than really executing them.

  • Caveat 2: In keeping with Billy Verreynne, an Oracle APEX skilled and Oracle ACE, this strategy:

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 instrument that can be utilized in nearly all of them. It has been known as QSplitter: a Java class liable for splitting a set into subcollections whose most measurement can be handed as a parameter.  The splitCollection technique returns an inventory during which every component is a subcollection extracted from the unique assortment. Its implementation is introduced under.

public class QSplitter {
  
  	public Record> splitCollection(Assortment assortment, int maxCollectionSize) {
		
		var collectionArray = assortment.toArray();
		var splitCollection = new ArrayList>();
		
		var partitions = (assortment.measurement() / maxCollectionSize)
				+ (assortment.measurement() % maxCollectionSize == 0 ? 0 : 1);
		
		Spliterator[] spliterators = new Spliterator[partitions];
		
		IntStream.vary(0, partitions).forEach(n -> {
			var fromIndex = n * maxCollectionSize;
			var toIndex = fromIndex + maxCollectionSize > assortment.measurement()
					? fromIndex + assortment.measurement() % 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 component 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 best: cut up the gathering of IDs into subcollections, contemplating Oracle’s higher restrict (L = 1,000) for the comma-delimited checklist of expressions. Every subcollection is then used to construct a question that fetches the corresponding subset of all desired information.  The following script illustrates the generic queries generated for a set of three,000 IDs. 

  • Word: This technique implies executing not less than N = assortment measurement / 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 had been applied: 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 give attention to the technique particulars, some strategies had been hidden: getConnection for getting an Oracle java.sql.Connection and resultSetToUsers which creates a brand new Consumer occasion for every report within the java.sql.ResultSet.

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

public class UserDao {
  
  non-public 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).acquire(Collectors.becoming a member of(",")))
	  .append(")").toString();
  
  public Record findByIds(Assortment ids) throws SQLException {
		
	attempt (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 acknowledged 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 strategy, 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 should 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 instrument should obtain a brand new technique liable 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 remaining int MAX_ORACLE_IN_CLAUSE_ELEMENTS = 1000;
  public static remaining int MAX_ORACLE_RETRIEVE_ELEMENTS = 65535;
  
  public Record>> splitAndGroupCollection(Assortment assortment) {
    var groupedCollection = new ArrayList>>();
    var splitCollection = this.splitCollection(assortment, MAX_ORACLE_IN_CLAUSE_ELEMENTS);
		
    if (assortment.measurement() ());	
    splitCollection.forEach(partition -> {
      if (groupedCollection.getLast().measurement() * MAX_ORACLE_IN_CLAUSE_ELEMENTS + partition.measurement() 
          > MAX_ORACLE_RETRIEVE_ELEMENTS) {
        groupedCollection.add(new ArrayList());
      } 
      groupedCollection.getLast().add(partition);
    });
		
    return groupedCollection;
  }
}
  

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

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

public class UserDao {
  
  non-public 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)
                                           .acquire(Collectors.becoming a member of(","))).append(")"))
                 .acquire(Collectors.becoming a member of(" OR "))).toString();
  
  public Record findByDisjunctionsOfExpressionLists(Record>> idsList) throws SQLException {
		
    var customers = new ArrayList();
		
    attempt (var conn = this.getConnection()) {
      for (var ids : idsList) {	
        attempt (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 checklist of IDs into an inventory of multivalued expressions. Any second worth can be utilized as a second column to assemble tuples. This easy change will increase the capability of the checklist of expressions from 1,000 to 65,535. After all, if the dimensions of the unique assortment exceeds the restrict of 65,535, one other question should 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))

For the reason that 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 operate created for the primary technique. On this case, all generated SELECTs are joined by UNION ALL operators. It’s price noting that there isn’t any restriction on the variety of expression lists. So just one question is executed. The code is introduced under.

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

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

5. Temp Desk Technique

The final technique entails creating a short lived desk into which all IDs needs to be inserted. The values inserted into the desk should exist after the top of the transaction. To attain this, the ON COMMIT PRESERVE ROWS clause should be a part of the SQL assertion for creating the desk. The whole command is introduced under.

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

The implementation would not use the QSplitter instrument since there isn’t any 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 Record findUsersByTempTable(Record ids) throws SQLException {
    return this.dao.findByTemporaryTable(ids);
  }
}

public class UserDao {
  
  public Record findByTemporaryTable(Record ids) throws SQLException {
		
    var queryInsertTempTable = "INSERT INTO employee_id (emp_id) VALUES (?)";
    var querySelectUsers = """
        SELECT id, title, e mail, street_name, metropolis, nation 
        FROM worker JOIN employee_id ON id = emp_id ORDER BY id
        """;
		
    attempt (var conn = this.getConnection()) {
      attempt (var pstmt = conn.prepareStatement(queryInsertTempTable)) {
        
        for (var id : ids) {
            pstmt.setLong(1, id);
            pstmt.addBatch();
        }
				
        pstmt.executeBatch();
      }
			
      var customers = new ArrayList();
			
      attempt (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 measurement from 1,000 to 100,000. The code was applied utilizing Oracle JDK 21.0.4 and java.time.* API was used to depend the wall time of every technique. All exams had 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 is usually 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 probably the most environment friendly ones will be higher analyzed. Though there may be not a giant efficiency distinction between them, it’s seen that the Temp Desk technique dominates with decrease occasions in all experiments. Although it performs an extra step of inserting all IDs into the momentary desk, this technique doesn’t want to make use of the QSplitter instrument to generate subcollections and takes benefit of using the JOIN clause to keep away from indicating hard-coded IDs within the SELECT. This tradeoff could have benefited the technique’s efficiency.

Conclusion

The necessity to retrieve a big quantity of information from the Oracle database primarily based on a big checklist of identifiers shouldn’t be an unusual activity 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 varied 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 whole supply code is out there 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