Postgresql – Join 3 tables of a one-to-many relationship

join;postgresql

I have 3 files. 1 shapefile and 2 DBF files. For those of who are not a familiar with shapefiles (GIS data format), they are essentially database tables with rows and columns that have spatial geometry such as point, line, polygon etc., which can be displayed visually and used in calculations in a GIS software.

trucknetwork (shapefile) – 564 rows (column sri links to table1) – specific truck road network in NJ with no road names:

gid integer NOT NULL DEFAULT nextval('trucknetwork_gid_seq'::regclass),
id numeric(10,0),
sri character varying(20),
mp_start double precision,
mp_end double precision,
descriptio character varying(50),
road_type character varying(50),
geom geometry(MultiLineStringM),
CONSTRAINT trucknetwork_pkey PRIMARY KEY (gid)

table1488715 rows (links to shapefile with sri column and table2 with seg_guid column) – start and end points of all NJ roads:

gid integer NOT NULL DEFAULT nextval('table1_gid_seq'::regclass),
objectid integer,
seg_guid character varying(38),
sri character varying(20),
route_type numeric,
sld_mp_st numeric,
sld_mp_end numeric,
CONSTRAINT table1_pkey PRIMARY KEY (gid)

table2669557 rows (links to table1 with seg_guid column) – all NJ road names:

gid integer NOT NULL DEFAULT nextval('table2_gid_seq'::regclass),
objectid integer,
seg_guid character varying(38),
s_name character varying(254),
CONSTRAINT table2_pkey PRIMARY KEY (gid)

I want to join these 3 tables into 1 table – with the result being 564 rows of each road segment with its correct name. It is a one-to-many relationship.

I have posted this question on GIS stackexchange but I did not get a lot of feedback and I want to do this task in SQL not a GIS software.

I have tried left join, inner join – and I have tried the group by but for every query I keep getting over 100,000 + records

select shp.descriptio as descr, shp.road_type as road_type, shp.geom as geom, t2.s_name as street_name 
      into networkEX 
from trucknetwork as shp 
   join table1 as t1 on shp.sri = t1.sri 
   join table2 as t2 on t1.seg_guid = t2.seg_guid; 

I am not sure what the logic should be. Subquery? Do I have to break this up into smaller steps?

Best Answer

Obviously, there can be multiple rows with the same value in table1.sri and / or in table2.seg_guid. That's the only logical explanation for the multiplied row count in your query result.

Which of both is impossible to tell from the information you provided. But the name "trucknetwork" indicates there should be many "street names" in the result, so your task seems contradictory at the outset.

My educated guess is that the multiplication happens in table1 (many start and end points of roads in the same truck network). There are various techniques to reduce to a single pick. The best one depends on information not in the question. Since you get so many result rows, a LATERAL join with LIMIT 1 is a hot contender. Be sure to support it with an index.

Since you want to join to exactly 1 row in the result for each row in trucknetwork, you have to define which row to pick from a set of multiple matches. More information we don't have. I pick an arbitrary winner.

And there is also the possibility that some of the rows in trucknetwork might find no matching row in table1 and/or table2 at all. In an ideal world, referential integrity would be enforced with FOREIGN KEY constraints. To make do with what we have, use LEFT [OUTER] JOIN to avoid eliminating trucknetwork rows (which would probably indicate data errors in table1 or table2).

So:

CREATE TABLE network_ex AS
SELECT shp.descriptio AS descr, shp.road_type, shp.geom, t2.s_name AS street_name 
FROM   trucknetwork shp
LEFT   JOIN LATERAL (
   SELECT seg_guid
   FROM   table1
   WHERE  sri = shp.sri
   -- ORDER  BY ???      -- define which one to pick if that matters
   LIMIT  1              -- ! just the one !
   ) t1 ON true
LEFT   JOIN table2 t2 USING (seg_guid);

Ideally, you would have these two multicolumn indexes to make this fast:

CREATE INDEX table1_sri_seg_guid_idx ON table1 (sri, seg_guid);
CREATE INDEX table2_seg_guid_s_name_idx ON table2 (seg_guid, s_name);

If there can be duplication in table2 (as well), reduce to a single match in a similar fashion. Again, the best technique depends on the complete picture.

Alternatively, you could aggregate all (distinct) road names into a single string or array in the result ...

Related:

Asides: