ORA-01795: 列表中的表达式数量已达到最大限制1000,是否有解决方法?

113

有没有解决方法可以避免出现'ORA-01795: maximum number of expressions in a list is 1000 error'的错误?

我有一个查询,它根据一个字段的值选择字段。我使用in子句,其中包含10000多个值。

例如:

select field1, field2, field3 
from table1 
where name in 
(
'value1',
'value2',
...
'value10000+'
);
每次执行查询时都会出现"ORA-01795:列表中最多表达式数为1000"错误。我正在尝试在 TOAD 中执行查询,但没有区别,还是同样的错误。我该如何修改查询以使其正常工作?

3
将value1....value1000+放入一个表格中,并选择表格中值为(select value from table)的名称。 - basdwarf
2
错误不取决于您执行查询的环境(例如SQL * Plus或TOAD或...)。 - René Nyffenegger
15个回答

154

只需使用多个in子句来解决此问题:

select field1, field2, field3 from table1 
where  name in ('value1', 'value2', ..., 'value999') 
    or name in ('value1000', ..., 'value1999') 
    or ...;

10
请记住,如果您想要使用“不在”逻辑,则需要将这些语句进行“与”的组合。 - Keith Ritter
是的,这似乎是最好的答案,应该被勾选。需要注意的是,列表中的限制超过1000个项目,将它们分成多个列表是一个聪明的解决方案,对我很有用。 - TheGabiRod
这个解决方案对我来说效果很好,但可能不是最具可扩展性的解决方案。 - juanheyns
我正在使用一些辅助函数来生成SQL字符串。将其拆分为多个列表会使其变得更加混乱(我猜你需要将括号放在安全的位置并将其作为单个子句进行分组),但元组也是如此。有人知道各种选项之间是否存在性能差异吗? - Adam
如何在Perl中解决?有人能帮我回答这个问题吗? - Biswajit Maharana
显示剩余2条评论

56

一些解决方法包括:

1. 将IN子句分割

将IN子句分割成多个子句,其中文字量小于1000,并使用OR子句进行组合:

��原始的“WHERE”子句从一个“IN”条件拆分为多个“IN”条件:

Select id from x where id in (1, 2, ..., 1000,…,1500);

收件人:

Select id from x where id in (1, 2, ..., 999) OR id in (1000,...,1500);

2. 使用元组

1000的限制仅适用于单个项目的集合:(x) IN ((1), (2), (3), ...)。 如果集合包含两个或更多项目,则没有限制:(x, 0) IN ((1,0), (2,0), (3,0), ...):

Select id from x where (x.id, 0) IN ((1, 0), (2, 0), (3, 0),.....(n, 0));

3. 使用临时表

Select id from x where id in (select id from <temporary-table>);

5
好的总结。你知道这些选项之间是否有性能差异吗? - Adam
我在Java中有一个列表中的数据。我想知道是否可以使用with子句:with foo as (select :foo_1 id from dual union all ... select foo_n id from dual) select * from bar inner join foo on bar.id = foo.id 作为创建每个查询的临时表的替代方法。您有什么评论吗? - Adam
1
2非常好,它已经多次拯救了我。0甚至比https://dev59.com/c3RC5IYBdhLWcg3wJNcN#17019130中的“魔法”更好。 - Tomáš Záluský

29

我最近遇到了这个问题,并想出了一个聪明的方法来解决它,而无需串联额外的IN子句。

您可以利用元组。

SELECT field1, field2, field3
FROM table1
WHERE (1, name) IN ((1, value1), (1, value2), (1, value3),.....(1, value5000));

Oracle允许>1000个元组,但不允许简单值。更多信息请参见 https://community.oracle.com/message/3515498#3515498https://community.oracle.com/thread/958612

当然,如果您不能使用子查询在IN语句中从临时表中获取所需的值,则可以使用此方法。


7
请在 in 子句中使用内部查询:
select col1, col2, col3... from table1
 where id in (select id from table2 where conditions...)

可能使用内连接,这在我们的情况下显著加快了选择速度(8秒 vs 50毫秒)。 - jahav
1
假设您的 where 子句所需的数据在同一数据库中的另一个表中,并且您知道如何选择它!这并不总是正确的。 - Adam

7

另一种方法:

CREATE OR REPLACE TYPE TYPE_TABLE_OF_VARCHAR2 AS TABLE OF VARCHAR(100);
-- ...
SELECT field1, field2, field3
  FROM table1
  WHERE name IN (
    SELECT * FROM table (SELECT CAST(? AS TYPE_TABLE_OF_VARCHAR2) FROM dual)
  );

我认为它不是最优的,但它能够工作。提示/*+ CARDINALITY(...) */非常有用,因为Oracle不理解传递的数组的基数,无法估计最佳执行计划。
作为另一种选择,可以批量插入到临时表中,并在子查询中使用最后一个IN谓词。

5
还有另一种选择:使用with语法。使用OP的示例,代码如下:
with data as (
  select 'value1' name from dual
  union all
  select 'value2' name from dual
  union all
...
  select 'value10000+' name from dual)
select field1, field2, field3 
from table1 t1
inner join data on t1.name = data.name;

我遇到了这个问题。在我的情况下,我有一个Java中的数据列表,每个项都有item_id和customer_id。数据库中有两个表,分别是项目对应客户的订阅。我想获取所有订阅该项目或该顾客的订阅列表,并带有项目id。
我尝试了三种变体:
  1. 从Java中进行多次选择(使用元组来绕过限制)
  2. 使用 With 语法
  3. 临时表
选项1:从Java中进行多次选择
基本上,我首先
select item_id, token 
from item_subs 
where (item_id, 0) in ((:item_id_0, 0)...(:item_id_n, 0))

那么

select cus_id, token 
from cus_subs 
where (cus_id, 0) in ((:cus_id_0, 0)...(:cus_id_n, 0))

我用Java建立了一个Map,以cus_id作为键,以列表项作为值,并且对于找到的每个客户订阅,我都会添加一个所有相关项的条目(使用item_id从第一个选择返回的列表中)。 这段代码比较混乱。

选项2:使用with语法

使用类似SQL的方法一次性获取所有内容。

with data as (
  select :item_id_0 item_id, :cus_id_0 cus_id
  union all
  ...
  select :item_id_n item_id, :cus_id_n cus_id )
select I.item_id item_id, I.token token
from item_subs I
inner join data D on I.item_id = D.item_id
union all
select D.item_id item_id, C.token token
from cus_subs C
inner join data D on C.cus_id = D.cus_id

选项3:临时表

创建一个具有三个字段的全局临时表:rownr(主键)、item_id和cus_id。将所有数据插入其中,然后运行与选项2非常相似的选择,但链接临时表而不是with data

性能

这不是完全科学的性能分析。

  • 我正在使用开发数据库,数据集中有略多于1000行要查找订阅。
  • 我只尝试了一个数据集。
  • 我不在我的DB服务器的同一物理位置。 它并不远,但是我注意到如果我通过VPN从家里尝试,那么所有事情都会慢得多,即使它距离相同(也不是我的家庭互联网有问题)。
  • 我正在测试完整调用,因此我的API调用另一个(也在dev中的同一实例中运行)也连接到DB以获取初始数据集。 但这在所有三种情况下都是相同的。

结果可能会有所不同。

话虽如此,临时表选项要慢得多。 如两倍慢。 我得到的是选项1的14-15秒,选项2的15-16秒和选项3的30秒。

我会在有机会时再次尝试它们,并检查是否从DB服务器的同一网络中更改了这些内容。


4

我知道这是一个关于TOAD的老问题,但如果你需要用C#进行编码,你可以通过for循环拆分列表。你也可以使用subList()在Java中实现相同的功能;

    List<Address> allAddresses = GetAllAddresses();
    List<Employee> employees = GetAllEmployees(); // count > 1000

    List<Address> addresses = new List<Address>();

    for (int i = 0; i < employees.Count; i += 1000)
    {
        int count = ((employees.Count - i) < 1000) ? (employees.Count - i) - 1 : 1000;
        var query = (from address in allAddresses
                     where employees.GetRange(i, count).Contains(address.EmployeeId)
                     && address.State == "UT"
                     select address).ToList();

        addresses.AddRange(query);
    }

希望这能帮助到某些人。

3

操作符联合

select * from tableA where tableA.Field1 in (1,2,...999)
union
select * from tableA where tableA.Field1 in (1000,1001,...1999)
union
select * from tableA where tableA.Field1 in (2000,2001,...2999)

这是最佳解决方案,因为它可以提高性能。只需使用“UNION ALL”而不是“UNION”,即可获得最大性能。 - DanielCuadra

3

还有另一种解决此问题的方法。假设您有两个表Table1和Table2。需要使用Criteria查询检索Table1中未在Table2中引用/存在的所有条目。请按照以下步骤进行:

List list=new ArrayList(); 
Criteria cr=session.createCriteria(Table1.class);
cr.add(Restrictions.sqlRestriction("this_.id not in (select t2.t1_id from Table2 t2 )"));
.
.

. . . 它将在SQL中直接执行所有子查询函数,而无需在Hibernate框架转换的SQL中包含1000个或更多参数。这对我很有效。注意:您可能需要根据需要更改SQL部分。


2
    **Divide a list to lists of n size**

    import java.util.AbstractList;
    import java.util.ArrayList;
    import java.util.List;

    public final class PartitionUtil<T> extends AbstractList<List<T>> {

        private final List<T> list;
        private final int chunkSize;

        private PartitionUtil(List<T> list, int chunkSize) {
            this.list = new ArrayList<>(list);
            this.chunkSize = chunkSize;
        }

        public static <T> PartitionUtil<T> ofSize(List<T> list, int chunkSize) {
            return new PartitionUtil<>(list, chunkSize);
        }

        @Override
        public List<T> get(int index) {
            int start = index * chunkSize;
            int end = Math.min(start + chunkSize, list.size());

            if (start > end) {
                throw new IndexOutOfBoundsException("Index " + index + " is out of the list range <0," + (size() - 1) + ">");
            }

            return new ArrayList<>(list.subList(start, end));
        }

        @Override
        public int size() {
            return (int) Math.ceil((double) list.size() / (double) chunkSize);
        }
    }





Function call : 
              List<List<String>> containerNumChunks = PartitionUtil.ofSize(list, 999)

更多细节:https://e.printstacktrace.blog/divide-a-list-to-lists-of-n-size-in-Java-8/

这个问题是关于SQL而不是Java的。这个回答如何解答这个问题? - Captain Jack Sparrow
在Java中,我们可以通过上述解决方案来解决这个问题,在任何编程语言中都是一种解决方法。 - Akhil Sabu

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接