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;
Common table expression s window funkcí
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;
Případně můžeme použít anonymní blok kódu
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.