Haversine公式中的联接运算

Ash*_*eef 4 php mysql optimization join haversine

我正在PHP中实现Haversine公式,如下所示

$result=mysqli_query($mysqli,"SELECT *,( 6371 * acos( cos( radians({$lat}) ) * cos( radians( `latitude` ) ) * cos( radians( `longitude` ) -radians({$lon}) ) +sin( radians({$lat}) ) * sin( radians( `latitude` ) ) ) ) AS distance FROM `places` HAVING distance <= {$radius} ORDER BY distance ASC") or die(mysqli_error($mysqli));
Run Code Online (Sandbox Code Playgroud)

在Haversine访存循环中,我有一个查询,它遍历Haversine的结果以选择与Haversine公式返回的ID匹配的记录。查询如下。

 while($row = mysqli_fetch_assoc($result)) 

    {
   $rest_time=$row['id'];

$result1=mysqli_query($mysqli,"SELECT * FROM my_friends  WHERE personal_id='".$personal_id."' AND id='".$rest_time."'") or die(mysqli_error($mysqli)); 

//Some operations here
    }
Run Code Online (Sandbox Code Playgroud)

如何执行Join操作将这些查询混合为一个查询?从优化的角度来看,这样做是否明智?如果第二个表有5万个用户,而第一个表有近1000条记录呢?

exu*_*sum 5

您在此处执行的所有行上的任何操作都将因为有这么多记录而变慢。

您需要做的是利用索引的优势。要使用索引,它必须是一个简单的查询,而不是函数的结果(当前)。

通过进行半径搜索,您正在做的是围绕一个点制作一个圆,在制作圆之前使用一些三角函数,我们可以得出以下结论

圈出两个方块

其中S1是内部最大的正方形,S2是外部最小的正方形。

现在我们可以计算出这两个正方形的尺寸,并且S2的任何外部都将被索引指向,而S1的任何内部都将被索引指向,只剩下其中的小区域,现在需要使用慢速方法进行查找。 。

如果您需要到该点的距离,请忽略S1部分(因为圆内部的所有内容都需要haversine函数)作为注释,尽管圆内部的所有内容都需要它,但并非每个点都在该距离之内,因此这两个WHERE子句都是仍然需要

因此,让我们使用单位圆计算这些点 单位圆

function getS1S2($latitude, $longitude, $kilometer)
{
    $radiusOfEarthKM  = 6371;
    $latitudeRadians  = deg2rad($latitude);
    $longitudeRadians = deg2rad($longitude);
    $distance         = $kilometer / $radiusOfEarthKM;

    $deltaLongitude = asin(sin($distance) / cos($latitudeRadians));

    $bounds = new \stdClass();

    // these are the outer bounds of the circle (S2)
    $bounds->minLat  = rad2deg($latitudeRadians  - $distance);
    $bounds->maxLat  = rad2deg($latitudeRadians  + $distance);
    $bounds->minLong = rad2deg($longitudeRadians - $deltaLongitude);
    $bounds->maxLong = rad2deg($longitudeRadians + $deltaLongitude);

    // and these are the inner bounds (S1)
    $bounds->innerMinLat  = rad2deg($latitudeRadians  + $distance       * cos(5 * M_PI_4));
    $bounds->innerMaxLat  = rad2deg($latitudeRadians  + $distance       * sin(M_PI_4));
    $bounds->innerMinLong = rad2deg($longitudeRadians + $deltaLongitude * sin(5 * M_PI_4));
    $bounds->innerMaxLong = rad2deg($longitudeRadians + $deltaLongitude * cos(M_PI_4));

    return $bounds;
}
Run Code Online (Sandbox Code Playgroud)

现在您的查询变为

SELECT 
  *
FROM
  `places` 
HAVING p.nlatitude BETWEEN {$bounds->minLat} 
  AND {$bounds->maxLat} 
  AND p.nlongitude BETWEEN {$bounds->minLong} 
  AND {$bounds->maxLong} 
  AND (
    (
      p.nlatitude BETWEEN {$bounds->innerMinLat} 
      AND {$bounds->innerMaxLat} 
      AND p.nlongitude BETWEEN {$bounds->innerMinLong} 
      AND {$bounds->innerMaxLong}
    ) 
    OR (
      6371 * ACOS(
        COS(RADIANS({ $lat })) * COS(RADIANS(`latitude`)) * COS(
          RADIANS(`longitude`) - RADIANS({ $lon })
        ) + SIN(RADIANS({ $lat })) * SIN(RADIANS(`latitude`))
      )
    )
  )) <= {$radius} 
ORDER BY distance ASC 
Run Code Online (Sandbox Code Playgroud)

重要

上面的文字出于可读性考虑,请确保正确避开这些值/最好将其参数化

这样就可以利用索引,并允许更快地进行连接

添加联接成为

SELECT 
  *
FROM
  `places` p
  INNER JOIN my_friends f ON f.id = p.id
WHERE   p.latitude BETWEEN {$bounds->minLat} 
  AND {$bounds->maxLat} 
  AND p.longitude BETWEEN {$bounds->minLong} 
  AND {$bounds->maxLong} 
  AND (
    (
      p.latitude BETWEEN {$bounds->innerMinLat} 
      AND {$bounds->innerMaxLat} 
      AND p.longitude BETWEEN {$bounds->innerMinLong} 
      AND {$bounds->innerMaxLong}
    ) 
    OR (
      6371 * ACOS(
        COS(RADIANS({ $lat })) * COS(RADIANS(`latitude`)) * COS(
          RADIANS(`longitude`) - RADIANS({ $lon })
        ) + SIN(RADIANS({ $lat })) * SIN(RADIANS(`latitude`))
      )
    )
  )  <= {$radius} 
  AND f.personal_id = {$personal_id}
ORDER BY distance ASC 
Run Code Online (Sandbox Code Playgroud)

重要

上面的文字出于可读性考虑,请确保正确避开这些值/最好将其参数化

假设您具有正确的索引,则此查询应保持快速并允许您进行联接。

看上面的代码我不知道personal_id来自哪里,所以就这样离开了

如果您需要查询的距离,则可以删除S1正方形

    (
      p.latitude BETWEEN {$bounds->innerMinLat} 
      AND {$bounds->innerMaxLat} 
      AND p.longitude BETWEEN {$bounds->innerMinLong} 
      AND {$bounds->innerMaxLong}
    ) 
Run Code Online (Sandbox Code Playgroud)

并移动第二部分 OR

  6371 * ACOS(
    COS(RADIANS({ $lat })) * COS(RADIANS(`latitude`)) * COS(
      RADIANS(`longitude`) - RADIANS({ $lon })
    ) + SIN(RADIANS({ $lat })) * SIN(RADIANS(`latitude`))
  )
Run Code Online (Sandbox Code Playgroud)

回到选择,仍然使用S2。

我还要确保删除查询6371中的“幻数”是公里的地球半径