Исключить подзапрос для среднего числового значения - PullRequest
1 голос
/ 06 мая 2010

Квест

Запрос выбирает все точки, начинающиеся с "Ванкувер", и находятся в пределах 5-минутной зоны от центра всех локаций, начинающихся с "Ванкувер".Например, Ванкуверский Южный Фрейзер, Ванкуверский Фэрвью и Ванкуверский Баллантри Плэйс W имеют широту и долготу в пределах 5 минут от их средней широты и долготы.Широты и долготы хранятся в виде (4915, 12311) целочисленных пар (что означает 49,15'N и 123,11'W).

Код SQL

Следующая мерзость SQL делаетхитрость:

SELECT
  NAME
FROM
 STATION
WHERE
      DISTRICT_ID = '110'
  AND NAME LIKE 'Vancouver%'
  AND LATITUDE BETWEEN
    (SELECT round((min(LATITUDE) + max(LATITUDE)) / 2)-5 FROM STATION WHERE DISTRICT_ID = '110' AND NAME LIKE 'Vancouver%')
    and
    (SELECT round((min(LATITUDE) + max(LATITUDE)) / 2)+5 FROM STATION WHERE DISTRICT_ID = '110' AND NAME LIKE 'Vancouver%')
  AND LONGITUDE BETWEEN
    (SELECT round((min(LONGITUDE) + max(LONGITUDE)) / 2)-5 FROM STATION WHERE DISTRICT_ID = '110' AND NAME LIKE 'Vancouver%')
    and
    (SELECT round((min(LONGITUDE) + max(LONGITUDE)) / 2)+5 FROM STATION WHERE DISTRICT_ID = '110' AND NAME LIKE 'Vancouver%')
ORDER BY
  LATITUDE

Вопрос

Как можно упростить этот запрос для удаления избыточности без использования представления?

Ограничения

База данных MySQL, но ANSI SQL всегда хорош.

Спасибо!

Ответы [ 5 ]

2 голосов
/ 06 мая 2010

Прежде всего, обратите внимание, что ваше определение «в течение 5 минут друг от друга» не определяет единственное решение и что ваш (MIN () + MAX ()) / 2 не является средним, а просто средним значением min и max. Возможно, вы ищете AVG () в своих подзапросах.

Во-вторых, вы не получаете результаты в течение 5 секунд друг от друга, но записи, долгота и широта которых не превышают 10 секунд (что по диагонали может быть ближе к 14).

В MySQL вы можете использовать переменные сеанса, такие как:

SET @avg_lat := (SELECT round(avg(LATITUDE)) FROM STATION WHERE DISTRICT_ID = '110' AND NAME LIKE 'Vancouver%');
SET @avg_long := (SELECT round(avg(LONGITUDE)) FROM STATION WHERE DISTRICT_ID = '110' AND NAME LIKE 'Vancouver%');

SELECT
  NAME
FROM
 STATION
WHERE
  DISTRICT_ID = '110'
  AND pow(LATITUDE-@avg_lat,2)+pow(LONGITUDE-@avg_long,2)<25
ORDER BY
  LATITUDE

Даже если в этом нет необходимости (как в запросе, написанном выше, обе переменные встречаются только один раз).

РЕДАКТИРОВАТЬ: Упс, неправильно прочитал вопрос. Это радиус центра - поэтому замените 25 на 100 (и пока решите, хотите ли вы использовать меньше или равно). Кроме того, если центр является центром ограничительной рамки, то ваша (min () + max ()) / 2 - правильная формула, а не мое предложение. Тем не менее «центр всех мест» немного расплывчат, поэтому я оставляю свой ответ (его легко изменить).

РЕДАКТИРОВАТЬ2: Только что заметил, что единицы в моем запросе не являются правильными, если широта хранится в сантиметрах, то сравнение должно быть тоже с сантиминутами (10 * 100) ^ 2 = 1000000

И, наконец, ваше решение придерживаться (min () + max ()) / 2 приведет к случаям, когда у вас может быть одна строка, далеко идущая к максимальному и минимальному значениям, из-за которой запрос может пропустить любой из результатов. (и может случиться, что обычно все местоположения с похожими названиями расположены рядом друг с другом, но нередко бывает, что другое местоположение начинается с того же имени, которое является изолированным местом вдали от конгломерата местоположений)

Что касается 5-минутной области, если быть точным, лучше сказать, что это 10x10-минутная область, это то, что возвращают запросы.

EDIT3: формула, использованная выше для расстояния, не очень точна, если вы отойдете от экватора. Вот лучшее приближение расстояния формула Для серьезной работы вам может понадобиться что-то вроде this

2 голосов
/ 06 мая 2010
select 
  name
from 
  (select 
    round((min(LATITUDE) + max(LATITUDE)) / 2) as LATITUDE,
    round((min(LONGITUDE) + max(LONGITUDE)) / 2) as LONGITUDE
   from STATION 
   where DISTRICT_ID = '110' 
     AND NAME LIKE 'Vancouver%') AS center
  inner join STATION s
where
  s.DISTRICT_ID = '110' 
  and s.NAME like 'Vancouver%'
  and s.LATITUDE between center.LATITUDE - 5 and center.LATITUDE + 5
  and s.LONGITUDE between center.LONGITUDE - 5 and center.LONGITUDE + 5
1 голос
/ 06 мая 2010

Использовать общее табличное выражение ...

with cte as
 (  SELECT round((min(LATITUDE) + max(LATITUDE)) / 2)-5 min_lat
           , round((min(LATITUDE) + max(LATITUDE)) / 2)+5 max_lat
           , round((min(LONGITUDE) + max(LONGITUDE)) / 2)-5 min_long
           , round((min(LONGITUDE) + max(LONGITUDE)) / 2)+5 max_long
           , DISTRICT_ID
           ,  'Vancouver%' AS NAME 
    FROM STATION 
    WHERE DISTRICT_ID = '110' 
AND NAME LIKE 'Vancouver%'
group by DISTRICT_ID,  'Vancouver%')
SELECT
  NAME
FROM
 STATION , cte
WHERE
      station.DISTRICT_ID = cte.DISTRICT_ID
  AND station.NAME LIKE cte.NAME
  AND station.LATITUDE BETWEEN cte.min_lat AND cte.max_lat
   AND station.LONGITUDE BETWEEN cte.min_long AND cte.max_long
ORDER BY
  station.LATITUDE

NB. Сейчас у меня нет доступа к базе данных, поэтому я не смог проверить это. Следовательно, я не могу гарантировать, что это работает. Я проверю это, когда смогу. Принцип держит.

0 голосов
/ 06 мая 2010

Что бы ни случилось с старым добрым Пифагором (хорошо, я знаю, что на самом деле это не относится к криволинейным поверхностям - но должно быть достаточно хорошим приближением).Если вы ищете центр (на самом деле, centroid , основанный на интерпретации, примененной физиком, а не геометристами) для набора пар координат, то вам не следует использовать MIN и MAX, хотя вы могли бы рассмотретьограничивая поиск на основе MIN и MAX).Единственная оставшаяся ложка дегтя - это то, что вы храните целочисленное представление строкового представления координатного угла.

Рассмотрим:

SELECT b.name
FROM
(SELECT AVG(CALC(a.lattitude)) AS c_lat, AVG(CALC(a.longitude)) AS c_long
  FROM station a 
  WHERE a.district_id='110'
  AND a.name like 'VANCOUVER%'
) AS ilv,
station b
WHERE b.district_id='110'
AND b.name LIKE 'VANCOUVER%'
AND POW(ilv.c_lat-CALC(b.lattitude),2)
     + POW(olv.c_long-CALC(b.longitude),2)<=25;

Где функция CALC преобразует сохраненное значение вдолгота / широта в минутах, т.е.

CALC(x)=(FLOOR(x/100)*60+MOD(x,100))

C.

0 голосов
/ 06 мая 2010

Я предполагаю, что исходный запрос дал точный результат для вашей цели. Если это так, то вы можете объединить запрос, поместив вычисление конечных точек в подзапрос.

Select ...
From Station As S
    Cross Join  (
                Select Round( (Min(S1.Latitude)  + Max(S1.Latitude)) / 2 ) As Latitude
                    , Round( (Min(S1.Longitude)  + Max(S1.Longitude)) / 2 ) As Longitude
                From Station As S1
                Where S1.District_Id = '110'
                    And S1.Name Like 'Vancouver%'
                ) As S2
Where S.District_Id = '110'
    And S.Name Like 'Vancouver%'
    And  S.Latitude Between (S2.Latitude - 5) And (S2.Latitude + 5)
    And  S.Longitude Between (S2.Longitude - 5) And (S2.Longitude + 5)
Order By S.Latitude
...