Jakie są najlepsze obejścia dla używania IN
klauzuli SQL z instancjami java.sql.PreparedStatement
, które nie są obsługiwane dla wielu wartości z powodu problemów związanych z bezpieczeństwem ataku wstrzykiwania SQL: Jeden ?
symbol zastępczy reprezentuje jedną wartość, a nie listę wartości.
Rozważ następującą instrukcję SQL:
SELECT my_column FROM my_table where search_column IN (?)
Używanie preparedStatement.setString( 1, "'A', 'B', 'C'" );
jest zasadniczo niedziałającą próbą obejścia przyczyn używania ?
w pierwszej kolejności.
Jakie obejścia są dostępne?
Odpowiedzi:
Analiza różnych dostępnych opcji oraz zalety i wady każdej z nich jest dostępna tutaj .
Sugerowane opcje to:
SELECT my_column FROM my_table WHERE search_column = ?
, uruchom ją dla każdej wartości, a UNION wyniki po stronie klienta. Wymaga tylko jednego przygotowanego wyciągu. Powolny i bolesny.SELECT my_column FROM my_table WHERE search_column IN (?,?,?)
i uruchom. Wymaga jednego przygotowanego zestawienia na każdy rozmiar listy. Szybki i oczywisty.SELECT my_column FROM my_table WHERE search_column = ? ; SELECT my_column FROM my_table WHERE search_column = ? ; ...
i uruchom. [Lub użyjUNION ALL
zamiast tych średników. --ed] Wymaga jednego przygotowanego zestawienia na każdy rozmiar listy. Głupio powolne, zdecydowanie gorsze niżWHERE search_column IN (?,?,?)
, więc nie wiem, dlaczego bloger nawet to zasugerował.SELECT my_column FROM my_table WHERE search_column IN (1,2,3,4,5,6,6,6,6,6)
. Każdy przyzwoity serwer zoptymalizuje zduplikowane wartości przed uruchomieniem zapytania.Żadna z tych opcji nie jest jednak świetna.
W tych miejscach udzielono odpowiedzi na duplikaty z równie rozsądnymi alternatywami, ale żadne z nich nie jest super świetne:
Prawidłowa odpowiedź, jeśli korzystasz z JDBC4 i obsługiwanego serwera
x = ANY(y)
, powinna być zgodna z poniższymPreparedStatement.setArray
opisem:Wydaje się jednak, że nie ma sposobu na
setArray
pracę z listami IN.Czasami instrukcje SQL są ładowane w czasie wykonywania (np. Z pliku właściwości), ale wymagają zmiennej liczby parametrów. W takich przypadkach najpierw zdefiniuj zapytanie:
Następnie załaduj zapytanie. Następnie określ liczbę parametrów przed uruchomieniem. Po znaniu liczby parametrów uruchom:
Na przykład:
W przypadku niektórych baz danych, w których przekazywanie tablicy za pomocą specyfikacji JDBC 4 nie jest obsługiwane, ta metoda może ułatwić przekształcenie spowolnienia
= ?
wIN (?)
warunek szybszej klauzuli, który można następnie rozwinąć, wywołującany
metodę.źródło
Rozwiązanie dla PostgreSQL:
lub
źródło
.createArrayOf()
części jest bardziej specyficzny dla JDBC4 niż dla PostgreSQL , ale nie jestem pewien, czy ścisła semantyka dla użytkownikaArray
jest zdefiniowana w specyfikacji JDBC..createArrayOf
nie działa, możesz wykonać własne ręczne tworzenie literału tablicowego, takiego jakString arrayLiteral = "{A,\"B \", C,D}"
(zwróć uwagę, że „B” ma spację, podczas gdy C nie), a następnie,statement.setString(1,arrayLiteral)
gdzie jest przygotowane wyrażenie... IN (SELECT UNNEST(?::VARCHAR[]))
lub... IN (SELECT UNNEST(CAST(? AS VARCHAR[])))
. (PS: Nie sądzę, abyANY
SELECT
Nie ma prostej drogi AFAIK. Jeśli celem jest utrzymanie wysokiego współczynnika pamięci podręcznej instrukcji (tj. Nie tworzenie instrukcji dla każdej liczby parametrów), możesz wykonać następujące czynności:
utwórz instrukcję z kilkoma (np. 10) parametrami:
... GDZIE WEJŚĆ (?,?,?,?,?,?,?,?,?,?) ...
Powiąż wszystkie aktualne parametry
setString (1, „foo”); setString (2, „bar”);
Powiąż resztę jako NULL
setNull (3, Typ.VARCHAR) ... setNull (10, Typ.VARCHAR)
NULL nigdy nie pasuje do niczego, więc jest optymalizowany przez konstruktora planu SQL.
Logikę można łatwo zautomatyzować po przekazaniu listy do funkcji DAO:
źródło
NULL
w zapytaniu pasujeNULL
wartość w bazie danych?NOT IN
iIN
nie obsługuj wartości null w ten sam sposób. Uruchom to i zobacz, co się stanie:select 'Matched' as did_it_match where 1 not in (5, null);
Następnie usuńnull
i obserwuj magię.a IN (1,2,3,3,3,3,3)
to samo coa IN (1,2,3)
. Działa również wNOT IN
przeciwieństwie doa NOT IN (1,2,3,null,null,null,null)
(który zawsze nie zwraca wierszy, ponieważany_value != NULL
zawsze jest fałszem).Nieprzyjemnym obejściem, ale z pewnością wykonalnym jest użycie zapytania zagnieżdżonego. Utwórz tymczasową tabelę MYVALUES z kolumną w niej. Wstaw listę wartości do tabeli MYVALUES. Następnie wykonaj
Brzydka, ale realna alternatywa, jeśli lista wartości jest bardzo duża.
Ta technika ma tę dodatkową zaletę, że potencjalnie lepsze plany zapytań optymalizatora (sprawdź stronę pod kątem wielu wartości, skanuj tabele tylko raz zamiast tego raz na wartość itp.) Może zaoszczędzić na narzutach, jeśli baza danych nie buforuje przygotowanych instrukcji. Twoje „WSTAWKI” musiałyby być wykonane wsadowo, a tabela MYVALUES może wymagać ulepszenia, aby zapewnić minimalne blokowanie lub inne wysokie zabezpieczenia.
źródło
Ograniczenia operatora in () są źródłem wszelkiego zła.
Działa w trywialnych przypadkach i można go rozszerzyć o „automatyczne generowanie przygotowanego zestawienia”, jednak zawsze ma swoje ograniczenia.
Podejście in () może być wystarczające w niektórych przypadkach, ale nie jest odporne na rakiety :)
Rakietowym rozwiązaniem jest przekazanie dowolnej liczby parametrów w osobnym wywołaniu (na przykład przez przekazanie zbioru parametrów), a następnie wyświetlenie (lub w jakikolwiek inny sposób) reprezentacji ich w języku SQL i użycie tam, gdzie jest kryteria
Wariant brutalnej siły jest tutaj http://tkyte.blogspot.hu/2006/06/varying-in-lists.html
Jednak jeśli możesz używać PL / SQL, ten bałagan może stać się całkiem czysty.
Następnie możesz przekazać dowolną liczbę identyfikatorów klientów oddzielonych przecinkami w parametrze i:
Sztuką jest tutaj:
Widok wygląda następująco:
gdzie aux_in_list.getpayload odnosi się do oryginalnego ciągu wejściowego.
Możliwym podejściem byłoby przekazywanie tablic pl / sql (obsługiwanych tylko przez Oracle), jednak nie można ich używać w czystym SQL, dlatego zawsze potrzebny jest krok konwersji. Konwersji nie można wykonać w języku SQL, więc przecież przekazywanie clob ze wszystkimi parametrami w ciągu i konwertowanie go w widoku jest najbardziej wydajnym rozwiązaniem.
źródło
Oto jak rozwiązałem to we własnej aplikacji. Idealnie byłoby użyć StringBuilder zamiast + dla Strings.
Zastosowanie zmiennej takiej jak x powyżej zamiast konkretnych liczb bardzo pomaga, jeśli zdecydujesz się zmienić zapytanie w późniejszym czasie.
źródło
Nigdy tego nie próbowałem, ale czy .setArray () zrobiłby to, czego szukasz?
Aktualizacja : najwyraźniej nie. Wydaje się, że setArray działa tylko z java.sql.Array, który pochodzi z kolumny ARRAY, która została pobrana z poprzedniego zapytania, lub podzapytania z kolumną ARRAY.
źródło
Moje obejście:
Teraz możesz użyć jednej zmiennej, aby uzyskać pewne wartości w tabeli:
Tak przygotowane oświadczenie może być:
Pozdrowienia,
Javier Ibanez
źródło
Podejrzewam, że można (przy użyciu podstawowej manipulacji ciągiem) wygenerować ciąg zapytania w
PreparedStatement
celu uzyskania liczby?
pasującej do liczby elementów na liście.Oczywiście, jeśli to robisz, jesteś o krok od wygenerowania giganta powiązanego
OR
w zapytaniu, ale bez odpowiedniej liczby?
w ciągu zapytania nie widzę, jak inaczej możesz to obejść.źródło
Możesz użyć metody setArray, jak wspomniano w tym javadoc :
źródło
Możesz użyć
Collections.nCopies
do wygenerowania zbioru symboli zastępczych i dołączenia ich za pomocąString.join
:źródło
Oto kompletne rozwiązanie w Javie, aby stworzyć dla Ciebie przygotowaną instrukcję:
źródło
Wiosna umożliwia przekazywanie pliku java.util.Lists do obiektu NamedParameterJdbcTemplate , który automatyzuje generowanie (?,?,?, ...,?) Odpowiednio do liczby argumentów.
W przypadku Oracle ten post na blogu omawia użycie oracle.sql.ARRAY (Connection.createArrayOf nie działa z Oracle). W tym celu musisz zmodyfikować instrukcję SQL:
Funkcja tabeli wyroczni przekształca przekazaną tablicę w tabelę o wartości użytecznej w
IN
instrukcji.źródło
spróbuj użyć funkcji instr?
następnie
Wprawdzie jest to trochę brudny hack, ale zmniejsza możliwości zastrzyku sql. W każdym razie działa w wyroczni.
źródło
Sormula obsługuje operator SQL IN, umożliwiając dostarczenie obiektu java.util.Collection jako parametru. Tworzy przygotowane oświadczenie z? dla każdego z elementów kolekcji. Zobacz przykład 4 (w przykładzie SQL jest komentarzem wyjaśniającym, co zostało utworzone, ale nie jest używane przez Sormulę).
źródło
zamiast używać
użyj instrukcji Sql jako
i
lub użyj procedury składowanej byłoby to najlepsze rozwiązanie, ponieważ instrukcje SQL będą kompilowane i przechowywane na serwerze DataBase
źródło
Natknąłem się na szereg ograniczeń związanych z przygotowanym oświadczeniem:
Spośród proponowanych rozwiązań wybrałbym takie, które nie zmniejsza wydajności zapytań i sprawia, że mniej jest zapytań. To będzie numer 4 (grupowanie kilku zapytań) z linku @Don lub określenie wartości NULL dla niepotrzebnych „?” znaki zaproponowane przez @Vladimir Dyuzhev
źródło
Właśnie opracowałem dla tego opcję specyficzną dla PostgreSQL. Jest to trochę hack i ma swoje zalety i wady oraz ograniczenia, ale wydaje się, że działa i nie ogranicza się do konkretnego języka programowania, platformy lub sterownika PG.
Sztuczka polega oczywiście na znalezieniu sposobu na przekazanie zbioru wartości o dowolnej długości jako jednego parametru i spowodowanie, aby db rozpoznał je jako wiele wartości. Rozwiązaniem, które pracuję, jest skonstruowanie łańcucha rozdzielanego z wartości w kolekcji, przekazanie go jako jednego parametru i użycie string_to_array () z wymaganym rzutowaniem dla PostgreSQL, aby właściwie z niego skorzystać.
Więc jeśli chcesz wyszukać „foo”, „bla” i „abc”, możesz połączyć je razem w jeden ciąg znaków: „foo, bla, abc”. Oto prosty SQL:
Oczywiście zmieniłbyś jawną rzutowanie na cokolwiek, co chcesz, aby wynikowa tablica wartości była - int, tekst, uuid itp. A ponieważ funkcja przyjmuje jedną wartość ciągu (lub dwie, jak sądzę, jeśli chcesz dostosować separator) również), możesz przekazać go jako parametr w przygotowanej instrukcji:
Jest to nawet wystarczająco elastyczne, aby obsługiwać takie rzeczy jak porównania LIKE:
Ponownie, bez wątpienia jest to hack, ale działa i pozwala nadal korzystać ze wstępnie skompilowanych przygotowanych instrukcji, które przyjmują * ahem * dyskretne parametry, z towarzyszącymi korzyściami bezpieczeństwa i (być może) wydajnością. Czy jest to wskazane i faktycznie skuteczne? Oczywiście to zależy, ponieważ parsowanie ciągów i być może przesyłanie odbywa się jeszcze przed uruchomieniem zapytania. Jeśli spodziewasz się wysłać trzy, pięć, kilkadziesiąt wartości, na pewno jest to w porządku. Kilka tysięcy? Tak, może nie tak bardzo. YMMV, obowiązują ograniczenia i wyłączenia, bez gwarancji wyraźnej lub dorozumianej.
Ale to działa.
źródło
Dla kompletności: tak długo, jak zestaw wartości nie jest zbyt duży, można również po prostu utworzyć łańcuch typu instrukcja
który możesz następnie przekazać do przygotowania (), a następnie użyć setXXX () w pętli, aby ustawić wszystkie wartości. Wygląda to na szczęście, ale wiele „dużych” systemów komercyjnych rutynowo robi takie rzeczy, dopóki nie osiągną limitów specyficznych dla DB, takich jak 32 KB (tak myślę) dla instrukcji w Oracle.
Oczywiście musisz upewnić się, że zestaw nigdy nie będzie nieuzasadniony duży, ani nie wychwytuj błędów, jeśli tak jest.
źródło
Podążając za pomysłem Adama. Ułóż przygotowaną instrukcję, wybierając opcję moja_kolumna z mojej_tabeli, w której kolumna_wyszukiwania w (#) Utwórz ciąg x i wypełnij go liczbą „?,?,?” w zależności od listy wartości Następnie zmień # w zapytaniu dla nowego ciągu x i wypełnij
źródło
Wygeneruj ciąg zapytania w PreparedStatement, aby liczba? Pasowała do liczby elementów na liście. Oto przykład:
źródło
StringBuilder
. Ale nie tak, jak myślisz. Podczas dekompilacjigenerateQsForIn
widać, że dla każdej iteracji pętli przydzielane są dwa noweStringBuilder
itoString
są one wywoływane dla każdego z nich.StringBuilder
Optymalizacja łapie tylko takie rzeczy"x" + i+ "y" + j
, ale nie wykracza poza jednym wyrażeniu.ps.setObject(1,items)
zamiast iteracji po liście, a następnie ustawieniaparamteres
?Istnieją różne alternatywne podejścia, których możemy użyć dla klauzuli IN w PreparedStatement.
Użyj NULL w zapytaniach PreparedStatement - Optymalna wydajność, działa świetnie, gdy znasz limit argumentów klauzuli IN. Jeśli nie ma limitu, możesz wykonywać zapytania wsadowo. Przykładowy fragment kodu to;
Możesz sprawdzić więcej szczegółów na temat tych alternatywnych podejść tutaj .
źródło
W niektórych sytuacjach wyrażenie regularne może pomóc. Oto przykład, który sprawdziłem na Oracle i działa.
Ale ma to wiele wad:
źródło
Po przeanalizowaniu różnych rozwiązań na różnych forach i nie znalezieniu dobrego rozwiązania, uważam, że poniższy hack, który wymyśliłem, jest najłatwiejszy do naśladowania i kodowania:
Przykład: Załóżmy, że masz wiele parametrów do przekazania w klauzuli „IN”. Wystarczy umieścić fikcyjny ciąg w klauzuli „IN”, powiedzmy, „PARAM” oznacza listę parametrów, które będą przychodzić w miejsce tego fikcyjnego ciągu.
Możesz zebrać wszystkie parametry w jedną zmienną String w kodzie Java. Można to zrobić w następujący sposób:
W naszym przypadku możesz dołączyć wszystkie parametry oddzielone przecinkami do jednej zmiennej String, „param1”.
Po zebraniu wszystkich parametrów w jednym ciągu możesz po prostu zastąpić tekst fikcyjny w zapytaniu, tj. „PARAM” w tym przypadku parametrem String, tj. Param1. Oto, co musisz zrobić:
Możesz teraz wykonać zapytanie za pomocą metody executeQuery (). Upewnij się tylko, że w zapytaniu nigdzie nie ma słowa „PARAM”. Możesz użyć kombinacji znaków specjalnych i alfabetów zamiast słowa „PARAM”, aby mieć pewność, że takie słowo nie pojawi się w zapytaniu. Mam nadzieję, że masz rozwiązanie.
Uwaga: Chociaż nie jest to przygotowane zapytanie, wykonuje ono pracę, którą chciałem wykonać w moim kodzie.
źródło
Dla kompletności i ponieważ nie widziałem, żeby ktokolwiek inny to sugerował:
Przed wdrożeniem którejkolwiek z powyższych skomplikowanych sugestii zastanów się, czy wstrzyknięcie SQL rzeczywiście stanowi problem w twoim scenariuszu.
W wielu przypadkach wartość podana do IN (...) jest listą identyfikatorów, które zostały wygenerowane w taki sposób, że możesz mieć pewność, że żaden zastrzyk nie jest możliwy ... (np. Wyniki poprzedniego wyboru some_id z some_table gdzie jakiś warunek.)
W takim przypadku możesz po prostu połączyć tę wartość i nie korzystać z usług lub przygotowanej instrukcji lub użyć ich do innych parametrów tego zapytania.
źródło
PreparedStatement nie zapewnia dobrego sposobu radzenia sobie z klauzulą SQL IN. Na http://www.javaranch.com/journal/200510/Journal200510.jsp#a2 „Nie można zastąpić rzeczy, które mają stać się częścią instrukcji SQL. Jest to konieczne, ponieważ jeśli sam SQL może się zmienić, właściwość sterownik nie może wstępnie skompilować instrukcji. Ma także ładny efekt uboczny, uniemożliwiając ataki polegające na wstrzyknięciu SQL. ” Skończyło się na następującym podejściu:
źródło
SetArray to najlepsze rozwiązanie, ale nie jest dostępne dla wielu starszych sterowników. W java8 można zastosować następujące obejście
To rozwiązanie jest lepsze niż inne brzydkie rozwiązania pętli while, w których ciąg zapytania jest budowany przez iteracje ręczne
źródło
To zadziałało dla mnie (psuedocode):
określić wiązanie:
źródło
Mój przykład dla baz danych SQLite i Oracle.
Pierwsza pętla For służy do tworzenia obiektów PreparedStatement.
Druga pętla For służy do podawania wartości parametrów parametrów PreparedStatement.
źródło
Moje obejście (JavaScript)
SearchTerms
to tablica zawierająca dane wejściowe / klucze / pola itpźródło