SQL-запрос для выполнения поиска по радиусу на основе широты и долготы - PullRequest
11 голосов
/ 13 ноября 2009

У нас есть таблица ресторанов, в которой для каждой строки есть данные по широте.

Нам нужно написать запрос, который выполняет поиск, чтобы найти все рестораны в пределах указанного радиуса, например, 1 миля, 5 миль и т. Д.

Для этого у нас есть следующий запрос:

***Parameters***

Longitude: -74.008680
Latitude: 40.711676
Radius: 1 mile

***Query***

SELECT *
FROM restaurant
WHERE (
POW( ( 69.1 * ( Longitude - -74.008680 ) * cos( 40.711676 / 57.3 ) ) , 2 ) + POW( ( 69.1 * ( Latitude - 40.711676 ) ) , 2 )
) < ( 1 *1 );

В таблице около 23 тыс. Строк. Размер результирующего набора иногда бывает странным, например, для поиска в 5,4 мили он возвращает 880 строк, а для 5,5 миль - 21 000 строк.

В этой таблице содержатся данные ресторана для nyc, поэтому реальное распределение не соответствует результирующему набору.

Вопрос: НИЧЕГО НЕПРАВИЛЬНО С этим запросом?

DB: MySQL, долгота: DECIMAL (10,6), широта: DECIMAL (10,6)

Ответы [ 4 ]

13 голосов
/ 13 ноября 2009

ЭТО НИЧЕГО НЕПРАВИЛЬНО запрос

По моему мнению, предложение WHERE будет медленным из-за математики, и использование функций в предложении WHERE не позволит базе данных использовать индекс для ускорения запроса - так что, по сути, вы изучите все ресторан в базе данных, и выполнять математику большого круга в каждой строке, каждый раз, когда вы делаете запрос.

Лично я бы вычислил координаты TopLeft и BottomRight квадрата (который нужно только грубо вычислить с помощью пифагора) со сторонами, равными искомому диапазону, а затем выполнил бы более сложный тест предложения WHERE на меньшее подмножество записей, которые находятся в пределах этого квадрата Lat / Long.

С индексом Lat & Long в базе данных запрос

WHERE     MyLat >= @MinLat AND MyLat <= @MaxLat
      AND MyLong >= @MinLong AND MyLong <= @MaxLong

должен быть очень эффективным

(Обратите внимание, что я не знаю конкретно MySQL, только о MS SQL)

3 голосов
/ 13 ноября 2009

Возможно, вы захотите создать индекс SPATIAL для своей таблицы, чтобы ускорить поиск.

Для этого добавьте в таблицу столбец POINT:

ALTER TABLE restaurant ADD coords POINT NOT NULL;

CREATE SPATIAL INDEX sx_restaurant_coords ON restaurant (coords);

SELECT  *
FROM    restaurant
WHERE   MBRContains(coords, LineString(Point(583734 - 1609, 4507223 - 1609), Point(583734 + 1609, 4507223 + 1609))
        AND GLength(LineString(Point(583734, 4507223), coords)) <= 1609

Вы должны хранить coords как UTM координаты в пределах одной зоны.

0 голосов
/ 08 декабря 2009

Используйте функцию, например, тот , который я разместил здесь .

Затем запросите ваши рестораны, например, чтобы получить все в радиусе 5 миль

select * from restaurants 
  where dbo.udf_Haversine(latitude, longitude, @lat, @long) < 5

Это хорошо работает с данными почтового индекса.

0 голосов
/ 20 ноября 2009

Если ваши данные находятся в базе данных сервера SQL, вы можете использовать это:

CREATE PROC up_FindZipCodesWithinRadius

    @ZipCode char(5) ,
    @GivenMileRadius int
AS
SET NOCOUNT ON

DECLARE @lat1 float, 
    @long1 float

SELECT  @lat1= latitude,
    @long1 = longitude 
FROM ZipSource
WHERE zipcode = @ZipCode

SELECT ZipCode ,DistanceInMiles
FROM
(
    SELECT  ZipCode,3958.75 * ( Atan(Sqrt(1 - power(((Sin(@Lat1/57.2958) * Sin(latitude/57.2958)) + 
            (Cos(@Lat1/57.2958) * Cos(latitude/57.2958) * Cos((longitude/57.2958) - (@Long1/57.2958)))), 2)) / 
            ((Sin(@Lat1/57.2958) * Sin(latitude/57.2958)) + (Cos(@Lat1/57.2958) * Cos(latitude/57.2958) * 
            Cos((longitude/57.2958) - (@Long1/57.2958)))))) as DistanceInMiles
FROM ZipSource
) a
WHERE a.DistanceInMiles <= @GivenMileRadius
--AND ZipCode <> @ZipCode
ORDER BY DistanceInMiles

GO

EXEC up_FindZipCodesWithinRadius '35085',20
GO

DROP PROC up_FindZipCodesWithinRadius
...