UM JOIN com condições adicionais usando o Query Builder ou Eloquent

95

Estou tentando adicionar uma condição usando uma consulta JOIN com o 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')
);

Sei que posso usar expressões brutas, mas haverá pontos de injeção de SQL. Tentei o seguinte com o Query Builder, mas a consulta gerada (e, obviamente, os resultados da consulta) não é o que eu pretendia:

$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();

Esta é a consulta gerada pelo 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

Como você pode ver, a saída da consulta não tem a estrutura (especialmente no escopo JOIN). É possível adicionar condições adicionais no JOIN?

Como posso construir a mesma consulta usando o Query Builder do Laravel (se possível) É melhor usar o Eloquent, ou deveria ficar com DB :: select?

dede
fonte

Respostas:

143
$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();

Não tenho certeza se a cláusula between pode ser adicionada à junção no laravel.

Notas:

  • DB::raw() instrui o Laravel a não colocar aspas de volta.
  • Ao passar uma closure para métodos de junção, você pode adicionar mais condições de junção a ela, on()irá adicionar ANDcondição e orOn()irá adicionar ORcondição.
Abishek
fonte
Obrigado pela resposta. Infelizmente, a consulta gerada é ligeiramente diferente, pois a condição de junção agora têm and arrival >= ? and arrival <= ? and departure >= ? and departure <= ?vez AND ( r.arrival between ? and ? OR r.departure between ? and ? )(por favor observe a ORcláusula). Isso adiciona linhas adicionais ao resultado que não deveriam estar lá. Eu acho que não é possível gerar todos os tipos de condições em join usando QB.
dede
1
Na verdade, percebi que o? não é adicionado às cláusulas ON. Esses valores no ON não são parametrizados e não estão protegidos da injeção de SQL. Eu postei uma pergunta tentando descobrir a solução.
programahammer
3
isso não respondeu à pergunta original que tinha uma cláusula ou que foi ignorada nesta resposta.
ionescho de
Solução perfeita. Apenas lembre-se de usar DB :: raw ao usar um valor, caso contrário, o Laravel (pelo menos em 5.6.29) adiciona aspas e "quebra" o objetivo.
Adrian Hernandez-Lopez
35

Você pode replicar esses colchetes na junção à esquerda:

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

é

->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(''));
})

Você então terá que definir os vínculos mais tarde usando "setBindings" conforme descrito neste post do SO: Como vincular parâmetros a uma consulta de banco de dados bruta no Laravel que é usada em um modelo?

Não é bonito, mas funciona.

Rickywiens
fonte
32

Se você tiver alguns parâmetros, pode fazer isso.

    $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();

e depois retornar sua consulta

retornar $ resultados;

Vroldan
fonte
23

O exemplo de consulta sql como este

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

Laravel junta-se com múltiplas condições

->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
fonte
11

Estou usando o laravel5.2 e podemos adicionar associações com diferentes opções, que você pode modificar de acordo com sua necessidade.

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
fonte
3

Há uma diferença entre as consultas brutas e as seleções padrão (entre os métodos DB::rawe DB::select).

Você pode fazer o que quiser usando um DB::selecte simplesmente inserindo o ?espaço reservado, da mesma forma que faz com as instruções preparadas (na verdade, é o que está fazendo).

Um pequeno exemplo:

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

O segundo parâmetro é uma matriz de valores que será usada para substituir os espaços reservados na consulta da esquerda para a direita.

Jason Lewis
fonte
Isso significa que os parâmetros são escapados automaticamente (salvar da injeção SQL)?
dede
2
Sim, é apenas um invólucro para instruções preparadas PDO.
Jason Lewis
Existe alguma maneira de usar essa técnica em uma cláusula ON em uma junção à esquerda? Veja minha pergunta aqui . Eu tentei dentro da minha junção esquerda fechar, $join->on('winners.year','=',DB::select('?',array('2013'));mas não funcionou.
programahammer
não é assim que o eloqüente foi projetado para funcionar. você pode muito bem fazer um DB :: raw () e torná-lo você mesmo
mydoglixu