Чи є обхідне рішення для ORA-01795: максимальна кількість виразів у списку становить 1000 помилок?


75

Чи є обхідне рішення для

'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: maximum number of expressions in a list is 1000 error. Я намагаюся виконати запит у TOAD, немає різниці, та сама помилка. Як я можу змінити запит, щоб він працював?

Спасибі заздалегідь


2
помістіть value1 .... value1000 + у таблицю та виберіть ім'я у (виберіть значення з таблиці)
basdwarf

2
Помилка не залежить від середовища (наприклад, SQL * Plus або TOAD або ...), де ви виконуєте свій запит.
René Nyffenegger

Відповіді:


120

Просто використовуйте декілька речень, щоб обійти це:

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

5
Майте на увазі, що якщо ви хочете використовувати логіку NOT IN, вам потрібно І ці висловлювання разом
Кіт Ріттер,

Так, це, мабуть, найкраща відповідь, і його слід поставити галочку. Важливо зазначити, що обмеження становить понад 1000 пунктів у списку, розділяючи їх у різних списках, це розумне рішення та корисне для мене.
theGabyRod

Це рішення для мене добре працювало, але, мабуть, це не найбільш масштабоване рішення.
juanheyns

Я використовую деякі допоміжні функції для генерації рядків SQL. Розбиття на декілька списків робить це більш неприємним (я думаю, вам потрібні дужки, щоб бути у безпеці і згрупувати це як єдине речення), але це робить і кортежі. Хтось знає, чи є різниця у продуктивності між різними варіантами
Адам,

Як це зробити в perl? Хто-небудь може мені допомогти з цим питанням? stackoverflow.com/questions/62507305 / ...
Biswajit Maharana

28

Нижче наведено кілька обхідних рішень:

1. Розділити розділ IN

Розділіть пропозицію 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>);

2
Гарне резюме. Чи знаєте ви, чи існує різниця в продуктивності між різними варіантами?
Адам

У мене є дані у списку на 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як альтернативу створенню тимчасових таблиць для кожного запиту. Будь-які коментарі?
Адам

24

Нещодавно я зіткнувся з цим питанням і придумав нахабний спосіб зробити це, не зв’язуючи додаткові речення 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#3515498
та
https://community.oracle.com/thread/958612

Це звичайно, якщо у вас немає можливості використовувати підзапит всередині IN, щоб отримати потрібні значення з тимчасової таблиці.


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предиката.


7

Будь ласка, використовуйте внутрішній запит всередині in-застереження:

select col1, col2, col3... from table1
 where id in (select id from table2 where conditions...)

Можливе використання внутрішнього з'єднання, це значно пришвидшило вибір у нашому випадку (8 секунд проти 50 мс).
jahav

1
Це передбачає, що ваші дані для речення where є в іншій таблиці в тій самій БД, і ви знаєте, як вибрати, щоб отримати їх! Не завжди правда
Адам

4

Є ще один варіант: 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. У мене є дві таблиці в БД з підписками на елементи відповідних клієнтів. Я хочу отримати список усіх підписок на товари або замовника на цей товар разом з ідентифікатором товару.

Я спробував три варіанти:

  1. Кілька виділень з Java (за допомогою кортежів, щоб обійти межу)
  2. З-синтаксисом
  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 з ключем cus_id і списком елементів як значенням, і для кожного знайденого підписки клієнта додаю (до списку, що повертається з першого вибору) запис для всіх відповідних елементів із цим елементом_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 рядків, на які я хочу знайти підписки.
  • Я спробував лише один набір даних.
  • Я перебуваю не в тому ж фізичному розташуванні, що і мій сервер БД. Це не так далеко, але я помічаю, якщо я спробую з дому через VPN, то це все набагато повільніше, хоча це однакова відстань (і проблема не в моєму домашньому Інтернеті).
  • Я тестував повний виклик, тому мій API викликає інший (який також працює в тому ж екземплярі в dev), який також підключається до БД, щоб отримати початковий набір даних. Але це однаково у всіх трьох випадках.

YMMV.

Тим не менш, варіант тимчасового столу був набагато більшим повільнішим. Як у подвійному так повільно. Я отримував 14-15 секунд для варіанту 1, 15-16 для варіанту 2 і 30 для варіанту 3.

Я спробую їх знову з тієї ж мережі, що і сервер БД, і перевірю, чи це не змінює ситуації, коли я отримаю можливість.


3

існує також інший спосіб вирішити цю проблему. припустимо, у вас є дві таблиці Таблиця1 та Таблиця2. і потрібно отримати всі записи Таблиці1, на які не посилаються / присутні в Таблиці2, використовуючи запит Критерії. Тож вперед так ...

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, не включаючи 1000 або більше параметрів у SQL, перетворених за допомогою Hibernate framework. У мене це спрацювало. Примітка: Можливо, вам доведеться змінити частину SQL відповідно до ваших вимог.


3

Я усвідомлюю, що це старе питання, яке стосується TOAD, але якщо вам потрібно кодувати це за допомогою c #, ви можете розділити список через цикл for. Ви можете по суті зробити те саме з Java, використовуючи subList ();

    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

Союз Operato

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", щоб отримати максимальну ефективність.
Даніель Куадра

1
    **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. Як це відповідає на питання?
Ноа Бройлес,

На Java ми можемо вирішити цю проблему за допомогою вищезазначеного рішення та будь-якої мови програмування - це спосіб вирішення
Akhil Sabu

0

Існує також обхідний спосіб, який дозволяє роз’єднати ваш масив, це працювало для мене, оскільки інші рішення було важко реалізувати за допомогою деяких старих фреймворків.

select * from tableA where id = 1 or id = 2 or id = 3 ...

Але для кращого перфо, я б використовував рішення Миколи Нечая з профспілками, якщо це можливо.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.