Melhorando o desempenho do STIntersects

11

A tabela T_PINpossui 300.000 pinos e T_POLYGON36.000 polígonos. T_PINtem este índice:

CREATE SPATIAL INDEX [T_PIN_COORD] ON [dbo].[T_PIN]
(
[Coord]
)USING  GEOGRAPHY_GRID 
WITH (GRIDS =(LEVEL_1 = HIGH,LEVEL_2 = HIGH,LEVEL_3 = HIGH,LEVEL_4 = HIGH), 
CELLS_PER_OBJECT = 128, PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, 
SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, 
ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
ON [PRIMARY];

T_POLYGON tem:

CREATE SPATIAL INDEX [T_POLYGON_COORD] ON [dbo].[T_POLYGON]
(
[COORD]
)USING  GEOGRAPHY_GRID 
WITH (GRIDS =(LEVEL_1 = HIGH,LEVEL_2 = HIGH,LEVEL_3 = HIGH,LEVEL_4 = HIGH), 
CELLS_PER_OBJECT = 128, PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF,
SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, 
ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) 
ON [PRIMARY];

Uma consulta para encontrar a interseção T_PINe T_POLYGONleva mais de 45 minutos para executar:

SELECT COUNT(*)
FROM T_PIN 
INNER JOIN T_POLYGON
    ON T_PIN.Coord.STIntersects(T_POLYGON.COORD) = 1;

O resultado é 4.438.318 linhas.

Como posso acelerar esta consulta?

seb49
fonte
Você já tentou usar `T_POLYGON.Coord.STIntersects (T_PIN.COORD) = 1 '?
precisa
Eu estaria interessado em ver seu plano de consulta. Eu trabalho no Postgres, mas tenho que executar consultas semelhantes, mas em conjuntos de dados significativamente maiores. Eu inventei uma técnica que reduz os meus piores para cerca de 2 dias (o que infelizmente envolve scripts), mas eu estaria interessado em ver seu plano de consulta primeiro.
John Powell
Multiplicando os polígonos em minhas duas tabelas, tenho um número 7000 vezes mais, em termos de número de interseções em potencial, do que você tem em sua combinação, então acho que sob essa luz meus dois dias parecem muito bons. No entanto, sem ver um plano de consulta e saber algo sobre o número médio de pontos por polígono, será difícil encontrar soluções concretas.
John Powell

Respostas:

7

Primeiramente, verifique se um índice espacial está sendo usado, observando o plano de execução da consulta e veja se há um item de Procura de Índice em Cluster (Espacial).

Supondo que ele esteja sendo usado, você pode tentar adicionar um filtro secundário / simplificado com base em uma caixa delimitadora com polígonos simplificados para verificar primeiro. As correspondências com esses polígonos simplificados poderiam ser executadas no filtro primário para obter os resultados finais.

1) Adicione uma nova coluna de geografia e geometria à tabela [dbo]. [T_POLYGON]:

ALTER TABLE [dbo].[T_POLYGON] ADD SimplePolysGeom geometry;
ALTER TABLE [dbo].[T_POLYGON] ADD SimplePolysGeog geography;

2) Crie os polígonos da caixa delimitadora (isso envolve uma conversão inicial em geometria para aproveitar o STEnvelope ()):

UPDATE [dbo].[T_POLYGON] SET SimplePolysGeom = geometry::STGeomFromWKB(
    COORD.STAsBinary(), COORD.STSrid).STEnvelope();

UPDATE [dbo].[T_POLYGON] SET SimplePolysGeog = geography::STGeomFromWKB(
    SimplePolysGeom.STAsBinary(), SimplePolysGeom.STSrid);

3) Crie um índice espacial na coluna de geografia simplificada

4) Obtenha as interseções nessa coluna de geografia simplificada e filtre novamente os tipos de dados geográficos correspondentes. Aproximadamente, algo como isto:

;WITH cte AS
(
   SELECT pinID, polygonID FROM T_PIN INNER JOIN T_POLYGON
    ON T_PIN.Coord.STIntersects(T_POLYGON.SimplePolysGeog ) = 1
)
SELECT COUNT(*)
FROM T_PIN 
INNER JOIN T_POLYGON
    ON T_PIN.Coord.STIntersects(T_POLYGON.COORD) = 1
    AND T_PIN.pinID IN (SELECT pinID FROM cte)
    AND T_POLYGON.polygonID IN (SELECT polygonID FROM cte)

EDIT : você pode substituir (1) e (2) por esta coluna persistente calculada. agradecemos a Paul White pela sugestão.

ALTER TABLE [dbo].[T_POLYGON] ADD SimplePolysGeog AS  ([geography]::STGeomFromWKB([geometry]::STGeomFromWKB([COORD].[STAsBinary](),[COORD].[STSrid]).STEnvelope().STAsBinary(),(4326))) PERSISTED
g2server
fonte
Sim, é mais ou menos no que eu estava falando. O problema que encontrei ao "unir” espacialmente dois conjuntos de tabelas com uma ampla área de cobertura é que o otimizador costuma fazer duas verificações completas da tabela e muito ponto nos testes de polígono.
John Powell
2

Consultas como essa geralmente levam muito tempo devido à complexidade dos polígonos. Eu já vi linhas costeiras complexas (por exemplo) levarem séculos para testar pontos que estão próximos de seus limites, tendo que ampliar muitos níveis para descobrir se um ponto está dentro ou fora.

... para tentar .Reduce()os polígonos, para ver se isso ajuda.

E para saber mais sobre essa função, consulte http://msdn.microsoft.com/en-us/library/cc627410.aspx

Rob Farley
fonte
1

De acordo com os documentos da Microsoft, os índices espaciais serão usados ​​com os tipos de região geográfica nos métodos a seguir quando aparecerem no início de um predicado de comparação com uma WHEREcláusula:

  • STIntersects
  • STDistance
  • STEquals

Somente os métodos dos tipos de geometria (lista restrita) acionarão o uso do índice espacial JOIN ... ON, portanto, mude seu código para usar WHERE geog1.STIntersects(geog2) = 1e isso deve melhorar a velocidade.

Também recomendo seguir o conselho da resposta do g2server e adicionar o seguinte para filtrar e adicionar índice espacial nele

ALTER TABLE [dbo].[T_POLYGON] ADD SimplePolysGeog AS
     ([geography]::STGeomFromWKB([geometry]::STGeomFromWKB([COORD].[STAsBinary](),
                                                           [COORD].[STSrid])
                 .STEnvelope().STAsBinary(),(4326))) PERSISTED

você poderia então ter uma consulta como a seguinte (escrevi esta postagem rapidamente e ainda não a testei, isso é apenas algo para tentar, porque eu vi que sua consulta e as respostas mais altas postadas usam JOIN ON spatial op = 1, que não usará um índice espacial):

SELECT   
     (SELECT p2.polygon_id
      FROM   T_Polygon p2
      WHERE  p2.coords.STIntersects(t.coords) = 1),
     t.pin_id
FROM     T_PIN t
WHERE    
     (SELECT t.coords.STIntersects(p.coords)
      FROM   T_POLYGON p
      WHERE  t.coords.STIntersects(p.SimplePolysGeog) = 1) = 1

Para sua informação: O exemplo acima não funciona se SimplePolysGeogacabar sobreposto (como em um alfinete pode estar em dois geogs simplificados, apenas o executei em pessoas em distritos de um estado e, como os polys normais compartilham o limite, as caixas delimitadoras se sobrepõem), portanto, na maior parte do uso casos, lançará um erro que a subconsulta retornou mais de um resultado.

Da visão geral dos índices espaciais do MS Docs :

Métodos de geografia suportados por índices espaciais

Sob certas condições, os índices espaciais suportam os seguintes métodos de geografia orientada a conjunto: STIntersects (), STEquals () e STDistance (). Para serem suportados por um índice espacial, esses métodos devem ser usados ​​na cláusula WHERE de uma consulta e devem ocorrer dentro de um predicado da seguinte forma geral:

geography1.method_name (geography2) Comparation_operatorvalid_number

Para retornar um resultado não nulo, geography1 e geography2 devem ter o mesmo SRID (Spatial Reference Identifier) . Caso contrário, o método retornará NULL.

Os índices espaciais suportam as seguintes formas de predicado:


Consultas que usam índices espaciais

Os índices espaciais são suportados apenas em consultas que incluem um operador espacial indexado na cláusula WHERE. Por exemplo, sintaxe como:

[spatial object].SpatialMethod([reference spatial object]) [ = | < ] [const literal or variable]

O otimizador de consulta entende a comutatividade das operações espaciais (isso @a.STIntersects(@b) = @b.STInterestcs(@a)). No entanto, o índice espacial não será usado se o início de uma comparação não contiver o operador espacial (por exemplo WHERE 1 = spatial op, não usará o índice espacial). Para usar o índice espacial, reescreva a comparação (por exemplo WHERE spatial op = 1).

...

A consulta a seguir funcionará se SimplePolysGeogssobrepor:

;WITH cte AS
(
   SELECT T_PIN.PIN_ID, 
          T_POLYGON.POLYGON_ID, 
          T_POLYGON.COORD 
   FROM T_PIN 
   INNER JOIN T_POLYGON
   ON T_PIN.COORD.STIntersects(T_POLYGON.SimplePolysGeog) = 1
)

SELECT COUNT(*)
FROM T_PIN 
INNER JOIN cte
ON T_PIN_PIN_ID = cte.PIN_ID
where cte.[COORD].STIntersects(T_PIN.COORD) = 1
pbordeaux
fonte