Przekazywanie tablicy do zapytania za pomocą klauzuli WHERE

314

Biorąc pod uwagę tablicę identyfikatorów $galleries = array(1,2,5), chcę mieć zapytanie SQL, które wykorzystuje wartości tablicy w jej klauzuli WHERE, takie jak:

SELECT *
FROM galleries
WHERE id = /* values of array $galleries... eg. (1 || 2 || 5) */

Jak mogę wygenerować ten ciąg zapytania do użycia z MySQL?

Braiam
źródło
5
@trante ten jest najstarszy (2009).
Fabián
Czy istnieje podobne rozwiązanie problemu, takiego jak WYBIERZ * Z TABELI GDZIE NAZWA JAK („ABC%” lub „ABD%” LUB…)
Eugine Joseph

Odpowiedzi:

332

STRZEC SIĘ! Ta odpowiedź zawiera poważną lukę w iniekcji SQL . NIE używaj próbek kodu przedstawionych tutaj, nie upewniając się, że wszelkie zewnętrzne dane wejściowe są dezynfekowane.

$ids = join("','",$galleries);   
$sql = "SELECT * FROM galleries WHERE id IN ('$ids')";
Flavius ​​Stef
źródło
7
Identyfikatory są wciąż listą cytowaną, więc pojawia się na przykład jako „WHERE id IN ('1,2,3,4')”. Musisz podać każdy identyfikator osobno lub porzucić cytaty w nawiasach.
Rob
22
Dodam tylko ostrzeżenie, które $galleriesnależy zweryfikować przed tą instrukcją! Przygotowane instrukcje nie obsługują tablic AFAIK, więc jeśli przyzwyczaisz się do wiązania zmiennych, możesz tutaj łatwo wprowadzić SQL.
leemes
3
dla początkujących w PHP, takich jak ja, czy ktoś może wyjaśnić lub wskazać mi zasób, aby wyjaśnić, dlaczego jest to podatne na zastrzyki i jak należy to zrobić poprawnie, aby temu zapobiec? Co się stanie, jeśli lista identyfikatorów zostanie wygenerowana z zapytania bezpośrednio przed uruchomieniem następnego zapytania, czy nadal jest niebezpieczne?
ministe2003
3
@ ministe2003 Wyobraź sobie, że $galleriesmiał następującą wartość: array('1); SELECT password FROM users;/*'). Jeśli tego nie zrobisz, zapytanie zostanie odczytane SELECT * FROM galleries WHERE id IN (1); SELECT password FROM users;/*). Zmień nazwy tabeli i kolumny na coś, co masz w bazie danych i spróbuj tego zapytania, sprawdź wyniki. Jako wynik znajdziesz listę haseł, a nie listę galerii. W zależności od sposobu wyprowadzania danych lub tego, co robi skrypt z tablicą nieoczekiwanych danych, dane te mogą zostać wyświetlone w widoku publicznym ... ouch.
Chris Baker
18
Na zadane pytanie jest to całkowicie ważna i bezpieczna odpowiedź. Ktokolwiek narzeka, nie jest bezpieczny - co powiesz na umowę, w której ustawiłem ten konkretny kod zgodnie z tym, $galleriesco podano w pytaniu, a ty wykorzystujesz go przy użyciu wspomnianej „podatności na wstrzyknięcie sql”. Jeśli nie możesz - płacisz mi 200 USD. Co ty na to?
zerkms
307

Korzystanie z PDO: [1]

$in = join(',', array_fill(0, count($ids), '?'));
$select = <<<SQL
    SELECT *
    FROM galleries
    WHERE id IN ($in);
SQL;
$statement = $pdo->prepare($select);
$statement->execute($ids);

Korzystanie z MySQLi [2]

$in = join(',', array_fill(0, count($ids), '?'));
$select = <<<SQL
    SELECT *
    FROM galleries
    WHERE id IN ($in);
SQL;
$statement = $mysqli->prepare($select);
$statement->bind_param(str_repeat('i', count($ids)), ...$ids);
$statement->execute();
$result = $statement->get_result();

Wyjaśnienie:

Użyj IN()operatora SQL , aby sprawdzić, czy wartość istnieje na danej liście.

Ogólnie wygląda to tak:

expr IN (value,...)

Możemy zbudować wyrażenie do umieszczenia wewnątrz ()naszej tablicy. Zauważ, że w nawiasie musi znajdować się co najmniej jedna wartość, inaczej MySQL zwróci błąd; oznacza to upewnienie się, że nasza tablica wejściowa ma co najmniej jedną wartość. Aby zapobiec atakom typu SQL injection, najpierw wygeneruj ?dla każdego elementu wejściowego, aby utworzyć sparametryzowane zapytanie. Tutaj zakładam, że tablica zawierająca twoje identyfikatory nazywa się $ids:

$in = join(',', array_fill(0, count($ids), '?'));

$select = <<<SQL
    SELECT *
    FROM galleries
    WHERE id IN ($in);
SQL;

Podana tablica wejściowa trzech elementów $selectbędzie wyglądać następująco:

SELECT *
FROM galleries
WHERE id IN (?, ?, ?)

Ponownie zauważ, że ?dla każdego elementu w tablicy wejściowej znajduje się znak . Następnie użyjemy PDO lub MySQLi do przygotowania i wykonania zapytania, jak wspomniano powyżej.

Używanie IN()operatora z ciągami znaków

Łatwo jest zmieniać ciągi znaków i liczby całkowite ze względu na związane parametry. W przypadku ChNP zmiana nie jest wymagana; dla MySQLi zmień str_repeat('i',na, str_repeat('s',jeśli chcesz sprawdzić ciągi.

[1]: Pominąłem sprawdzanie błędów pod kątem zwięzłości. Musisz sprawdzić typowe błędy dla każdej metody bazy danych (lub ustawić sterownik DB tak, aby generował wyjątki).

[2]: Wymaga PHP 5.6 lub wyższej. Znowu pominąłem pewne sprawdzanie błędów pod kątem zwięzłości.

Levi Morrison
źródło
7
Co robi ... $ ids? Pojawia się komunikat „błąd składni, nieoczekiwany”. ”.
Marcel
Widzę je, używam MySQLi i mam php 5.6
Marcel
1
Jeśli masz na myśli $statement->bind_param(str_repeat('i', count($ids)), ...$ids);, to ...jest rozszerzenie identyfikatora z tablicy do wielu parametrów. Jeśli masz na myśli expr IN (value,...)to oznacza to, że może być więcej wartości np WHERE id IN (1, 3, 4). Musi być tylko jeden.
Levi Morrison
1
Byłem zdezorientowany, czym jest <<<, ale znalazłem referencję: php.net/manual/en/…
Tsangares
1
Oto także odniesienie do ...: wiki.php.net/rfc/argument_unpacking
Tsangares
58

ints:

$query = "SELECT * FROM `$table` WHERE `$column` IN(".implode(',',$array).")";

smyczki:

$query = "SELECT * FROM `$table` WHERE `$column` IN('".implode("','",$array)."')";
użytkownik542568
źródło
1
Dlaczego „\”? Powiedz mi
zloctb
29

Zakładając, że wcześniej odpowiednio odkażysz swoje dane wejściowe ...

$matches = implode(',', $galleries);

Następnie dostosuj zapytanie:

SELECT *
FROM galleries
WHERE id IN ( $matches ) 

Podaj wartości odpowiednio w zależności od zestawu danych.

AvatarKava
źródło
Próbowałem tego, co proponujesz, ale właśnie pobrałem pierwszą kluczową wartość. Wiem, że to nie ma sensu, ale jeśli zrobię to na przykładzie user542568, to cholerstwo działa.
Samuel Ramzan
12

Posługiwać się:

select id from galleries where id in (1, 2, 5);

Działa prosta for eachpętla.

Sposób Flaviusa / AvatarKava jest lepszy, ale upewnij się, że żadna z wartości tablicy nie zawiera przecinków.

Matthew Flaschen
źródło
9

Jako odpowiedź Flaviusa Stefa możesz intval()się upewnić, że wszystkie idsą wartościami int:

$ids = join(',', array_map('intval', $galleries));  
$sql = "SELECT * FROM galleries WHERE id IN ($ids)";
Van-Duyet Le
źródło
7

Dla MySQLi z funkcją Escape:

$ids = array_map(function($a) use($mysqli) { 
    return is_string($a) ? "'".$mysqli->real_escape_string($a)."'" : $a;
  }, $ids);
$ids = join(',', $ids);  
$result = $mysqli->query("SELECT * FROM galleries WHERE id IN ($ids)");

W przypadku PDO z przygotowanym wyciągiem:

$qmarks = implode(',', array_fill(0, count($ids), '?'));
$sth = $dbh->prepare("SELECT * FROM galleries WHERE id IN ($qmarks)");
$sth->execute($ids);
artoodetoo
źródło
jest to miłe, krótkie i pozwala uniknąć podatności na wstawienie kodu! +1
Stephan Richter
MySQLi również przygotował wyciągi. Nie unikaj wprowadzania danych, jest to potencjalnie wciąż podatne na wstrzyknięcie SQL.
Dharman
6

Powinniśmy zająć się lukami w iniekcji SQL i pustym stanem . Zajmę się obydwoma jak poniżej.

Dla czystego tablicy numerycznej użyciu odpowiedniego mianowicie konwersji typu intvallub floatvallub doublevalnad każdym elementem. Dla typów ciągów, mysqli_real_escape_string()które można również zastosować do wartości liczbowych, jeśli chcesz. MySQL dopuszcza zarówno liczby, jak i warianty dat jako ciąg znaków .

Aby odpowiednio uniknąć wartości przed przekazaniem do zapytania, utwórz funkcję podobną do:

function escape($string)
{
    // Assuming $db is a link identifier returned by mysqli_connect() or mysqli_init()
    return mysqli_real_escape_string($db, $string);
}

Taka funkcja najprawdopodobniej byłaby już dostępna w twojej aplikacji, a może już ją stworzyłeś.

Wyczyść tablicę ciągów, np .:

$values = array_map('escape', $gallaries);

Tablica numeryczna może być zdezynfekowana za pomocą intvallub floatvallub doublevalzamiast tego odpowiednio:

$values = array_map('intval', $gallaries);

Następnie w końcu zbuduj warunek zapytania

$where  = count($values) ? "`id` = '" . implode("' OR `id` = '", $values) . "'" : 0;

lub

$where  = count($values) ? "`id` IN ('" . implode("', '", $values) . "')" : 0;

Ponieważ tablica może być czasami pusta, $galleries = array();należy zauważyć, że IN ()nie zezwala na pustą listę. Można również użyć ORzamiast tego, ale problem pozostaje. Tak więc powyższa kontrola count($values)ma zapewnić to samo.

I dodaj go do końcowego zapytania:

$query  = 'SELECT * FROM `galleries` WHERE ' . $where;

TIP : Jeśli chcesz wyświetlić wszystkie rekordy (bez filtrowania) w przypadku pustej tablicy zamiast ukrywając wszystkie wiersze, po prostu wymień 0 z 1 w trójoperandowy fałszywej strony.

Izhar Aazmi
źródło
Aby moje rozwiązanie było $query = 'SELECT * FROM galleries WHERE ' . (count($gallaries) ? "id IN ('" . implode("', '", array_map('escape', $gallaries)) . "')" : 0);
jednowarstwowe
5

Bezpieczniej

$galleries = array(1,2,5);
array_walk($galleries , 'intval');
$ids = implode(',', $galleries);
$sql = "SELECT * FROM galleries WHERE id IN ($ids)";
Filipe
źródło
5

Biblioteka SafeMySQL płk Shrapnela dla PHP zawiera sparametryzowane symbole zastępcze w sparametryzowanych zapytaniach i zawiera kilka wygodnych symboli zastępczych do pracy z tablicami. Symbol ?azastępczy rozwija tablicę do rozdzielonej przecinkami listy ciągów znaków *.

Na przykład:

$someArray = [1, 2, 5];
$galleries = $db->getAll("SELECT * FROM galleries WHERE id IN (?a)", $someArray);

* Zauważ, że ponieważ MySQL wykonuje automatyczny przymus typu, nie ma znaczenia, że ​​SafeMySQL skonwertuje powyższe identyfikatory na ciągi - nadal otrzymasz poprawny wynik.

Mark Amery
źródło
4

Możemy użyć tej klauzuli „WHERE id IN”, jeśli odpowiednio przefiltrujemy tablicę wejściową. Coś takiego:

$galleries = array();

foreach ($_REQUEST['gallery_id'] as $key => $val) {
    $galleries[$key] = filter_var($val, FILTER_SANITIZE_NUMBER_INT);
}

Podobnie jak w poniższym przykładzie:wprowadź opis zdjęcia tutaj

$galleryIds = implode(',', $galleries);

Tj. Teraz powinieneś bezpiecznie używać $query = "SELECT * FROM galleries WHERE id IN ({$galleryIds})";

Supratim Roy
źródło
@ levi-morrison opublikował o wiele lepsze rozwiązanie tego problemu.
Supratim Roy,
4

Możesz mieć stolik texts (T_ID (int), T_TEXT (text))i stoliktest (id (int), var (varchar(255)))

W insert into test values (1, '1,2,3') ;następujących wierszy wyjściowe będzie z tekstami stole, gdzie T_ID IN (1,2,3):

SELECT * FROM `texts` WHERE (SELECT FIND_IN_SET( T_ID, ( SELECT var FROM test WHERE id =1 ) ) AS tm) >0

W ten sposób możesz zarządzać prostą relacją bazy danych n2m bez dodatkowej tabeli i używając tylko SQL bez potrzeby używania PHP lub innego języka programowania.

SERJOU
źródło
3

Więcej przykładu:

$galleryIds = [1, '2', 'Vitruvian Man'];
$ids = array_filter($galleryIds, function($n){return (is_numeric($n));});
$ids = implode(', ', $ids);

$sql = "SELECT * FROM galleries WHERE id IN ({$ids})";
// output: 'SELECT * FROM galleries WHERE id IN (1, 2)'

$statement = $pdo->prepare($sql);
$statement->execute();
Ricardo Canelas
źródło
2

Oprócz użycia zapytania IN masz do tego dwie opcje, ponieważ w zapytaniu IN istnieje ryzyko podatności na iniekcję SQL. Możesz użyć pętli, aby uzyskać dokładne dane, lub możesz użyć zapytania z literą OR

1. SELECT *
      FROM galleries WHERE id=1 or id=2 or id=5;


2. $ids = array(1, 2, 5);
   foreach ($ids as $id) {
      $data[] = SELECT *
                    FROM galleries WHERE id= $id;
   }
Gaurav Singh
źródło
2

Bezpieczny sposób bez PDO:

$ids = array_filter(array_unique(array_map('intval', (array)$ids)));

if ($ids) {
    $query = 'SELECT * FROM `galleries` WHERE `id` IN ('.implode(',', $ids).');';
}
  • (array)$idsRzuć $idszmienną na tablicę
  • array_map Przekształć wszystkie wartości tablic na liczby całkowite
  • array_unique Usuń powtarzające się wartości
  • array_filter Usuń wartości zerowe
  • implode Połącz wszystkie wartości z wyborem IN
Lito
źródło
1

Ponieważ oryginalne pytanie dotyczy tablicy liczb i używam tablicy ciągów, nie mogłem sprawić, by podane przykłady działały.

Odkryłem, że każdy ciąg musi być zamknięty w pojedynczych cudzysłowach, aby pracować z IN()funkcją.

Oto moje rozwiązanie

foreach($status as $status_a) {
        $status_sql[] = '\''.$status_a.'\'';
    }
    $status = implode(',',$status_sql);

$sql = mysql_query("SELECT * FROM table WHERE id IN ($status)");

Jak widać, pierwsza funkcja otacza każdą zmienną tablicową, single quotes (\')a następnie wszczepia tablicę.

UWAGA: $statusnie zawiera pojedynczych cudzysłowów w instrukcji SQL.

Prawdopodobnie jest lepszy sposób dodawania cytatów, ale to działa.

RJaus
źródło
Lub$filter = "'" . implode("','",$status) . "'";
Alejandro Salamanca Mazuelo
1
Jest to podatne na wstrzyknięcia.
Mark Amery
Gdzie ucieka sznurki? Na przykład 'wewnątrz ciągu? Wrażliwość na SQL Injection. Użyj PDO :: quote lub mysqli_real_escape_string.
18C
1

Poniżej znajduje się metoda, której użyłem, używając PDO z nazwanymi symbolami zastępczymi dla innych danych. Aby pokonać iniekcję SQL, filtruję tablicę, aby zaakceptować tylko wartości, które są liczbami całkowitymi, i odrzucam wszystkie inne.

$owner_id = 123;
$galleries = array(1,2,5,'abc');

$good_galleries = array_filter($chapter_arr, 'is_numeric');

$sql = "SELECT * FROM galleries WHERE owner=:OWNER_ID AND id IN ($good_galleries)";
$stmt = $dbh->prepare($sql);
$stmt->execute(array(
    "OWNER_ID" => $owner_id,
));

$data = $stmt->fetchAll(PDO::FETCH_ASSOC);
kojow7
źródło
-3

Podstawowe metody zapobiegania wstrzykiwaniu SQL:

  • Używaj przygotowanych instrukcji i sparametryzowanych zapytań
  • Unikanie znaków specjalnych w niebezpiecznej zmiennej

Użycie przygotowanych instrukcji i sparametryzowanych zapytań jest uważane za lepszą praktykę, ale jeśli wybierzesz metodę znaków specjalnych, możesz wypróbować mój przykład poniżej.

Możesz wygenerować zapytania, array_mapdodając pojedynczy cytat do każdego z elementów w $galleries:

$galleries = array(1,2,5);

$galleries_str = implode(', ',
                     array_map(function(&$item){
                                   return "'" .mysql_real_escape_string($item) . "'";
                               }, $galleries));

$sql = "SELECT * FROM gallery WHERE id IN (" . $galleries_str . ");";

Wygenerowany $ sql var będzie:

SELECT * FROM gallery WHERE id IN ('1', '2', '5');

Uwaga: mysql_real_escape_string , jak opisano w jego dokumentacji tutaj , był przestarzały w PHP 5.5.0 i został usunięty w PHP 7.0.0. Zamiast tego należy użyć rozszerzenia MySQLi lub PDO_MySQL. Zobacz także MySQL: wybór przewodnika po interfejsie API i powiązane FAQ, aby uzyskać więcej informacji. Alternatywy dla tej funkcji obejmują:

  • mysqli_real_escape_string ()

  • PDO :: quote ()

David
źródło
4
Nie tylko nie dodaje to nic nowego w porównaniu z innymi odpowiedziami, ale jest podatne na wstrzykiwanie, pomimo że zaakceptowana odpowiedź zawierała ostrzeżenia o wstrzyknięciu SQL od lat.
Mark Amery