web-dev-qa-db-ja.com

Java Oracleの例外-「リスト内の式の最大数は1000です」

必要なデータを取得するために、文字列のリストをクエリ(SQLクエリの作成)に渡します。しかし、私はこの例外を得ています:

ora-01795リスト内の式の最大数は1000です

クエリのINパラメーターに渡されたリストに1000を超えるエントリがあることを確認しました。

17
Anand

これは、クエリのリストパスの数におけるOracleの制限です。

  1. クエリを切り刻むか、
  2. 代わりに、IN句でサブクエリ/結合を指定してください。
14
digitebs

Oracle DBを使用している場合、1つの「where」条件に1000を超える要素を含むリストを作成することはできません。したがって、複数の「where」条件で「where」条件を切り詰めて、「or」句で結合できます。

Hibernate Criteriaを使用している場合は、以下のJavaメソッドを使用してこれを実行できます。使用した場所でコードを置き換えるだけです。

criteria.add(Restrictions.in(propertyName, mainList));

addCriteriaIn(propertyName, mainList, criteria);

どの方法であるか:

 private void addCriteriaIn (String propertyName, List<?> list,Criteria criteria)
  {
    Disjunction or = Restrictions.disjunction();
    if(list.size()>1000)
    {        
      while(list.size()>1000)
      {
        List<?> subList = list.subList(0, 1000);
        or.add(Restrictions.in(propertyName, subList));
        list.subList(0, 1000).clear();
      }
    }
    or.add(Restrictions.in(propertyName, list));
    criteria.add(or);
  }
9

リストをサイズ1000のバッチに分割し、ORを使用して結合することで、これを解決しました。

例えばIDのeid []配列。

このクエリを実行する場合は、

String sql = select * from employee where some conditions and empid in(eid)

私は小さなコードを書いてこのクエリを書き直しました:

String sql = select * from employee where some conditions and ( 
                             empid in(empid[0...999]) OR
                             empid in(empid[1000...1999]) OR
                             empid in(empid[2000...2999]) OR .... );

Hibernateを使用しているときにこのエラーに対処するには、リストを100のバッチに細断してこの問題に取り組み、次に上記のクエリに示すように個々の結果を結合する必要があります。

この問題がMySQLやDB2のような別のDBの場合ではない可能性があるため、この問題を処理しないことはhibernateの制限ではないと思います。 HibernateはクロスDB ORMフレームワークです。

5
Vallabh Patade

一時テーブルを作成し、INステートメントで使用する値を挿入して、一時テーブルを実際のテーブルに結合できます。 一時テーブルの詳細情報

4
dursun

dba-Oracle.com から:

ORA-01795:リスト内の式の最大数は1000のヒントです

Burleson Consulting(S. Karam)によるOracleエラーのヒント

Oracleのドキュメントでは、ora-01795エラーについてこれに注意しています*:ORA-01795リスト内の式の最大数は1000です原因:254を超える列または式がリストで指定されました。処置:リストから式の一部を削除してください。 Oracle MOSCフォーラムで、OracleユーザーがエラーコードORA-01795を回避する方法を見つけようとしました。オラクルのReem Munakashが彼の質問に答えました。

Oracle8の制限は1000式です。バグ495555があり、間違った番号(254)を与えるエラーテキストに対して提出されます。ただし、使用しているツールによってはさらに制限がある場合があります。 1000の式はsqlplus内にあります。

回避策は、サブクエリを使用することです。

エラーメッセージに関するバグは8.1.5で修正されています。

1
Igor Kostenko

データベース側のロジックをクエリからストアドプロシージャに変換できる場合は、より長い配列(コレクション)を渡すことができます。

ここ 簡単な例を見つけることができます。ドキュメントへのリンクは古くなっているため、9iドキュメントへのリンク http://docs.Oracle.com/cd/B10500_01/Java.920/a96654/oraarr.htm#1040124

import Java.io.*;
import Java.sql.*;
import Oracle.sql.*;
import Oracle.jdbc.driver.*;

public class ArrayDemo
{
    public static void passArray() throws SQLException
    {
        Connection conn =
            new OracleDriver().defaultConnection();

        int intArray[] = { 1,2,3,4,5,6 };

        ArrayDescriptor descriptor =
            ArrayDescriptor.createDescriptor( "NUM_ARRAY", conn );

        ARRAY array_to_pass =
            new ARRAY( descriptor, conn, intArray );

        OraclePreparedStatement ps =
            (OraclePreparedStatement)conn.prepareStatement
            ( "begin give_me_an_array(:x); end;" );

        ps.setARRAY( 1, array_to_pass );

        ps.execute();

    }
}

そしてSQL部分

create or replace type NUM_ARRAY as table of number;

create or replace
procedure give_me_an_array( p_array in num_array )
as
begin
    for i in 1 .. p_array.count
    loop
        dbms_output.put_line( p_array(i) );
    end loop;
end;
1
bpgergo

Java Hibernateを使用して、この問題を解決するために、Hibernate-core JARを変更することにしました。ヘルパークラスを作成して、次のような結合で式を分割しました:... t.column IN (: list_1) OR t.column IN (: list_2) ...、コレクションが制限を超えた場合にAbstractQueryImpl.expandParameterListメソッドをhibernateからメソッドを呼び出すように変更しました。
私のhibernate-coreバージョンは3.6.10.Finalですが、4.xバージョンでは問題なく動作します-私はテストしました。
私のコードは次のケースでテストされています:

  where t.id in (:idList)
  where (t.id in (:idList))
  where ((t.id) in (:idList))
  where 1=1 and t.id in (:idList)
  where 1=1 and (t.id in (:idList))
  where 1=1 and(t.id) in (:idList)
  where 1=1 and((t.id) in (:idList))
  where 1=1 and(t.id in (:idList))

  where t.id not in (:idList)
  where (t.id not in (:idList))
  where ((t.id) not in (:idList))

AbstractQueryImpl.expandParameterList:

private String expandParameterList(String query, String name, TypedValue typedList, Map namedParamsCopy) {
    Collection vals = (Collection) typedList.getValue();
    Type type = typedList.getType();

    boolean isJpaPositionalParam = parameterMetadata.getNamedParameterDescriptor( name ).isJpaStyle();
    String paramPrefix = isJpaPositionalParam ? "?" : ParserHelper.HQL_VARIABLE_PREFIX;
    String placeholder =
            new StringBuffer( paramPrefix.length() + name.length() )
                    .append( paramPrefix ).append(  name )
                    .toString();

    if ( query == null ) {
        return query;
    }
    int loc = query.indexOf( placeholder );

    if ( loc < 0 ) {
        return query;
    }

    String beforePlaceholder = query.substring( 0, loc );
    String afterPlaceholder =  query.substring( loc + placeholder.length() );

    // check if placeholder is already immediately enclosed in parentheses
    // (ignoring whitespace)
    boolean isEnclosedInParens =
            StringHelper.getLastNonWhitespaceCharacter( beforePlaceholder ) == '(' &&
                    StringHelper.getFirstNonWhitespaceCharacter( afterPlaceholder ) == ')';

    if ( vals.size() == 1  && isEnclosedInParens ) {
        // short-circuit for performance when only 1 value and the
        // placeholder is already enclosed in parentheses...
        namedParamsCopy.put( name, new TypedValue( type, vals.iterator().next(), session.getEntityMode() ) );
        return query;
    }

    // *** changes by Vasile Bors for HHH-1123 ***
    // case vals.size() > 1000
    if ((vals.size() >= InExpressionExpander.MAX_ALLOWED_PER_INEXPR) && isEnclosedInParens) {

        InExpressionExpander inExpressionExpander = new InExpressionExpander(beforePlaceholder, afterPlaceholder);
        if(inExpressionExpander.isValidInOrNotInExpression()){

            List<String> list = new ArrayList<String>( vals.size() );
            Iterator iter = vals.iterator();
            int i = 0;
            String alias;
            while ( iter.hasNext() ) {
                alias = ( isJpaPositionalParam ? 'x' + name : name ) + i++ + '_';
                namedParamsCopy.put( alias, new TypedValue( type, iter.next(), session.getEntityMode() ) );
                list.add(ParserHelper.HQL_VARIABLE_PREFIX + alias );
            }

            String expandedExpression = inExpressionExpander.expandExpression(list);
            if(expandedExpression != null){
                return expandedExpression;
            }
        }
    }
    // *** end changes by Vasile Bors for HHH-1123 ***

    StringBuffer list = new StringBuffer(16);
    Iterator iter = vals.iterator();
    int i = 0;
    while (iter.hasNext()) {
        String alias = (isJpaPositionalParam ? 'x' + name : name) + i++ + '_';
        namedParamsCopy.put(alias, new TypedValue(type, iter.next(), session.getEntityMode()));
        list.append(ParserHelper.HQL_VARIABLE_PREFIX).append(alias);
        if (iter.hasNext()) {
            list.append(", ");
        }
    }

    return StringHelper.replace(
            beforePlaceholder,
            afterPlaceholder,
            placeholder.toString(),
            list.toString(),
            true,
            true
    );
}

私のヘルパークラスInExpressionExpander:

パッケージorg.hibernate.util;

import org.hibernate.QueryException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import Java.util.Iterator;
import Java.util.List;
import Java.util.Stack;

/**
 * Utility class for expand Hql and Sql IN expressions with a parameter with more than IN expression limit size (HHH-1123).
 * <br/>
 * It work for expression with formats:
 * <pre>
 *
 * where t.id in (:idList)
 * where (t.id in (:idList))
 * where ((t.id) in (:idList))
 * where 1=1 and t.id in (:idList)
 * where 1=1 and (t.id in (:idList))
 * where 1=1 and(t.id) in (:idList)
 * where 1=1 and((t.id) in (:idList))
 * where 1=1 and(t.id in (:idList))
 *
 * where t.id not in (:idList)
 * where (t.id not in (:idList))
 * where ((t.id) not in (:idList))
 * </pre>
 * <p/>
 * Example:
 * <pre>
 * select t.id from tableOrEntity t where t.id IN (:idList)
 * </pre
 *
 * @author Vasile Bors
 * @since 13/12/2015.
 */
public class InExpressionExpander {
    private static final Logger log = LoggerFactory.getLogger(InExpressionExpander.class);

    public static final  int MAX_ALLOWED_PER_INEXPR = 1000;
    private static final int MAX_PARAMS_PER_INEXPR  = 500;

    private Stack<String> stackExpr = new Stack<String>();
    private StringBuilder toWalkQuery;

    private final String beforePlaceholder;
    private final String afterPlaceholder;
    private boolean wasChecked         = false;
    private boolean isEnclosedInParens = false;
    private boolean isInExpr           = false;
    private boolean isNotInExpr        = false;

    public InExpressionExpander(String beforePlaceholder, String afterPlaceholder) {
        this.toWalkQuery = new StringBuilder(beforePlaceholder);

        this.beforePlaceholder = beforePlaceholder;
        this.afterPlaceholder = afterPlaceholder;
    }

    public boolean isValidInOrNotInExpression() {
        if (!wasChecked) {
            String lastExpr = extractLastExpression();
            if ("(".equals(lastExpr)) {
                isEnclosedInParens = true;
                lastExpr = extractLastExpression();
            }
            isInExpr = "in".equalsIgnoreCase(lastExpr);
        }

        wasChecked = true;
        return isInExpr;
    }

    public String expandExpression(List paramList) {
        if (isValidInOrNotInExpression()) {

            final String lastExpr = extractLastExpression(false);

            if ("not".equalsIgnoreCase(lastExpr)) {
                isNotInExpr = true;
                extractLastExpression(); //extract "not" and consume it
            }

            extractColumnForInExpression();

            StringBuilder exprPrefixBuilder = new StringBuilder();
            for (int i = stackExpr.size() - 1; i > -1; i--) {
                exprPrefixBuilder.append(stackExpr.get(i)).append(' ');
            }
            if (!isEnclosedInParens) {
                exprPrefixBuilder.append('(');
            }

            String expandedExpression = expandInExpression(exprPrefixBuilder, paramList);
            String beforeExpression = getBeforeExpression();
            String afterExpression = getAfterExpression();

            String expandedQuery = new StringBuilder(beforeExpression).append(expandedExpression)
                    .append(afterExpression)
                    .toString();

            if (log.isDebugEnabled()) {
                log.debug(
                        "Query was changed to prevent exception for maximum number of expression in a list. Expanded IN expression query:\n {}",
                        expandedExpression);

                log.debug("Expanded query:\n {}", expandedQuery);
            }

            return expandedQuery;
        }

        log.error("Illegal call of InExpressionExpander.expandExpression() without IN expression.");
        return null;
    }

    private String expandInExpression(StringBuilder exprPrefixBuilder, List values) {

        String joinExpr = isNotInExpr ? ") and " : ") or ";
        StringBuilder expr = new StringBuilder(16);
        Iterator iter = values.iterator();
        int i = 0;
        boolean firstExpr = true;
        while (iter.hasNext()) {
            if (firstExpr || i % MAX_PARAMS_PER_INEXPR == 0) {
                //close previous expression and start new expression
                if (!firstExpr) {
                    expr.append(joinExpr);
                } else {
                    firstExpr = false;
                }
                expr.append(exprPrefixBuilder);
            } else {
                expr.append(", ");
            }
            expr.append(iter.next());
            i++;
        }

        expr.append(')');// close for last in expression

        return expr.toString();
    }

    /**
     * Method extract last expression parsed by space from toWalkQuery and remove it from toWalkQuery;<br/>
     * If expression has brackets it will return al content between brackets and it will add additional space to adjust splitting by space.
     *
     * @return last expression from toWalkQuery
     */
    private String extractLastExpression() {
        return extractLastExpression(true);
    }

    /**
     * Method extract last expression parsed by space from toWalkQuery, remove it from toWalkQuery if is consume = true;<br/>
     * If expression has brackets it will return al content between brackets and it will add additional space to adjust splitting by space.
     *
     * @param consum if true  the method will extract and remove last expression from toWalkQuery
     * @return last expression from toWalkQuery
     */
    private String extractLastExpression(final boolean consum) {
        int lastIndex = this.toWalkQuery.length() - 1;
        String lastExpr;
        int exprSeparatorIndex = this.toWalkQuery.lastIndexOf(" ");
        if (lastIndex == exprSeparatorIndex) { //remove last space from the end
            this.toWalkQuery.delete(exprSeparatorIndex, this.toWalkQuery.length());
            return extractLastExpression(consum);
        } else {
            lastExpr = this.toWalkQuery.substring(exprSeparatorIndex + 1, this.toWalkQuery.length());

            if (lastExpr.length() > 1) {
                if (lastExpr.endsWith(")")) {
                    //if parens are closed at the end we need to find where it is open
                    int opensParens = 0;
                    int closedParens = 0;
                    int startExprIndex = -1;

                    char c;
                    for (int i = lastExpr.length() - 1; i > -1; i--) {
                        c = lastExpr.charAt(i);
                        if (c == ')') {
                            closedParens++;
                        } else if (c == '(') {
                            opensParens++;
                        }

                        if (closedParens == opensParens) {
                            startExprIndex = i;
                            break;
                        }
                    }

                    if (startExprIndex > -1) {
                        lastExpr = lastExpr.substring(startExprIndex, lastExpr.length());
                        exprSeparatorIndex = exprSeparatorIndex + startExprIndex
                                + 1; // +1 because separator is not space and don't must be deleted
                    }
                } else if (lastExpr.contains("(")) {
                    int parentsIndex = exprSeparatorIndex + lastExpr.indexOf('(') + 1;
                    this.toWalkQuery.replace(parentsIndex, parentsIndex + 1, " ( ");
                    return extractLastExpression(consum);
                }
            }

            if (consum) {
                this.toWalkQuery.delete(exprSeparatorIndex, this.toWalkQuery.length());
            }
        }

        if (consum) {
            stackExpr.Push(lastExpr);
        }

        return lastExpr;
    }

    private String extractColumnForInExpression() {
        String column = extractLastExpression();

        String beforeColumn = extractLastExpression(false);
        long pointIndx = beforeColumn.lastIndexOf('.');
        if (pointIndx > -1) {
            if (pointIndx == (beforeColumn.length() - 1)) {
                throw new QueryException(
                        "Invalid column format: " + beforeColumn + ' ' + column
                                + " . Remove space from column!");
            }
        }
        return column;
    }

    private String getBeforeExpression() {
        return this.toWalkQuery + " (";
    }

    private String getAfterExpression() {
        if (StringHelper.getFirstNonWhitespaceCharacter(afterPlaceholder) == ')') {
            return afterPlaceholder;
        }
        return afterPlaceholder + ") ";
    }
}

このソリューションを改善するための提案があれば嬉しいです。

0
Vasile Bors