如何在数据库中搜索关键字并返回位于50英里内的数据

时间:2017-07-19 16:34:47

标签: php mysql pdo

我已经能够在数据库中搜索关键字并将结果作为json返回给应用程序,但现在我正在尝试 准备一个语句,该语句将返回位于选定英里数内的数据,但此语句返回null。 我在$ sql = $ db-> prepare语句和$ sql-> execute语句中做错了什么?

FUNCTION TO GET CARD FROM SEARCH WORD, LONGITUDE, LATITUDE, AMD MILES CALLED FROM GetCards.php   
public function getAllCards($word, $longitude, $latitude, $miles) {

//Connect to db using the PDO not PHP
$db = new PDO('mysql:host=localhost;dbname=XXXXX', 'XXXXX', 'XXXXX');

//Here we prepare the SELECT statement from the search, word place holder :word, longitude place holder :longitude, latitude place holder :latitude, miles place holder :miles
$sql = $db->prepare('SELECT * FROM carddbtable WHERE businessNameDB=:word OR lastNameDB=:word OR firstKeywordDB=:word OR secondKeywordDB=:word OR thirdKeywordDB=:word OR fourthKeywordDB=:word OR fithKeywordDB=:word AND latitudeLocateDB, longitudeLocateDB, 3956 * 2 * 
      ASIN(SQRT( POWER(SIN(($latitude - latitudeLocateDB)*pi()/180/2),2)
      +COS($latitude*pi()/180 )*COS(latitudeLocateDB*pi()/180)
      *POWER(SIN(($longitude-longitudeLocateDB)*pi()/180/2),2))) 
      as distance FROM $carddbtable WHERE 
      longitudeLocateDB between ($longitude-$miles/cos(radians($latitude))*69) 
      and ($longitude+$miles/cos(radians($latitude))*69) 
      and latitudeLocateDB between ($latitude-($miles/69)) 
      and ($latitude+($miles/69)) 
      having distance < $miles ORDER BY distance limit 100');


//We execute the $sql with the search word variable $word, $longitude, $latitude, $miles
$sql->execute([':word' => $word, ':longitude' => $longitude, ':latitude' => $latitude, ':miles' => $miles]);

//Looping through the results
while ($row = $sql->fetch(PDO::FETCH_ASSOC)) {

//Store all return rows in $returnArray
$returnArray[] = $row;

//Print to screen
// echo json_encode($row). "<br>"."<br>";
}

//Feedback results
return $returnArray;

}   

1 个答案:

答案 0 :(得分:1)

尝试使用此代码。我无法测试它,但如果你一步一步地去耐心它会起作用。如果您有错误或遇到问题,请向我们提供反馈。可能是,sql语句中LIMIT的参数在第一次尝试时不起作用。告诉我们。另外,我使用以下代码进行Haversine公式和矩形计算:Reverse Geocoding with it loaded into MySQL database?,因为它与您的几乎相同。

一些建议:

  • 尝试转到OOP。
  • 始终应用异常处理并激活错误报告和 处理。我只投了Exception给了你一般观点。 通常你应该学习如何抛出和处理SPL (标准PHP库)类型也是如此。
  • 始终使用prepared statements(我知道你已经这样做了; - )。
  • 始终在PHP Manual中读取返回的PHP函数,以便正确应用句柄案例(例外,bool等)。

资源:

祝你好运!

具有PDO准备语句的哈弗森公式&amp;异常处理

index.php(主页):

<?php

require_once 'configs.php';
require_once 'functions.php';
require_once 'geolocationFunctions.php';

// Activate error reporting (only on development).
activateErrorReporting();

try {
    // Create db connection.
    $connection = createConnection(
            MYSQL_HOST
            , MYSQL_DATABASE
            , MYSQL_USERNAME
            , MYSQL_PASSWORD
            , MYSQL_PORT
            , MYSQL_CHARSET
    );

    $cardsInRadius = getCardsInRadius($connection, $word, $longitude, $latitude, $radius, $limit = 100);

    // For testing purposes.
    printData($cardsInRadius, TRUE);

    closeConnection($connection);
} catch (PDOException $pdoException) {
    // On development.
    printData($pdoException, TRUE);

    // On production.
    // echo $pdoException->getMessage();

    exit();
} catch (Exception $exception) {
    // On development.
    printData($exception, TRUE);

    // On production.
    // echo $exception->getMessage();

    exit();
}

geolocationFunctions.php(包含在主页中):

<?php

/*
 * ---------------------
 * Geolocation functions
 * ---------------------
 */

/**
 * Search the for a keyword and return data located within given radius.
 * 
 * @param PDO $connection Connection instance.
 * @param string $word Keyword to lookup.
 * @param double $longitude Longitude value to lookup.
 * @param double $latitude Latitude value to lookup.
 * @param integer $radius Distance radius (in miles) having lat/long as center point.
 * @param integer $limit [optional] Number of records to return.
 * @throws Exception
 */
function getCardsInRadius($connection, $word, $longitude, $latitude, $radius, $limit = 100) {
    /*
     * Create a rectangle in which the circle with the given radius will be defined.
     *  >> 1° of latitude ~= 69 miles
     *  >> 1° of longitude ~= cos(latitude) * 69
     */
    $rectLong1 = $longitude - $radius / abs(cos(deg2rad($latitude)) * 69);
    $rectLong2 = $longitude + $radius / abs(cos(deg2rad($latitude)) * 69);
    $rectLat1 = $latitude - ($radius / 69);
    $rectLat2 = $latitude + ($radius / 69);

    // Approximate the circle inside the rectangle.
    $distance = sprintf('3956 * 2 * ASIN(SQRT(POWER(SIN((%s - latitudeLocateDB) * pi()/180 / 2), 2) + COS(%s * pi()/180) * COS(latitudeLocateDB * pi()/180) * POWER(SIN((%s - longitudeLocateDB) * pi()/180 / 2), 2) ))'
            , $latitude
            , $latitude
            , $longitude
    );

    // Sql statement.
    $sql = sprintf('SELECT 
                        *,
                        %s AS distance 
                    FROM carddbtable 
                    WHERE 
                        (
                            businessNameDB = :businessNameDB 
                            OR lastNameDB = :lastNameDB 
                            OR firstKeywordDB = :firstKeywordDB 
                            OR secondKeywordDB = :secondKeywordDB 
                            OR thirdKeywordDB = :thirdKeywordDB 
                            OR fourthKeywordDB = :fourthKeywordDB 
                            OR fithKeywordDB = :fithKeywordDB 
                        )
                        AND longitudeLocateDB BETWEEN :rectLong1 AND :rectLong2 
                        AND latitudeLocateDB BETWEEN :rectLat1 AND :rectLat2 
                    HAVING distance < :distance 
                    ORDER BY distance 
                    LIMIT :limit'
            , $distance
    );

    // Prepare and check sql statement (returns PDO statement).
    $statement = $connection->prepare($sql);
    if (!$statement) {
        throw new Exception('The SQL statement can not be prepared!');
    }

    // Bind values to sql statement parameters.
    $statement->bindValue(':businessNameDB', $word, getInputParameterDataType($word));
    $statement->bindValue(':lastNameDB', $word, getInputParameterDataType($word));
    $statement->bindValue(':firstKeywordDB', $word, getInputParameterDataType($word));
    $statement->bindValue(':secondKeywordDB', $word, getInputParameterDataType($word));
    $statement->bindValue(':thirdKeywordDB', $word, getInputParameterDataType($word));
    $statement->bindValue(':fourthKeywordDB', $word, getInputParameterDataType($word));
    $statement->bindValue(':fithKeywordDB', $word, getInputParameterDataType($word));
    $statement->bindValue(':rectLong1', $rectLong1, getInputParameterDataType($rectLong1));
    $statement->bindValue(':rectLong2', $rectLong2, getInputParameterDataType($rectLong2));
    $statement->bindValue(':rectLat1', $rectLat1, getInputParameterDataType($rectLat1));
    $statement->bindValue(':rectLat2', $rectLat2, getInputParameterDataType($rectLat2));
    $statement->bindValue(':distance', $radius, getInputParameterDataType($radius));
    $statement->bindValue(':limit', $limit, getInputParameterDataType($limit));

    // Execute and check PDO statement.
    if (!$statement->execute()) {
        throw new Exception('The PDO statement can not be executed!');
    }

    // Fetch person details.
    $fetchedData = $statement->fetchAll(PDO::FETCH_ASSOC);
    if (!$fetchedData) {
        throw new Exception('Fetching data failed!');
    }

    return $fetchedData;
}

configs.php(包含在主页中):

<?php

/*
 * ----------------
 * Database configs
 * ----------------
 */

define('MYSQL_HOST', '...');
define('MYSQL_PORT', '3306');
define('MYSQL_DATABASE', '...');
define('MYSQL_CHARSET', 'utf8');
define('MYSQL_USERNAME', '...');
define('MYSQL_PASSWORD', '...');

functions.php(包含在主页中):

<?php

/*
 * ---------------------
 * Data access functions
 * ---------------------
 */

/**
 * Create a new db connection.
 * 
 * @param string $host Host.
 * @param string $dbname Database name.
 * @param string $username Username.
 * @param string $password Password.
 * @param string $port [optional] Port.
 * @param array $charset [optional] Character set.
 * @param array $options [optional] Driver options.
 * @return PDO Db connection.
 */
function createConnection($host, $dbname, $username, $password, $port = '3306', $charset = 'utf8', $options = array(
    PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
    PDO::ATTR_EMULATE_PREPARES => false,
    PDO::ATTR_PERSISTENT => true,
)) {
    $dsn = getDsn($host, $dbname, $port, $charset);
    $connection = new PDO($dsn, $username, $password);
    foreach ($options as $key => $value) {
        $connection->setAttribute($key, $value);
    }
    return $connection;
}

/**
 * Create a mysql DSN string.
 * 
 * @param string $host Host.
 * @param string $dbname Database name.
 * @param string $port [optional] Port.
 * @param array $charset [optional] Character set.
 * @return string DSN string.
 */
function getDsn($host, $dbname, $port = '3306', $charset = 'utf8') {
    $dsn = sprintf('mysql:host=%s;port=%s;dbname=%s;charset=%s'
            , $host
            , $port
            , $dbname
            , $charset
    );
    return $dsn;
}

/**
 * Close a db connection.
 * 
 * @param PDO $connection Db connection.
 * @return void
 */
function closeConnection($connection) {
    $connection = NULL;
}

/**
 * Get the data type of a binding value.
 * 
 * @param mixed $value Binding value.
 * @return mixed Data type of the binding value.
 */
function getInputParameterDataType($value) {
    $dataType = PDO::PARAM_STR;
    if (is_int($value)) {
        $dataType = PDO::PARAM_INT;
    } elseif (is_bool($value)) {
        $dataType = PDO::PARAM_BOOL;
    }
    return $dataType;
}

/*
 * ---------------
 * Print functions
 * ---------------
 */

/**
 * Print data on screen.
 * 
 * @param mixed $data Data to print.
 * @param bool $preformatted Print preformatted if TRUE, print normal otherwise.
 * @return void
 */
function printData($data, $preformatted = FALSE) {
    if ($preformatted) {
        echo '<pre>' . print_r($data, true) . '</pre>';
    } else {
        echo $data;
    }
}

/*
 * -------------------------
 * Error reporting functions
 * -------------------------
 */

/**
 * Toggle error reporting.
 * 
 * @param integer $level Error level.
 * @param bool $display_errors Display errors if TRUE, hide them otherwise.
 * @return void
 */
function activateErrorReporting($level = E_ALL, $display_errors = TRUE) {
    error_reporting($level);
    ini_set('display_errors', ($display_errors ? 1 : 0));
}

编辑1:针对MySQL注入的附加措施:

像这样申请qoute()

function getCardsInRadius($connection, ...) {
    $longitude = $connection->quote($longitude);
    $latitude = $connection->quote($latitude);

    //...
}

编辑2:解决不良近似值:

在原始代码中,您使用了cos(radians($latitude))*69

...
between ($longitude-$miles/cos(radians($latitude))*69) 
    and ($longitude+$miles/cos(radians($latitude))*69)
...

在我的代码中,我使用了abs(cos(deg2rad($latitude)) * 69)。我记得我故意选择了这个:

$rectLong1 = $longitude - $radius / abs(cos(deg2rad($latitude)) * 69);
$rectLong2 = $longitude + $radius / abs(cos(deg2rad($latitude)) * 69);

似乎这可能是问题所在。首先,将deg2rad替换为radians。它应该是:

$rectLong1 = $longitude - $radius / abs(cos(radians($latitude)) * 69);
$rectLong2 = $longitude + $radius / abs(cos(radians($latitude)) * 69);

如果仍然无效,请删除abs。它应该是:

$rectLong1 = $longitude - $radius / (cos(radians($latitude)) * 69);
$rectLong2 = $longitude + $radius / (cos(radians($latitude)) * 69);

注意括号位置。

编辑3 - 可变测量单位:

应用变量测量单位(公里,英里等)作为功能参数,并相应地改变地球半径。如何将$measurementUnit注入函数是你的一部分。是的,地球半径差不多是3959英里。

function getCardsInRadius($connection, $word, $longitude, $latitude, $radius, $limit = 100, $measurementUnit = 'miles') {

    //...

    switch ($measurementUnit) {
        case 'miles':
            $earthRadius = 3959;
            break;

        case 'km':
            $earthRadius = 6371;
            break;

        default: // miles
            $earthRadius = 3959;
            break;
    }

    $distance = sprintf('%s * 2 * ...'
            , $earthRadius
            , $latitude
            , $latitude
            , $longitude
    );

    //...
}