2017-01-06 5 views
0

У меня есть функция PL/pgSQL, чтобы проверить, находится ли точка в многоугольнике. Чтобы начать, я хочу сделать тест AABB по широте и долготе мин/макс, поэтому мне не нужно делать raycast. Я выполняю следующее внутри функции, чтобы получить минимумы и максимумы.Реструктуризация/Оптимизация: избегать сканирования таблицы и оператора Select медленнее в функции plsql

Моя проблема заключается в том, что каждый оператор max()/select min() занимает около 500 мс для выполнения внутри функции. Если я выполняю те же самые инструкции вне функции, запросы занимают около 20 мс каждый. Почему они настолько медленны внутри функции?

select max(latitude) into maxLat from points where location=name_input; 
select max(longitude) into maxLong from points where location=name_input; 
select min(latitude) into minLat from points where location=name_input; 
select min(longitude) into minLong from points where location=name_input; 

Вот полная функция. Как вы можете догадаться из кода, я знаю очень мало SQL, и я пишу это как для postgresql, так и для oracle (поэтому некоторые части могут быть просто плохим портом, например, иметь два массива для lat/long вместо одного массива точек, который я сделал в оракуле). Я знаю, что мой вызов очень медленный, и план показывает, что он выполняет сканирование таблицы, даже если я индексирую функцию и столбцы на ней. Мне сказали в другом вопросе, что невозможно индексировать мою функцию, потому что я передаю строку как переменную, поэтому я пытаюсь выяснить, как ее исправить.

CREATE OR REPLACE FUNCTION GEOLOCATION_CONTAINS 
(
name_input IN VARCHAR, --Name of the geofilter 
lat_in IN DOUBLE PRECISION, --latitude of the point to test 
long_in IN DOUBLE PRECISION --longitude of the point to test 
) 
RETURNS INTEGER 
AS $$ 
DECLARE 
    j int := 0; --index to previous point 
    inside int := 0; -- If the point is inside or not 
    numPoints int := 0; --Total number of points in the geo filter 
    pointsLAT DOUBLE PRECISION[]; --An array of latitudes 
    pointsLONG DOUBLE PRECISION[]; --An array of longitudes 
    maxLat double precision := 0.0; 
    maxLong double precision := 0.0; 
    minLat double precision := 0.0; 
    minLong double precision := 0.0; 
BEGIN 

    --Populate the array of points by grabbing all the points in a filter 
    --The convention seems to be that order of a geo filter's points is defined by the order of their IDs, increasing 
    pointsLAT := array(SELECT latitude FROM points where location=name_input ORDER BY ID); 
    pointsLONG := array(SELECT longitude FROM points where location=name_input ORDER BY ID); 

    --Get the max/min lat/long to return before raycasting 
    select max(latitude) into maxLat from points where location=name_input; 
    select max(longitude) into maxLong from points where location=name_input; 
    select min(latitude) into minLat from points where location=name_input; 
    select min(longitude) into minLong from points where location=name_input; 

    --Check if it's even possible to be in the filter. If it's outside the bounds, return 0 for outside. 
    IF lat_in <= minLat OR lat_in >= maxLat OR long_in <= minLong OR long_in >= maxLong THEN 
    return 0; 
    END IF; 

    --Get the total number of points in the points array 
    SELECT COUNT(*) into numPoints from points where location=name_input; 

    --Init the pointer to the prev point index to the last guy in the array 
    j := numPoints; 

    --Perform raycast intersection test over the polgygon 
    for i IN 1..numPoints loop 
     --Test for intersection on an edge of the polygon 
     if((pointsLAT[i]>lat_in) != (pointsLAT[j]>lat_in)) then 
     if (long_in < (pointsLONG[j]-pointsLONG[i]) * (lat_in-pointsLAT[i])/(pointsLAT[j]-pointsLAT[i]) + pointsLONG[i]) then 
      --Intersected a line, toggle in/out 
      if(inside = 0) then 
      inside := 1; 
      else 
      inside := 0; 

      end if; 
     end if; 
     end if;  

    --set J to previous before incrementing i 
    j := i; 
    end loop; 

    RETURN inside; 
END; $$ LANGUAGE plpgsql IMMUTABLE; 

Я смотрю, чтобы найти способ получить индекс функции для работы, потому что это просто слишком медленно, если я запускаю его на стол с 200,000+ рядов (около 40 секунд теперь с оптимизациями при условии, до сих пор в ответах). Для сравнения, выполнение select * всех объектов и запуск его через класс Polygon Java занимает 2 секунды, поэтому, очевидно, я делаю что-то не так в моей реализации plsql. В настоящее время я читаю учебники, и я вижу такие вещи, как встроенные функции и представления, чтобы ускорить процесс, но я не совсем уверен, что за чтение, чтобы ускорить его работу.

+1

Было бы полезно узнать больше о вашей функции. Если вы, например, используете временные таблицы внутри функции, то вы технически теряете память и ресурсы диска, потому что временные таблицы кэшируются в буферах temp, которые очищаются в конце сеанса. Обычные запросы кэшируются в общих буферах и используются повторно. – JosMac

ответ

2

Почему есть четыре заявления?

select max(latitude), max(longitude), min(latitude), min(longitude) 
into maxLat, maxlong, minLat, minLong 
from points 
where location = name_input; 

Это не относится к тому, почему вызов кажется скорее вне функции, а не внутри. Но есть другие накладные расходы для вызова функции.

2

Вы можете сократить все семь операторов SQL в один:

select max(latitude), 
     max(longitude), 
     min(latitude), 
     min(longitude), 
     array_agg(latitude ORDER BY ID), 
     array_agg(longitude ORDER BY ID), 
     COUNT(*) over() 
    into into maxLat, maxlong, minLat, minLong, pointsLAT, pointsLONG, numPoints 
from points 
where location = name_input; 

У меня нет никакого опыта обработки ГИС, так что я мог бы быть совершенно неправильно со следующим:

Кажется, что вы сохраняете многоугольник в виде нескольких строк в таблице. Однако в Postgres и тем более с расширением PostGIS вы можете хранить полигоны в single column, а затем у вас есть native operators, который может проверить, находится ли точка внутри многоугольника. Запросы с использованием этих операторов могут использовать с использованием индексов GiST или GIN.

Мое понимание заключается в том, что для любой серьезной работы с ГИС вы обязательно должны изучить PostGIS. Встроенные геометрические типы данных в Postgres предлагают только очень простой набор функций.

+0

Выполнение этого ускорило мой вызов на 20 секунд на столе с 200 000. Есть ли способ «кэшировать» эти значения, чтобы он не пересчитывал его для всех 200 000 строк?Я сейчас смотрю на Oracle Spatial, но в случае, если мы не переходим к нему, я пытаюсь изучить все различные методы оптимизации. – GuitarStrum

+0

@GuitarStrum: у вас есть указатель на 'location'? Но опять же: я думаю, что сохранение многоугольника как одного объекта в одном столбце, скорее всего, способ пойти. –

 Смежные вопросы

  • Нет связанных вопросов^_^