Search code examples
phpmysqllaravelpdolaravel-query-builder

SQL query with Laravel Eloquent and named bindings : mixed named and positional parameters


I'm trying to execute the following query. Note I've to use named parameters in this query. I don't understand the issue because it seems I'm only using named parameters. How can I make named parameters work ?

Note : the query is fully functionnal when I'm executing it in DB or in laravel with a simple DB::table(users)->select( $sql_with_values_inside )

I'm new to Laravel so maybe I'm doing it wrong.

Query

$latitude = (float)$latitude;
$longitude = (float)$longitude;
$radius = (float)$radius;

$sql = '`firstname`,`lastname`,`email`,
     ST_X(`coordinates_public`) AS latitude,
     ST_Y(`coordinates_public`) AS longitude,
     (6371 * ACOS(COS(RADIANS(:latitude)) * COS(RADIANS(ST_Y(coordinates_private))) * COS(RADIANS(ST_X(coordinates_private)) - RADIANS(:longitude)) + SIN(RADIANS(:latitude)) * SIN(RADIANS(ST_Y(coordinates_private))))) AS distance';

$washers = DB::table('users')
    ->selectRaw($sql, ['latitude' => $latitude,'longitude'=> $longitude,'radius'=> $radius])
    ->whereRaw('MBRContains ( LineString (
        Point (
            :longitude + :radius / (111.320 * COS(RADIANS(:latitude))),
            :latitude + :radius / 111.133
        ),
        Point (
            :longitude - :radius / (111.320 * COS(RADIANS(:latitude))),
            :latitude - :radius / 111.133
        )), `coordinates_private`
    )', ['latitude' => $latitude,'longitude'=> $longitude,'radius'=> $radius])
    ->having('distance < :radius', ['radius'=> $radius])
    ->orderBy('distance')
    ->get();

Error

Illuminate\Database\QueryException: SQLSTATE[HY093]:
Invalid parameter number: mixed named and positional parameters (SQL: select `firstname`,`lastname`,`email`,
    ST_X(`coordinates_public`) AS latitude,
    ST_Y(`coordinates_public`) AS longitude,
    (6371 * ACOS(COS(RADIANS(:latitude)) * COS(RADIANS(ST_Y(coordinates_private))) * COS(RADIANS(ST_X(coordinates_private)) - RADIANS(:longitude)) + SIN(RADIANS(:latitude)) * SIN(RADIANS(ST_Y(coordinates_private))))) AS distance from `users` where MBRContains (
    LineString
        (
        Point (
            :longitude + :radius / (111.320 * COS(RADIANS(:latitude))),
            :latitude + :radius / 111.133
        ),
        Point (
            :longitude - :radius / (111.320 * COS(RADIANS(:latitude))),
            :latitude - :radius / 111.133
        )
     ),
    `coordinates_private`) having `distance < :radius` = 4.8312518210935 order by `distance` asc)

Solution

  • From the PHP manual:

    You must include a unique parameter marker for each value you wish to pass in to the statement when you call PDOStatement::execute(). You cannot use a named parameter marker of the same name more than once in a prepared statement, unless emulation mode is on.

    So you cannot use a named parameter marker of the same name.

    There are two solutions:

    1. Use the different named binding key for same value:
    $sql = "(6371 * ACOS(COS(RADIANS(:lat)) * COS(RADIANS(ST_Y(coordinates_private))) * COS(RADIANS(ST_X(coordinates_private)) - RADIANS(:long)) + SIN(RADIANS(lat2)) * SIN(RADIANS(ST_Y(coordinates_private))))) AS distance";
    ...
    ->selectRaw($sql, ["lat" => $latitude, "long" => $longitude, "lat2" => $latitude])
    
    
    1. Use ? instead of named binding and binding an array:
    $sql = '(6371 * ACOS(COS(RADIANS(?)) * COS(RADIANS(ST_Y(coordinates_private))) * COS(RADIANS(ST_X(coordinates_private)) - RADIANS(?)) + SIN(RADIANS(?)) * SIN(RADIANS(ST_Y(coordinates_private))))) AS distance';
    ...
    ->selectRaw($sql, [$latitude, $longitude, $latitude])