56

Using PostGIS, I just want to select points that fall outside of polygons. Ultimately this is the inverse of the ST_Intersects, as far as I can see it.

Example: I have a taxlot layer and a address point layer. I assume I should be using the ST_Intersects, but how do I tell it to do the reverse selection? I thought maybe adding a NOT statement in front of the code below, but that did not work.

CREATE table t_intersect AS
SELECT 
  hp.gid, 
  hp.st_address, 
  hp.city, 
  hp.st_num,
  hp.the_geom
FROM 
  public.parcel as par,
  public.housepoints as hp
WHERE 
  ST_Intersects(hp.the_geom,par.the_geom);
3
  • I had the same thought process, thought that the NOT would also do the trick like any other where condition
    – Luffydude
    Commented Sep 20, 2018 at 13:21
  • Same issue here. Why on earth would the NOT negation operator not work? Commented Nov 17, 2022 at 11:53
  • @FaustinGashakamba because, as Nicklas Avén states, ST_Intersects does a pair-wise comparison, looking for intersections between each object in the source table to each object in the destination table Commented Nov 18, 2022 at 21:34

10 Answers 10

58

The reason it doesn't work with "not intersects" is that you only compare geometries in pairs; there will be the same problem with disjoint. Every housepoint will disjoint some parcels even if it intersects one parcel.

underdark's suggestion doesn't have that problem. There is also another trick that probably will make more effective use of indexes:

CREATE TABLE t_intersect AS
SELECT 
  hp.gid, 
  hp.st_address, 
  hp.city, 
  hp.st_num,
  hp.the_geom
FROM 
  public.housepoints AS hp 
LEFT JOIN
  public.parcel AS par ON
  ST_Intersects(hp.the_geom,par.the_geom)
WHERE par.gid IS NULL;

The idea is to join them with st_intersects and get the rows where parcel id is not present.

The indexes needed here are a spatial index and an index on gid in parcels (assuming that id in parcels table is called gid too).

3
  • 4
    Thank you very much! Nicklas is exactly correct that ST_Disjoint will not produce the correct results. ST_Disjoint returns all features because, as he pointed out, each point is disjointed with some parcel polygons in the table, while this code snippet gave me the results I was hoping for. Commented Dec 17, 2010 at 0:16
  • This query will get planned the same as this one gis.stackexchange.com/a/136177/6052 so it's purely a matter of style which you prefer. =) For those shopping answers. Commented Jan 5, 2018 at 18:00
  • I really like this left join solution, good work!
    – jesnes
    Commented Nov 5, 2021 at 21:55
16

You may be looking for ST_Disjoint

ST_Disjoint — Returns TRUE if the Geometries do not "spatially intersect" - if they do not share any space together.

1
  • 7
    While ST_Disjoint does that, it does not however uses spatial indexes. You are going to wait a loooooong time
    – nickves
    Commented Jun 12, 2015 at 13:19
12

In case there is no specialized function:

CREATE table t_intersect AS
SELECT 
  hp.gid, 
  hp.st_address, 
  hp.city, 
  hp.st_num,
  hp.the_geom
FROM
  public.housepoints as hp
WHERE
  hp.gid NOT IN 
  (
    SELECT 
      h.gid
    FROM 
      public.parcel as p,
      public.housepoints as h
    WHERE 
      ST_Intersects(h.the_geom,p.the_geom)
  ) AS foo
9

Here we use NOT EXISTS and CREATE TABLE AS SELECT (CTAS)

CREATE table t_intersect
AS
  SELECT 
    hp.gid,
    hp.st_address,
    hp.city, hp.st_num,
    hp.the_geom
  FROM public.housepoints AS hp
  WHERE NOT EXISTS (
    SELECT 1
    FROM public.parcel AS par 
    WHERE ST_Intersects(hp.the_geom,par.the_geom)
  );
4

How about ST_Disjoint? — Returns TRUE if the Geometries do not "spatially intersect" - if they do not share any space together.

1
  • 5
    whoops - need to do a page refresh before answering :-)
    – Ian Turton
    Commented Dec 16, 2010 at 22:07
2

In some cases is very useful use LATERAL JOIN, it can be very fast It should look like

SELECT * FROM houses h
LEFT JOIN LATERAL (
   SELECT True t FROM parcels p
   WHERE ST_Intersects(p.geom, h.geom)
   LIMIT 1
) p ON True
WHERE p.t IS NULL;
1
  • 2
    The solution using LEFT JOIN was about twice as fast than this one for me (on PostgreSQL 13), because it could parallelize and use more CPUs.
    – hfs
    Commented Feb 28, 2021 at 8:34
1

You can simply use st_difference. Have not tested your example but it would be something like this:

CREATE table t_intersect AS
SELECT 
  hp.gid, 
  hp.st_address, 
  hp.city, 
  hp.st_num,
  hp.the_geom
FROM 
  public.parcel as par,
  public.housepoints as hp
WHERE 
  ST_Difference(hp.the_geom,par.the_geom);
0

Simply using NOT before the ST_Intersects does the trick:

This gets all addresses that are not within neighborhood #62:

select 
a.*
from denver.neighborhoods as n
join denver.addresses as a on not ST_Intersects(n.geom, a.geom)
where n.nbhd_id = '62'

Note the order of the geom columns - polygons first, points second, which is reversed from the usual usage of ST_Intersects.

Quick and simple! Have been wondering how to do this correctly for a while!

8
  • Also worked for "NOT ST_Within". My query completed in ~30.0 seconds for both the NOT ST_Within and using an outer join then checking for Null on the right side, so there doesn't seem to be any performance hit. Thanks! Commented Jan 26, 2017 at 17:57
  • @NateWanner good to know! I can't believe how easy and fast that is!!! Commented Jan 26, 2017 at 18:22
  • This is actually a pretty bad idea because you're getting the cartesian product Commented Jan 3, 2018 at 2:05
  • @EvanCarroll what does that mean? Commented Jan 5, 2018 at 16:46
  • It means if you're not just getting 1 denver.address, you're getting one for every not-matching denver.neighborhood. Commented Jan 5, 2018 at 17:07
0

Came across this recently. I am not stating that this is the optimal solution for large-scale datasets. However, it is an alternative that is a little easier to interpret and understand than all the solutions using joins.

Performance-wise it worked out pretty well for me on a large dataset.

select * from <tableA> 
where <tableA>.id not in (
    select <tableA>.id from <tableA> , <tableB> where <tableA>.geom && 
    <tableB>.geom and st_intersects(<tableA>.geom, <tableB>.geom)
)
-1

This may not be the fastest solution... But I usually just cheat by joining all the features of the other table.

Create table blah as
select
  d.*
from
  data_i_want d,
  (select st_union(geom) geom from not_in_here) n
where
  st_disjoint(d.geom,n.geom);

Nice and snappy if the not_in_here table isn't that complex.

1
  • That's never snappy. It's just not as unsnappy as it would be if not_in_here is complex. ;) Commented Jan 5, 2018 at 17:17

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.