Un JOIN con condiciones adicionales usando Query Builder o Eloquent

93

Estoy tratando de agregar una condición usando una consulta JOIN con 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')
);

Sé que puedo usar Raw Expressions, pero luego habrá puntos de inyección SQL. Probé lo siguiente con Query Builder, pero la consulta generada (y obviamente, los resultados de la consulta) no son lo que pretendía:

$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 es la consulta generada por 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 puede ver, la salida de la consulta no tiene la estructura (especialmente bajo el alcance JOIN). ¿Es posible agregar condiciones adicionales en JOIN?

¿Cómo puedo construir la misma consulta usando el Generador de consultas de Laravel (si es posible)? ¿Es mejor usar Eloquent, o debería quedarse con DB :: select?

dede
fuente

Respuestas:

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

No estoy muy seguro de si la cláusula entre se puede agregar a la combinación en laravel.

Notas:

  • DB::raw() le indica a Laravel que no vuelva a poner comillas.
  • Al pasar un cierre a los métodos de combinación, puede agregar más condiciones de combinación, on()agregará ANDcondición y orOn()agregará ORcondición.
Abishek
fuente
Gracias por la respuesta. Desafortunadamente, la consulta generada es ligeramente diferente ya que la condición de unión ahora tiene en su and arrival >= ? and arrival <= ? and departure >= ? and departure <= ?lugar AND ( r.arrival between ? and ? OR r.departure between ? and ? )(observe la ORcláusula). Esto agrega filas adicionales al resultado que no debería estar allí. Supongo que no es posible generar todos los tipos de condiciones en unir usando QB.
dede
1
En realidad me di cuenta de que el? no se agrega a las cláusulas ON. Esos valores en ON no están parametrizados y no están protegidos de la inyección SQL. Publiqué una pregunta tratando de encontrar la solución.
Programhammer
3
esto no respondió a la pregunta original que tenía una cláusula o que se ignoró en esta respuesta.
ionescho
Solución perfecta. Solo recuerde usar DB :: raw cuando use un valor, de lo contrario Laravel (al menos en 5.6.29) agregue comillas y "rompa" el objetivo.
Adrian Hernandez-Lopez
35

Puede replicar esos corchetes en la combinación izquierda:

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

es

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

Luego, tendrá que configurar los enlaces más tarde usando "setBindings" como se describe en esta publicación SO: ¿Cómo enlazar parámetros a una consulta de base de datos sin procesar en Laravel que se usa en un modelo?

No es bonito pero funciona.

Rickywiens
fuente
32

Si tiene algunos parámetros, puede hacer esto.

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

y luego devuelve tu consulta

devolver $ resultados;

vroldan
fuente
23

La muestra de consulta SQL como esta

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

Laravel se une con múltiples condiciones

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

Estoy usando laravel5.2 y podemos agregar uniones con diferentes opciones, puede modificar según sus requisitos.

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

Hay una diferencia entre las consultas sin procesar y las selecciones estándar (entre los métodos DB::rawy DB::select).

Puede hacer lo que quiera usando un DB::selecty simplemente colocando el ?marcador de posición como lo hace con las declaraciones preparadas (en realidad es lo que está haciendo).

Un pequeño ejemplo:

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

El segundo parámetro es una matriz de valores que se utilizará para reemplazar los marcadores de posición en la consulta de izquierda a derecha.

Jason Lewis
fuente
¿Significa eso que los parámetros se escapan automáticamente (salvo de la inyección SQL)?
dede
2
Sí, es solo un contenedor para declaraciones preparadas con DOP.
Jason Lewis
¿Hay alguna forma de usar esa técnica en una cláusula ON en una combinación izquierda? Vea mi pregunta aquí . Intenté dentro de mi cierre leftJoin para hacerlo, $join->on('winners.year','=',DB::select('?',array('2013'));pero no funcionó.
Programhammer
no es así como elocuente está diseñado para funcionar. también podría hacer un DB :: raw () y hacerlo usted mismo
mydoglixu