Simultaneous selecting from tables and functions which use values from these tables - postgresql

I have 3 tables: lightnings, powerlines, masts.
The main fields:
lightnings.geo_belief - an ellipse of a probable hitting.
powerlines.geo_path - a geo polyline of powerline's path.
masts.geo_coordinates - a geo point of a mast placing.
The task:
To calculate lightning strokes that hit powerline's corridor (5000
meters - its radius, and it is generated as a geometry by function
powerline_corridor())
To get info about a powerline's mast, nearest to a respective lightning hit and to get the distance from lightning.geo_ellipse to masts.geo_coordinates.
So I can select lightnings:
SELECT l.*
FROM lightnings l
JOIN ( SELECT geo_path, powerline_corridor(geo_path, 5000::smallint) AS geo_zone
FROM powerlines WHERE id=1)
AS by_pl
ON ST_Intersects(by_pl.geo_zone, l.geo_belief)
Also I have got the function namos_nearest_mast(powerlines.id, lightnings.geo_belief):
CREATE OR REPLACE FUNCTION public.namos_nearest_mast (
powerline_id integer,
geo public.geometry
)
RETURNS public.obj_powerline_masts AS
$body$
SELECT *
FROM obj_powerline_masts
WHERE powerline_id=$1
ORDER BY $2 <-> geo_coordinates ASC
LIMIT 1
$body$
LANGUAGE 'sql';
Couldn't you suggest good solutions for selecting?

Following is all I've done by myself:
SELECT
t.*,
ROUND(st_distance(namos_transform_meters(m.geo_coordinates), namos_transform_meters(t.geo_belief))) AS dist_m
FROM obj_powerline_masts AS m
JOIN
(
SELECT
l.*,
(SELECT id FROM nearest_mast(1, l.geo_belief)) AS mast_id
FROM lightnings l
JOIN (SELECT geo_path, powerline_corridor(geo_path, 5000::smallint) AS geo_zone FROM powerlines WHERE id=1) AS by_pl ON ST_Intersects(by_pl.geo_zone, l.geo_belief)
LIMIT 50 OFFSET 50
) AS t
ON t.mast_id=m.id
But I'm not sure if it's an optimal solution. For instance, in PHP I can't apply dataProviders on such queries (which abstracts e.g. working with pagination), because of we can't affect on subqueries in a trivial way.

Related

What is wrong with my ST_Within query - query result contains point twice although it exists only once

I have two point tables, tab_1 and tab_2. I want to query all points from the first table that are probably the same points from the table 2. So i give the points from table 2 a buffer. Then I want to get the points from table 1 and query from table 2 within a 30 m buffer. My problem is, I get the points from table 1 and table 2 twice. But point 1 from table 1 exists only once and point 1 from table 2 also only once.
My query is:
with
"points1" as
(
select id, geom from tab_1
)
,
"points2" as
(
select id, geom from tab_2
)
select "points1".*, "points2".* from "points1", "points2"
where
st_within(st_transform("points1".geom, 31468), st_buffer(st_transform("points2".geom, 31468), 30)) = true;
id_tab1
geom
id_tab2
geom
st_distance
767074270
POINT (11.6968379 48.132722)
16455
POINT (11.69707 48.13265)
19.041083533921977
767074270
POINT (11.6968379 48.132722)
16455
POINT (11.69707 48.13265)
19.041083533921977
The query should be give only one result:
id_tab1
geom
id_tab2
geom
st_distance
767074270
POINT (11.6968379 48.132722)
16455
POINT (11.69707 48.13265)
19.041083533921977
Is my query wrong?
STEP 1. Query
SELECT *
FROM tab_1
JOIN tab_2
ON ST_DWithin
( ST_Transform(tab_1.geom, 31468)
, ST_Transform(tab_2.geom, 31468)
, 30
)
STEP 2. Spatial index
Most likely, the query cannot use the spatial index (even if it exists) and the function ST_DWithin() properly (ST_Transform() does not allow using an existing spatial index).
Solution - create new spatial indexes for EPSG:31468
CREATE
INDEX tab_1_geom_31468_idx
ON tab_1
USING GIST (ST_Transform(geom, 31468))
;
CREATE
INDEX tab_2_geom_31468_idx
ON tab_2
USING GIST (ST_Transform(geom, 31468))
;

Comparing two text array columns using % and ordering by number of matches

I have a user table that contains a "skills" column which is a text array. Given some input array, I would like to find all the users whose skills % one or more of the entries in the input array, and order by number of matches (according to the % operator from pg_trgm).
For example, I have Array['java', 'ruby', 'postgres'] and I want users who have these skills ordered by the number of matches (max is 3 in this case).
I tried unnest() with an inner join. It looked like I was getting somewhere, but I still have no idea how I can capture the count of the matching array entries. Any ideas on what the structure of the query may look like?
Edit: Details:
Here is what my programmers table looks like:
id | skills
----+-------------------------------
1 | {javascript,rails,css}
2 | {java,"ruby on rails",adobe}
3 | {typescript,nodejs,expressjs}
4 | {auth0,c++,redis}
where skills is a text array.
Here is what I have so far:
SELECT * FROM programmers, unnest(skills) skill_array(x)
INNER JOIN unnest(Array['ruby', 'node']) search(y)
ON skill_array.x % search.y;
which outputs the following:
id | skills | x | y
----+-------------------------------+---------------+---------
2 | {java,"ruby on rails",adobe} | ruby on rails | ruby
3 | {typescript,nodejs,expressjs} | nodejs | node
3 | {typescript,nodejs,expressjs} | expressjs | express
*Assuming pg_trgm is enabled.
For an exact match between the user skills and the searched skills, you can proceed like this :
You put the searched skills in the target_skills text array
You filter the users from the table user_table whose user_skills array has at least one common element with the target_skills array by using the && operator
For each of the selected users, you select the common skills by using unnest and INTERSECT, and you calculate the number of these common skills
You order the result by the number of common skills DESC
In this process, the users with skill "ruby" will be selected for the target skill "ruby", but not the users with skill "ruby on rails".
This process can be implemented as follow :
SELECT u.user_id
, u.user_skills
, inter.skills
FROM user_table AS u
CROSS JOIN LATERAL
( SELECT array( SELECT unnest(u.user_skills)
INTERSECT
SELECT unnest(target_skills)
) AS skills
) AS inter
WHERE u.user_skills && target_skills
ORDER BY array_length(inter.skills, 1) DESC
or with this variant :
SELECT u.user_id
, u.user_skills
, array_agg(t_skill) AS inter_skills
FROM user_table AS u
CROSS JOIN LATERAL unnest(target_skills) AS t_skill
WHERE u.user_skills && array[t_skill]
GROUP BY u.user_id, u.user_skills
ORDER BY array_length(inter_skills, 1) DESC
This query can be accelerated by creating a GIN index on the user_skills column of the user_table.
For a partial match between the user skills and the target skills (ie the users with skill "ruby on rails" must be selected for the target skill "ruby"), you need to use the pattern matching operator LIKE or the regular expression, but it is not possible to use them with text arrays, so you need first to transform your user_skills text array into a simple text with the function array_to_string. The query becomes :
SELECT u.user_id
, u.user_skills
, array_agg(t_skill) AS inter_skills
FROM user_table AS u
CROSS JOIN unnest(target_skills) AS t_skill
WHERE array_to_string(u.user_skills, ' ') ~ t_skill
GROUP BY u.user_id, u.user_skills
ORDER BY array_length(inter_skills, 1) DESC ;
Then you can accelerate the queries by creating the following GIN (or GiST) index :
DROP INDEX IF EXISTS user_skills ;
CREATE INDEX user_skills
ON user_table
USING gist (array_to_string(user_skills, ' ') gist_trgm_ops) ; -- gin_trgm_ops and gist_trgm_ops indexes are compliant with the LIKE operator and the regular expressions
In any case, managing the skills as text will ever fail if there are typing errors or if the skills list is not normalized.
I accepted Edouard's answer, but I thought I'd show something else I adapted from it.
CREATE OR REPLACE FUNCTION partial_and_and(list1 TEXT[], list2 TEXT[])
RETURNS BOOLEAN AS $$
SELECT EXISTS(
SELECT * FROM unnest(list1) x, unnest(list2) y
WHERE x % y
);
$$ LANGUAGE SQL IMMUTABLE;
Then create the operator:
CREATE OPERATOR &&% (
LEFTARG = TEXT[],
RIGHTARG = TEXT[],
PROCEDURE = partial_and_and,
COMMUTATOR = &&%
);
And finally, the query:
SELECT p.id, p.skills, array_agg(t_skill) AS inter_skills
FROM programmers AS p
CROSS JOIN LATERAL unnest(Array['ruby', 'java']) AS t_skill
WHERE p.skills &&% array[t_skill]
GROUP BY p.id, p.skills
ORDER BY array_length(inter_skills, 1) DESC;
This will output an error saying column 'inter_skills' does not exist (not sure why), but oh well point is the query seems to work. All credit goes to Edouard.

Recursive PostGIS query

I am trying to transform all the roundabouts in a city into simple intersections/crossroads (o -> +). As I am using OpenStreetMap for the initial topology, some roundabouts are not a circle but just the segments of the circle (Ex: https://www.openstreetmap.org/#map=18/43.34516/-8.41536).
In practice the problem is that I need the centroid of each roundabout and I get it almost in all cases but sometimes I get several centroids for the same roundabout (centroids of the arches, no the full roundabout).
I have achieved this:
select f.osm_id as fid, (select ST_CENTROID(ST_Buffer(ST_UNION(way),1)) as r_geom
from planet_osm_line d
where st_intersects(f.way, d.way) and junction = 'roundabout') as rotonda
from planet_osm_line f
where junction like 'roundabout';
But this does not resolve the problem, it only reduces it. I am not getting the full circle, just bigger segments of it.
So I guess I need a recursive query in order to do this until the number of geometries retrieved remains the same (the full circle). Any ideas about how to build this query?
I was looking for something like this (hope it helps others in need):
create table no_roundabouts as
with recursive roundabout(geom) as (--Recursive function to build closed circled roundabouts even with roundabouts mapped as differents arches.
SELECT ST_TRANSFORM(way,3857)
FROM planet_osm_line ways --Get all segments tagged as 'roundabout'
WHERE ways.junction = 'roundabout'
UNION ALL
SELECT ST_TRANSFORM(ST_UNION(ways.way, roundabout.geom),3857)
FROM roundabout, planet_osm_line ways -- Compose segments building greater arches of the roundabout until we have the full circle (My_segment + a touching segment that is no contained in my segment)
WHERE ways.junction = 'roundabout' and ST_INTERSECTS(roundabout.geom, ways.way) and not ST_CONTAINS(roundabout.geom, ways.way)
)
SELECT * FROM roundabout;
alter table no_roundabouts add column id bigserial; -- Add id to each line
delete from no_roundabouts a -- Delete repeated roundabouts generated during recursion
where exists (select geom from no_roundabouts b where ST_CONTAINS(b.geom, a.geom) and b.id > a.id);
--select count(*) from no_roundabouts WHERE ST_IsClosed(geom) = false;
update no_roundabouts set geom = ST_LINEMERGE(geom) where ST_ISCLOSED(geom) is false --Force closed roundabouts
-- Query replacing roundabouts with crossroads (linking each way in and out with the centroid of the roundabout)
SELECT ST_TRANSFORM(ST_ADDPOINT(y.way, ST_CENTROID(x.geom), 0),4326)
FROM no_roundabouts x JOIN planet_osm_line y ON ST_INTERSECTS(y.way, x.geom)
WHERE y.highway is not null and ST_INTERSECTS(x.geom, st_pointn(y.way,1)) and ST_CONTAINS(x.geom, y.way) = false
UNION
SELECT ST_TRANSFORM(ST_ADDPOINT(y.way, ST_CENTROID(x.geom), -1),4326)
FROM no_roundabouts x JOIN planet_osm_line y ON ST_INTERSECTS(y.way, x.geom)
WHERE y.highway is not null and ST_INTERSECTS(x.geom, ST_POINTN(y.way,-1)) and ST_CONTAINS(x.geom, y.way) = false;

Postgis nearest coordinates

I'm trying to make a REST service that returns a list of places ordered by distance from the user coordinate. I found this query using postgis:
SELECT *
FROM your_table
ORDER BY your_table.geom <-> "your location..."
LIMIT 5;
But I'm not able to apply this to my actual database. I have a table that contains these columns:
title, address, description, latitude, longitude
all these values as Strings.
I'll be very happy if someone help me. Thx!
I dont know why, but ORDER BY <-> isnt exact. Sometime the closest link is on the 3rd position. So I get 101 element and then use distance to selected the closest one.
CREATE OR REPLACE FUNCTION map.get_near_link(
x numeric,
y numeric)
RETURNS TABLE(Link_ID int, distance int) AS
$BODY$
DECLARE
strPoint text;
BEGIN
strPoint = 'POINT('|| X || ' ' || Y || ')';
With CTE AS (
SELECT Link_ID,
TRUNC(ST_Distance(ST_GeomFromText(strPoint,4326), geom )*100000)::integer as distance
FROM map.vzla_seg S
ORDER BY
geom <-> ST_GeomFromText(strPoint, 4326)
LIMIT 101
)
SELECT *
FROM CTE
ORDER BY distance
LIMIT 5
In order to use PostGIS you have to enable the extension in the database. Ideally, you just run the CREATE EXTENSION postgis; command and it works. NOTE form the install page: DO NOT INSTALL it in the database called postgres. For more information visit the site.
Adding a geometry column (spatial data can be stored in this type of columns) to your table:
SELECT AddGeometryColumn(
'your_schema',
'your_table',
'geom', -- name of the column
4326, -- SRID, for GPS coordinates you can use this, for more information https://en.wikipedia.org/wiki/Spatial_reference_system
'POINT', -- type of geometry eg. POINT, POLYGON etc.
2 -- number of dimension (2 xy - 3 xyz)
);
UPDATE yourtable t SET t.geom = ST_SetSRID(ST_MakePoint(t.x, t.y), 4326)
-- the x and y is the latitude and longitude
Now you can use spatial queries on your table like this:
SELECT
*
FROM
your_table
ORDER BY
your_table.geom <-> ST_SetSRID(ST_MakePoint(x, y), 4326)
LIMIT 5;
NOTE: as others mentioned, below PostgreSQL 9.5 <-> isn't always reliable.

Nearest places from a certain point

I have the following table
create table places(lat_lng point, place_name varchar(50));
insert into places values (POINT(-126.4, 45.32), 'Food Bar');
What should be the query to get all places close to particular lat/long?
gis is installed.
If you actually wanted to use PostGIS:
create table places(
lat_lng geography(Point,4326),
place_name varchar(50)
);
-- Two ways to make a geography point
insert into places values (ST_MakePoint(-126.4, 45.32), 'Food Bar1');
insert into places values ('POINT(-126.4 45.32)', 'Food Bar2');
-- Spatial index
create index places_lat_lng_idx on places using gist(lat_lng);
Now to find all of the places within 1 km (or 1000 m):
select *, ST_Distance(lat_lng, ST_MakePoint(-126.4, 45.32)::geography)
from places
where ST_DWithin(lat_lng, ST_MakePoint(-126.4, 45.32)::geography, 1000)
order by ST_Distance(lat_lng, ST_MakePoint(-126.4, 45.32)::geography);
select *
from places
where lat_lng <-> POINT(-125.4, 46.32) < 1
order by lat_lng <-> POINT(-125.4, 46.32)
Create an Indexing on a location field :
CREATE INDEX ON table_name USING GIST(location);
GiST index is capable of optimizing “nearest-neighbor” search :
SELECT * FROM table_name ORDER BY location <-> point '(-74.013, 40.711)' LIMIT 10;
Note: The point first element is longitude and the second element is latitude.