Как поместить более 1000 значений в предложение Oracle IN

78

Есть ли способ обойти ограничение Oracle 10g на 1000 элементов в статическом разделе IN? У меня есть список разделенных запятыми многих идентификаторов, которые я хочу использовать в предложении IN. Иногда этот список может превышать 1000 элементов, после чего Oracle выдает ошибку. Запрос похож на этот...

select * from table1 where ID in (1,2,3,4,...,1001,1002,...)
  • 0
    Какой у вас клиент? .Net, Java ...?
  • 1
    Вы пробовали расширение? т.е. ГДЕ (ID = 1 ИЛИ ID = 2 ....)
Показать ещё 3 комментария
Теги:
in-clause

11 ответов

79
Лучший ответ

Поместите значения во временную таблицу, а затем выполните выбор, где находится id (выберите id из искушаемого)

  • 7
    Лично я поместил бы значения во временную таблицу и использовал бы JOIN для запроса значений. Я не знаю, действительно ли это лучшая производительность или нет.
  • 0
    @Neil Barnwell - я думаю, что любой приличный движок SQL оптимизировал бы так, чтобы IN и JOIN имели почти одинаковую производительность. Использование IN, по крайней мере, для меня яснее по своему намерению.
Показать ещё 7 комментариев
43

Вы можете попробовать использовать следующую форму:

select * from table1 where ID in (1,2,3,4,...,1000)
union all
select * from table1 where ID in (1001,1002,...)
  • 2
    Когда нет привилегии для создания временной таблицы, это делает супер обходной путь ..
  • 0
    Не подходит, если вы не знаете, на сколько тысяч ценностей раньше времени. Например, если есть 3005 значений, будет объединено 4 предложения выбора.
Показать ещё 1 комментарий
36

Я почти уверен, что вы можете разделить значения на несколько IN с помощью OR:

select * from table1 where ID in (1,2,3,4,...,1000) or 
ID in (1001,1002,...,2000)
  • 8
    Максимальное количество значений в предложении IN - это одно из тех ограничений, которыми вы никогда не должны ограничиваться.
  • 3
    Это можно сделать, но это означает, что Oracle каждый раз видит разные запросы, а это требует много сложного анализа, что замедляет работу.
Показать ещё 3 комментария
15
select column_X, ... from my_table
where ('magic', column_X ) in (
        ('magic', 1),
        ('magic', 2),
        ('magic', 3),
        ('magic', 4),
             ...
        ('magic', 99999)
    ) ...
  • 1
    Как это работает?
  • 2
    Поскольку Oracle просто так (шокирует?), Вы не поверите, пока не попробуете и не увидите, что это работает! Это лучше, чем создавать временную таблицу, особенно если вы хорошо работаете с vi / vim / subl.
Показать ещё 5 комментариев
8

Откуда вы получаете список идентификаторов от первого места? Поскольку они являются идентификаторами в вашей базе данных, они были получены из предыдущего запроса?

Когда я видел это в прошлом, это было потому, что: -

  • отсутствует ссылочная таблица, и правильным способом было бы добавить новую таблицу, поместить атрибут в эту таблицу и присоединиться к ней.
  • список идентификаторов извлекается из базы данных, а затем используется в последующих операциях SQL (возможно, позже или на другом сервере или что-то еще). В этом случае ответ заключается в том, чтобы никогда не извлекать его из базы данных. Либо хранить во временной таблице, либо просто написать один запрос.

Я думаю, что могут быть лучшие способы переделать этот код, чтобы просто заставить этот оператор SQL работать. Если вы предоставите более подробную информацию, вы можете получить некоторые идеи.

  • 1
    Отличные вопросы! Я часто использую технику массива, которую я уже опубликовал, но я использую ее, когда пользователь вручную выбрал несколько строк в сетке данных пользовательского интерфейса. Однако маловероятно, что пользователь выберет> 1000 строк вручную.
5

Использовать... из таблицы (...:

create or replace type numbertype
as object
(nr number(20,10) )
/ 

create or replace type number_table
as table of numbertype
/ 

create or replace procedure tableselect
( p_numbers in number_table
, p_ref_result out sys_refcursor)
is
begin
  open p_ref_result for
    select *
    from employees , (select /*+ cardinality(tab 10) */ tab.nr from table(p_numbers) tab) tbnrs 
    where id = tbnrs.nr; 
end; 
/ 

Это один из редких случаев, когда вам нужен намек, иначе Oracle не будет использовать индекс для идентификатора столбца. Одно из преимуществ этого подхода заключается в том, что Oracle не нуждается в жестком анализе запроса снова и снова. Использование временной таблицы в большинстве случаев медленнее.

изменить 1 упростить процедуру (спасибо jimmyorr) + пример

create or replace procedure tableselect
( p_numbers in number_table
, p_ref_result out sys_refcursor)
is
begin
  open p_ref_result for
    select /*+ cardinality(tab 10) */ emp.*
    from  employees emp
    ,     table(p_numbers) tab
    where tab.nr = id;
end;
/

Пример:

set serveroutput on 

create table employees ( id number(10),name varchar2(100));
insert into employees values (3,'Raymond');
insert into employees values (4,'Hans');
commit;

declare
  l_number number_table := number_table();
  l_sys_refcursor sys_refcursor;
  l_employee employees%rowtype;
begin
  l_number.extend;
  l_number(1) := numbertype(3);
  l_number.extend;
  l_number(2) := numbertype(4);
  tableselect(l_number, l_sys_refcursor);
  loop
    fetch l_sys_refcursor into l_employee;
    exit when l_sys_refcursor%notfound;
    dbms_output.put_line(l_employee.name);
  end loop;
  close l_sys_refcursor;
end;
/

Это выведет:

Raymond
Hans
3

Я оказался здесь, ища решение.

В зависимости от высокого количества элементов, с которыми вам нужно запросить, и если ваши объекты уникальны, вы можете разделить запрос на пакетные запросы из 1000 элементов и вместо этого комбинировать результаты на своем конце (здесь псевдокод):

//remove dupes
items = items.RemoveDuplicates();

//how to break the items into 1000 item batches        
batches = new batch list;
batch = new batch;
for (int i = 0; i < items.Count; i++)
{
    if (batch.Count == 1000)
    {
        batches.Add(batch);
        batch.Clear()
    }
    batch.Add(items[i]);
    if (i == items.Count - 1)
    {
        //add the final batch (it has < 1000 items).
        batches.Add(batch); 
    }
}

// now go query the db for each batch
results = new results;
foreach(batch in batches)
{
    results.Add(query(batch));
}

Это может быть хорошим компромиссом в сценарии, в котором обычно не более 1000 элементов, поскольку более 1000 элементов будут вашим "крайним" краевым сценарием. Например, если у вас есть 1500 элементов, два запроса (1000, 500) не так уж плохи. Это также предполагает, что каждый запрос не является особенно дорогостоящим по своему усмотрению.

Это было бы нецелесообразно, если бы ваше типичное количество ожидаемых предметов стало намного больше - скажем, в диапазоне 100000 - требуется 100 запросов. Если да, то вам, вероятно, следует более серьезно относиться к использованию решения глобальных временных таблиц, представленного выше, как наиболее "правильное" решение. Кроме того, если ваши объекты не уникальны, вам также необходимо будет разрешить дублировать результаты в ваших партиях.

  • 1
    Он, мое решение более правильное :) Нет необходимости во временной таблице.
  • 0
    правильное мышление, код немного слишком шаблонно, хотя. мы используем Lists.partition () из Google-коллекций, чтобы сделать это почти однострочно
Показать ещё 2 комментария
1

Да, очень странная ситуация для оракула. ​​

если вы укажете 2000 идентификаторов внутри предложения IN, он завершится с ошибкой. это не удается:

select ... 
where id in (1,2,....2000) 

но если вы просто поместите идентификаторы 2000 в другую таблицу (например, таблицу temp), она будет работать это работает:

select ... 
where id in (select userId 
             from temptable_with_2000_ids ) 

что вы можете сделать, на самом деле можно разделить записи на много 1000 записей и выполнить их по группам.

0

Вот какой-то код Perl, который пытается обойти ограничения, создав встроенное представление, а затем выбрав его. Текст инструкции сжимается, используя строки из двенадцати элементов каждый, вместо того, чтобы выбирать каждый элемент из DUAL отдельно, а затем несжатый путем объединения всех столбцов. UNION или UNION ALL в декомпрессии не должны иметь никакого значения здесь, поскольку все это входит внутрь IN, который наложит уникальность, прежде чем присоединяться к нему в любом случае, но в сжатии UNION ALL используется для предотвращения большого количества ненужного сравнения. Поскольку данные, которые я фильтрую, являются целыми числами, цитирование не является проблемой.

#
# generate the innards of an IN expression with more than a thousand items
#
use English '-no_match_vars';
sub big_IN_list{
    @_ < 13 and return join ', ',@_;
    my $padding_required = (12 - (@_ % 12)) % 12;  
    # get first dozen and make length of @_ an even multiple of 12
    my ($a,$b,$c,$d,$e,$f,$g,$h,$i,$j,$k,$l) = splice @_,0,12, ( ('NULL') x $padding_required );

    my @dozens; 
    local $LIST_SEPARATOR = ', '; # how to join elements within each dozen
    while(@_){
        push @dozens, "SELECT @{[ splice @_,0,12 ]} FROM DUAL"
    };  
    $LIST_SEPARATOR = "\n    union all\n    "; # how to join @dozens 
    return <<"EXP";
WITH t AS (
    select $a A, $b B, $c C, $d D, $e E, $f F, $g G, $h H, $i I, $j J, $k K, $l L FROM     DUAL
    union all
    @dozens
 )
select A from t union select B from t union select C from t union
select D from t union select E from t union select F from t union
select G from t union select H from t union select I from t union 
select J from t union select K from t union select L from t
EXP
}

Можно использовать так:

my $bases_list_expr = big_IN_list(list_your_bases());
$dbh->do(<<"UPDATE");
    update bases_table set belong_to = 'us'
    where whose_base in ($bases_list_expr)
UPDATE
0

Вместо использования предложения IN вы можете попробовать использовать JOIN с другой таблицей, которая извлекает идентификатор. таким образом нам не нужно беспокоиться о лимите. просто мысль с моей стороны.

  • 0
    Я вижу, что это был ваш первый ответ на вопрос, поэтому у вас не было представителя, который бы опубликовал это как комментарий, но в будущем вы можете подумать о том, чтобы опубликовать это как комментарий. Предполагается, что ответы - это решение проблемы, которое, как вы уверены, решит проблему.
  • 0
    Это не решит проблему. Объединения выберут все записи, но не выберут один или два элемента, выбранных в предложении IN.
-3

Вместо SELECT * FROM table1 WHERE ID IN (1,2,3,4,...,1000);

Используйте это:

SELECT * FROM table1 WHERE ID IN (SELECT rownum AS ID FROM dual connect BY level <= 1000);

* Обратите внимание, что вам нужно убедиться, что идентификатор не ссылается на другие внешние IDS, если это зависимость. Чтобы обеспечить доступность только существующих идентификаторов, выполните следующие действия:

SELECT * FROM table1 WHERE ID IN (SELECT distinct(ID) FROM tablewhereidsareavailable);

Приветствия

Ещё вопросы

Сообщество Overcoder
Наверх
Меню