Une jointure avec des conditions supplémentaires utilisant Query Builder ou Eloquent

93

J'essaye d'ajouter une condition en utilisant une requête JOIN avec 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')
);

Je sais que je peux utiliser des expressions brutes, mais il y aura des points d'injection SQL. J'ai essayé ce qui suit avec Query Builder mais la requête générée (et évidemment, les résultats de la requête) ne sont pas ce que je voulais:

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

Voici la requête générée par 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

Comme vous pouvez le voir, la sortie de la requête n'a pas la structure (en particulier sous la portée JOIN). Est-il possible d'ajouter des conditions supplémentaires dans le cadre du JOIN?

Comment puis-je créer la même requête en utilisant le Générateur de requêtes de Laravel (si possible) Est-il préférable d'utiliser Eloquent, ou devrait-il rester avec DB :: select?

dede
la source

Réponses:

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

Je ne sais pas si la clause between peut être ajoutée à la jointure dans laravel.

Remarques:

  • DB::raw() ordonne à Laravel de ne pas remettre de devis.
  • En passant une fermeture aux méthodes de jointure, vous pouvez y ajouter plus de conditions de jointure, on()ajouter une ANDcondition et orOn()ajouter une ORcondition.
Abishek
la source
Merci d'avoir répondu. Malheureusement, la requête générée est légèrement différente puisque la condition de jointure a maintenant à la and arrival >= ? and arrival <= ? and departure >= ? and departure <= ?place AND ( r.arrival between ? and ? OR r.departure between ? and ? )(veuillez noter la ORclause). Cela ajoute des lignes supplémentaires au résultat qui ne devraient pas être là. Je suppose qu'il n'est pas possible de générer tous les types de conditions dans la jointure en utilisant QB.
Dede
1
En fait, j'ai remarqué que le? n'est pas ajouté dans les clauses ON. Ces valeurs dans ON ne sont pas paramétrées et ne sont pas protégées contre l'injection SQL. J'ai posté une question pour essayer de trouver la solution.
prograhammer
3
cela ne répondait pas à la question d'origine qui contenait une clause ou ignorée dans cette réponse.
ionescho
Solution parfaite. N'oubliez pas d'utiliser DB :: raw lorsque vous utilisez une valeur, sinon Laravel (au moins en 5.6.29) ajoute des guillemets et "brise" le but.
Adrian Hernandez-Lopez
35

Vous pouvez répliquer ces crochets dans la jointure de gauche:

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

est

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

Vous devrez ensuite définir les liaisons plus tard en utilisant "setBindings" comme décrit dans cet article SO: Comment lier des paramètres à une requête DB brute dans Laravel qui est utilisée sur un modèle?

Ce n'est pas joli mais ça marche.

Rickywiens
la source
32

Si vous avez des paramètres, vous pouvez le faire.

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

puis renvoyez votre requête

retourne $ results;

vroldan
la source
23

L'exemple de requête SQL comme celui-ci

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

Laravel rejoint avec plusieurs conditions

->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
la source
11

J'utilise laravel5.2 et nous pouvons ajouter des jointures avec différentes options, vous pouvez modifier selon vos besoins.

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
la source
3

Il y a une différence entre les requêtes brutes et les sélections standard (entre les méthodes DB::rawet DB::select).

Vous pouvez faire ce que vous voulez en utilisant un DB::selectet en déposant simplement l' ?espace réservé, comme vous le faites avec des instructions préparées (c'est en fait ce que cela fait).

Un petit exemple:

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

Le deuxième paramètre est un tableau de valeurs qui sera utilisé pour remplacer les espaces réservés dans la requête de gauche à droite.

Jason Lewis
la source
Cela signifie-t-il que les paramètres sont automatiquement échappés (sauf à partir de l'injection SQL)?
dédée
2
Oui, c'est juste un wrapper pour les instructions préparées par PDO.
Jason Lewis
Existe-t-il un moyen d'utiliser cette technique dans une clause ON dans une jointure gauche? Voir ma question ici . J'ai essayé à l'intérieur de ma fermeture à gauche, $join->on('winners.year','=',DB::select('?',array('2013'));mais je n'ai pas fonctionné.
prograhammer
ce n'est pas vraiment comment éloquent est conçu pour fonctionner. vous pourriez aussi bien faire un DB :: raw () et le faire vous
mydoglixu