Praktické příklady

Nejbližší bod

Protože Vaše agentura pro boj s vesmírnými obludami nemá dostatek peněz, vybavuje své agenty pouze turistickými mapkami, které se prodávají na nádraží, najděte ke každému bodu nejbližší adresu.

Zadání

Ke každému z bodů v tabulce ukol_1.vesmirne_zrudice nejděte nejbližší bod z tabulky ukol_1.adresy.

Rozbor

Nejdříve zkontrolujeme, zda je tabulka s adresními body patřičně oindexovaná.

\d+ ukol_1.adresy
Indexy:
    "adresy_pk" PRIMARY KEY, btree (ogc_fid)
    "adresy_adresnibod_geom_idx" gist (adresnibod)
    "adresy_hasici_geom_idx" gist (hasici)
    "adresy_zachranka_geom_idx" gist (zachranka)

S ohledem na to, že pracujeme s body, vystačíme si s prostorovými operátory.

SET SEARCH_PATH = ukol_1, public;

SELECT a.kod, v.id, a.adresnibod<->v.geom_p vzdalenost
 FROM adresy a, vesmirne_zrudice v
 WHERE adresnibod IS NOT NULL
 LIMIT 200;

K výběru nejbližšího bodu použijeme LIMIT

SELECT a.kod, v.id, a.adresnibod<->v.geom_p vzdalenost
 FROM adresy a, vesmirne_zrudice v
 WHERE adresnibod IS NOT NULL
 AND v.id = 1
 ORDER BY a.adresnibod<->v.geom_p
 LIMIT 1;

K výběru nejbližšího bodu ke každému z bodů můžeme použít několik cest.

  • Vnořený poddotaz
SELECT
id,
(
   SELECT ARRAY[kod, adresnibod<->geom_p]
   FROM ukol_1.adresy
   WHERE adresnibod IS NOT NULL
   ORDER by adresnibod<->geom_p
   LIMIT 1
) FROM vesmirne_zrudice;
WITH cte AS
(
   SELECT
   ROW_NUMBER()
   OVER (
      PARTITION BY v.id
      ORDER BY a.adresnibod<->v.geom_p
   ) rn
   , v.id
   , a.kod
   , a.adresnibod<->v.geom_p vzdalenost

   FROM vesmirne_zrudice v,
   adresy a
)

SELECT * FROM cte WHERE rn = 1;

Poznámka

Elegantní řešení nemusí být však vždy to nejvýkonější. A to ani při optimalizaci.

WITH cte AS
(
   SELECT
   ROW_NUMBER()
   OVER (
      PARTITION BY v.id
      ORDER BY a.adresnibod<->v.geom_p
   ) rn
   , v.id
   , a.kod
   , a.adresnibod<->v.geom_p vzdalenost

   FROM vesmirne_zrudice v,
   adresy a WHERE v.geom_p<->a.adresnibod < 200
)

SELECT * FROM cte WHERE rn = 1;
  • Window funkce v poddotazu
SELECT * FROM
(
   SELECT
   ROW_NUMBER()
   OVER (
      PARTITION BY v.id
      ORDER BY a.adresnibod<->v.geom_p
   ) rn
   , v.id
   , a.kod
   , a.adresnibod<->v.geom_p vzdalenost

   FROM vesmirne_zrudice v,
   adresy a WHERE v.geom_p<->a.adresnibod < 200
) a
WHERE rn = 1;
BEGIN;

SET search_path to ukol_1, public;

CREATE TABLE outp (id int
   , kod numeric(30)
   , vzdalenost float
   , cislodomovni int
   , cisloorientacni int
   , psc int
   , ulicekod bigint
   , stavebniobjektkod bigint
);

DO $$DECLARE r record;
BEGIN
   FOR r IN
   SELECT id, geom_p
   FROM  vesmirne_zrudice v
 LOOP
   INSERT INTO outp
   SELECT r.id, kod
   , r.geom_p<->a.adresnibod
   , cislodomovni
   , cisloorientacni
   , psc
   , ulicekod
   , stavebniobjektkod
   FROM adresy a
   ORDER BY r.geom_p<->a.adresnibod
   LIMIT 1;
 END LOOP;
END$$;

SELECT
o.*
, u.nazev
FROM outp o
LEFT JOIN ulice u
ON u.kod = ulicekod
ORDER BY id;

ROLLBACK;
  • Obvykle bývá pro podobné úlohy velice efektivní použití klauzule LATERAL.
EXPLAIN ANALYZE
SELECT * FROM vesmirne_zrudice v
, LATERAL (
   SELECT * FROM adresy a
   ORDER BY a.adresnibod<->v.geom_p
   LIMIT 1
) nejblizsi_bod;

Poznámka

LATERAL lze použít i v JOINU.

Tip

Srovnejte výše uvedené dotazy pomocí EXPLAIN ANALYZE.

Výběr podle obalové zóny

V případě, že se obludy vylíhnou, všechno živé v okruhu čtvrt kilometru se změní ve sliz. Najděte všechny ulice ve vzdálenosti 250 metrů od vejce, aby je bylo možné evakuovat.

Zadání

Vyberte všechny ulice v okruhu 250 metrů kolem každého bodu.

Rozbor

Poznámka

V tabulce ulice nám nejspíš bude chybět index. Zkontrolujeme ho (\d) a pokud tam není, tak ho vytvoříme.

CREATE INDEX ON ulice USING gist (geom);

Ulice v okruhu 250 metrů můžeme vybrat buď pomocí obalové zóny anebo na základě vzdálenosti.

  • Výběr na základě obalové zóny
SET search_path to ukol_1, public;

SELECT u.*, v.geom_p
FROM ulice u,
vesmirne_zrudice v
WHERE ST_Relate(geom, ST_Buffer(geom_p, 250, 100), 'T********');

Tip

Vyzkoušejte místo ST_Relate funkci ST_Intersects

Poznámka

Optimalizovaná verze

SELECT * FROM
(
   SELECT u.*, v.geom_p
   FROM ulice u,
   vesmirne_zrudice v
   WHERE ST_Buffer(v.geom_p, 250, 100) && u.geom
) a
WHERE ST_Relate(geom, ST_Buffer(geom_p, 250, 100), 'T********');
  • Výběr na základě vzdálenosti
EXPLAIN ANALYZE
SELECT * FROM
(
   SELECT u.*, v.geom_p
   FROM ulice u,
   vesmirne_zrudice v
   WHERE (v.geom_p<#>u.geom) <= 250
) a
WHERE ST_Distance(geom, geom_p) <= 250;
  • Pomocí ST_DWithin a LATERAL
EXPLAIN ANALYZE
SELECT u.* FROM
ulice u
, LATERAL (
   SELECT * FROM
   vesmirne_zrudice v
   WHERE ST_DWithin(geom, geom_p, 250)
   LIMIT 1
) v;
  • Výběr na základě vzdálenosti s předvýběrem podle MOO
EXPLAIN ANALYZE
SELECT * FROM
(
   SELECT u.*, v.geom_p
   FROM ulice u,
   vesmirne_zrudice v
   WHERE ST_Expand(v.geom_p, 250) && u.geom
) a
WHERE ST_Distance(geom, geom_p) <= 250;

Tip

Zamyslete se, které dotazy by mohly vracet jednu ulici vícekrát.

Součet ploch v určitém okruhu

Nemáte dostatek agentů v terénu, nejspíše se nepodaří neutralizovat všechna vejce, seřaďte body podle počtu budov v ohrožené zóně, aby bylo možné minimalizovat škody.

Zadání

Vyberte budovy v okruhu 250 metrů kolem bodů z tabulky vesmirne_zrudice, zjistěte pro každý bod jejich počet. Dále určete plochu průniku u každého bodu a celkovou plochu všech zasažených podlaží.

Postup

Nahrajeme do databáze datovou vrstvu budov.

Načtení dat z PGDump z příkazové řádky

pg_restore -d pokusnik stav_objekty.dump

Indexy už v tabulce jsou.

SELECT
id
, originalnihranice
, ST_Intersection(originalnihranice, ST_Buffer(geom_p, 250, 100)) prunik
, pocetpodlazi

FROM
(
   SELECT b.*, v.geom_p, v.id
   FROM budovy b,
   vesmirne_zrudice v
   WHERE (v.geom_p<#>b.originalnihranice) <= 250
   AND originalnihranice IS NOT NULL
) a
WHERE ST_Relate(ST_Buffer(geom_p, 250, 100), originalnihranice, '2********');

Data ale obsahující chyby ve validnosti geometrií.

SELECT * FROM budovy WHERE NOT ST_IsValid(originalnihranice) ;

Chyby můžeme opravit nebo použít ST_MakeValid rovnou v dotazu.

SELECT
id
, COUNT(*) pocet_budov
, SUM(ST_Area(originalnihranice)) plocha_budov
, SUM(ST_Area(prunik)) plocha_pruniku
, SUM(ST_Area(prunik)*pocetpodlazi) plocha_zasazenych_podlazi
, SUM(
   CASE WHEN ((ST_Area(prunik)) / (ST_Area(originalnihranice))) > 0.5
      THEN 1
   ELSE 0
   END) pocet_zasazenych_vic_nez_z_poloviny
FROM
(
   SELECT
   id
   , originalnihranice
   , ST_Intersection(ST_MakeValid(originalnihranice), ST_Buffer(geom_p, 250, 100)) prunik
   , pocetpodlazi

   FROM
   (
      SELECT b.*, v.geom_p, v.id
      FROM budovy b,
      vesmirne_zrudice v
      WHERE (v.geom_p<#>b.originalnihranice) <= 250
      AND originalnihranice IS NOT NULL
   ) a
   WHERE ST_Relate(ST_Buffer(geom_p, 250, 100), originalnihranice, '2********')
)b
GROUP BY id
ORDER BY SUM(ST_Area(prunik)) DESC;

Poznámka

U mnoha budov ovšem nemáme polygon, ale pouze definiční bod.

Tip

Navrhněte, jak upravit dotaz tak, aby se použily definiční body u budov, u kterých nemáme geometrii. Pro výpočet plochy můžete použít zastavěnou plochu.

Tip

Navrhněte optimalizaci dotazu.

Nejbližší bod 2

U každého místa najděte nejbližší hasičskou stanici mimo kontaminovanou zónu.

Zadání

V tabulce osm.pozarni_stanice jsou body pro hasičské stanice. Navrhněte dotaz, ke každému bodu z tabulky vesmirne_zrudice najde nejbližší hasičskou stanici, která leží dále, než čtvrt kilometru.

Tip

Navrhněte různá řešení, srovnejte jejich rychlost a vracené záznamy.