JOIN z dodatkowymi warunkami za pomocą Query Builder lub Eloquent

93

Próbuję dodać warunek za pomocą zapytania JOIN z Laravel Query Builder.

<?php

$results = DB::select('
       SELECT DISTINCT 
          *
          FROM 
             rooms 
                LEFT JOIN bookings  
                   ON rooms.id = bookings.room_type_id
                  AND (  bookings.arrival between ? and ?
                      OR bookings.departure between ? and ? )
          WHERE
                bookings.room_type_id IS NULL
          LIMIT 20',
    array('2012-05-01', '2012-05-10', '2012-05-01', '2012-05-10')
);

Wiem, że mogę używać surowych wyrażeń, ale wtedy pojawią się punkty wstrzyknięcia SQL. Wypróbowałem poniższe z Query Builder, ale wygenerowane zapytanie (i oczywiście wyniki zapytania) nie są tym, co zamierzałem:

$results = DB::table('rooms')
    ->distinct()
    ->leftJoin('bookings', function ($join) {
        $join->on('rooms.id', '=', 'bookings.room_type_id');
    })
    ->whereBetween('arrival', array('2012-05-01', '2012-05-10'))
    ->whereBetween('departure', array('2012-05-01', '2012-05-10'))
    ->where('bookings.room_type_id', '=', null)
    ->get();

Oto zapytanie wygenerowane przez Laravel:

select distinct * from `room_type_info`
    left join `bookings` 
on `room_type_info`.`id` = `bookings`.`room_type_id` 
where `arrival` between ? and ? 
    and `departure` between ? and ? 
    and `bookings`.`room_type_id` is null

Jak widać, dane wyjściowe zapytania nie mają struktury (szczególnie w zakresie JOIN). Czy można dodać dodatkowe warunki w JOIN?

Jak mogę zbudować to samo zapytanie używając Laravel's Query Builder (jeśli to możliwe). Czy lepiej jest użyć Eloquent, czy powinien pozostać przy DB :: select?

dede
źródło

Odpowiedzi:

139
$results = DB::table('rooms')
                     ->distinct()
                     ->leftJoin('bookings', function($join)
                         {
                             $join->on('rooms.id', '=', 'bookings.room_type_id');
                             $join->on('arrival','>=',DB::raw("'2012-05-01'"));
                             $join->on('arrival','<=',DB::raw("'2012-05-10'"));
                             $join->on('departure','>=',DB::raw("'2012-05-01'"));
                             $join->on('departure','<=',DB::raw("'2012-05-10'"));
                         })
                     ->where('bookings.room_type_id', '=', NULL)
                     ->get();

Nie do końca jestem pewien, czy klauzulę between można dodać do złączenia w laravel.

Uwagi:

  • DB::raw() instruuje Laravel, aby nie odkładał cytatów.
  • Przekazując zamknięcie metod łączenia, możesz dodać do niego więcej warunków łączenia, on()dodasz ANDwarunek i orOn()dodasz ORwarunek.
Abishek
źródło
Dziękuję za odpowiedź. Niestety wygenerowane zapytanie jest nieco inne, ponieważ warunek sprzężenia ma teraz and arrival >= ? and arrival <= ? and departure >= ? and departure <= ?zamiast tego AND ( r.arrival between ? and ? OR r.departure between ? and ? )(proszę zwrócić uwagę na ORklauzulę). Spowoduje to dodanie dodatkowych wierszy, których nie powinno tam być. Wydaje mi się, że nie jest możliwe wygenerowanie wszystkich typów warunków łączenia za pomocą QB.
dede
1
Właściwie zauważyłem, że? nie jest dodawany do klauzul ON. Te wartości w ON nie są sparametryzowane i nie są chronione przed iniekcją SQL. Wysłałem pytanie, próbując znaleźć rozwiązanie.
prograhammer
3
to nie odpowiadało na pierwotne pytanie, które zawierało klauzulę lub, która została zignorowana w tej odpowiedzi.
ionescho
Idealne rozwiązanie. Pamiętaj tylko, aby używać DB :: raw podczas używania wartości, w przeciwnym razie Laravel (przynajmniej w 5.6.29) dodaje cudzysłowy i „łamie” cel.
Adrian Hernandez-Lopez
35

Możesz powielić te nawiasy w lewym złączeniu:

LEFT JOIN bookings  
               ON rooms.id = bookings.room_type_id
              AND (  bookings.arrival between ? and ?
                  OR bookings.departure between ? and ? )

jest

->leftJoin('bookings', function($join){
    $join->on('rooms.id', '=', 'bookings.room_type_id');
    $join->on(DB::raw('(  bookings.arrival between ? and ? OR bookings.departure between ? and ? )'), DB::raw(''), DB::raw(''));
})

Będziesz wtedy musiał ustawić powiązania później za pomocą "setBindings", jak opisano w tym poście SO: Jak powiązać parametry z surowym zapytaniem DB w Laravel, które jest używane w modelu?

Nie jest ładna, ale działa.

rickywiens
źródło
32

Jeśli masz jakieś parametry, możesz to zrobić.

    $results = DB::table('rooms')
    ->distinct()
    ->leftJoin('bookings', function($join) use ($param1, $param2)
    {
        $join->on('rooms.id', '=', 'bookings.room_type_id');
        $join->on('arrival','=',DB::raw("'".$param1."'"));
        $join->on('arrival','=',DB::raw("'".$param2."'"));

    })
    ->where('bookings.room_type_id', '=', NULL)
    ->get();

a następnie zwróć zapytanie

zwraca $ wyników;

vroldan
źródło
23

Przykład zapytania sql, taki jak ten

LEFT JOIN bookings  
    ON rooms.id = bookings.room_type_id
    AND (bookings.arrival = ?
        OR bookings.departure = ?)

Laravel dołącz z wieloma warunkami

->leftJoin('bookings', function($join) use ($param1, $param2) {
    $join->on('rooms.id', '=', 'bookings.room_type_id');
    $join->on(function($query) use ($param1, $param2) {
        $query->on('bookings.arrival', '=', $param1);
        $query->orOn('departure', '=',$param2);
    });
})
Majbah Habib
źródło
11

Używam laravel5.2 i możemy dodawać połączenia z różnymi opcjami, możesz je modyfikować zgodnie z wymaganiami.

Option 1:    
    DB::table('users')
            ->join('contacts', function ($join) {
                $join->on('users.id', '=', 'contacts.user_id')->orOn(...);//you add more joins here
            })// and you add more joins here
        ->get();

Option 2:
    $users = DB::table('users')
        ->join('contacts', 'users.id', '=', 'contacts.user_id')
        ->join('orders', 'users.id', '=', 'orders.user_id')// you may add more joins
        ->select('users.*', 'contacts.phone', 'orders.price')
        ->get();

option 3:
    $users = DB::table('users')
        ->leftJoin('posts', 'users.id', '=', 'posts.user_id')
        ->leftJoin('...', '...', '...', '...')// you may add more joins
        ->get();
Abid Ali
źródło
3

Istnieje różnica między zapytaniami nieprzetworzonymi a standardowymi selekcjami (między metodami DB::rawi DB::select).

Możesz robić, co chcesz, używając a DB::selecti po prostu upuszczając ?symbol zastępczy, podobnie jak w przypadku przygotowanych instrukcji (tak naprawdę to robi).

Mały przykład:

$results = DB::select('SELECT * FROM user WHERE username=?', ['jason']);

Drugi parametr to tablica wartości, które zostaną użyte do zastąpienia symboli zastępczych w zapytaniu od lewej do prawej.

Jason Lewis
źródło
Czy to oznacza, że ​​parametry są automatycznie zmieniane (zapisywane z iniekcji SQL)?
dede
2
Tak, to tylko opakowanie dla oświadczeń przygotowanych przez PDO.
Jason Lewis
Czy istnieje sposób na użycie tej techniki w klauzuli ON w lewym złączeniu? Zobacz moje pytanie tutaj . Próbowałem po wewnętrznej stronie mojego lewego zamknięcia, $join->on('winners.year','=',DB::select('?',array('2013'));ale nie zadziałało.
prograhammer
nie do końca tak elokwentne ma działać. równie dobrze możesz po prostu zrobić DB :: raw () i zrobić to sam
mydoglixu