Find neighboring polygons with maximum of 3 other polygons - sql

I have a case like the following picture
Say I have 9 polygons, and want to get a polygon that is maximum neighbors with 3 other polygons such as polygons 1, 3, 7, 9 (yellow)
I think this is done using ST_Touches in postgis, but I just come up with represent it in postgis code like
select a.poly_name, b.poly_name from tb a, tb b where ST_Touches(a.geom, b.geom)
And say I want to output this like:
poly_name poly_name
1 2
1 4
1 5
So how I get idea to done with this?

Your hint with ST_Touches is correct, however to get the amount of neighbor cells from one column related to other records in the same table you either need to run a subquery or call the table twice in the FROM clause.
Given the following grid on a table called tb ..
.. you can filter the cells with three neighbor cells or less like this:
SELECT * FROM tb q1
WHERE (
SELECT count(*) FROM tb q2
WHERE ST_Touches(q2.geom,q1.geom)) <=3;
If you want to also list which are the neighbor cells you might wanna first join the cells that touch in the WHERE clause and in a subquery or CTE count the results:
WITH j AS (
SELECT
q1.poly_name AS p1,q2.poly_name p2,
COUNT(*) OVER (PARTITION BY q1.poly_name) AS qt
FROM tb q1, tb q2
WHERE ST_Touches(q2.geom,q1.geom))
SELECT * FROM j
WHERE qt <= 3;
Demo: db<>fiddle
Further reading:
Create Hexagons (maybe relevant for your project)
Window Functions

Related

count number of points in table A that exist in a square area in table B - SQL + QGIS

I have a table A that has scattered points existing in space. Table B has a number of squared perimeters. I want an SQL code that will write a table of the number of points in table A that fall in the squared perimeters of table B.
I am writing this SQL in QGIS.
This is what the problem looks like:
Whereby: The dots of Table A are in Blue and the squared blocks are in Table B.
Output should be something like:
Claim Cell # | Count
1 = 9, 2 = 0, 3 = 0,
Etc...
So far I have:
select "TENURE_NUM", count(*) from Samples the Station_ID, but I don't know what to do next, I am trying to look at examples online but I have never really used SQL before.
from what I understand maybe the following SQL request is a solution for your work :
-- Select the columns for output table
-- a = dots , b = square
SELECT
b.number,
count(a.*) as nbr_of_dots
FROM a, b
-- Condition for intersection and group for each square
WHERE st_intersects(a.geom,b.geom)
GROUP BY b.geom,b.id;

sql how to convert multi select field to rows with totals

I have a table that has a field where the contents are a concatenated list of selections from a multi-select form. I would like to convert the data in this field into in another table where each row has the text of the selection and a count the number of times this selection was made.
eg.
Original table:
id selections
1 A;B
2 B;D
3 A;B;D
4 C
I would like to get the following out:
selection count
A 2
B 3
C 1
D 2
I could easily do this with split and maps in javascript etc, but not sure how to approach it in SQL. (I use Postgresql) The goal is to use the second table to plot a graph in Google Data Studio.
A much simpler solution:
select regexp_split_to_table(selections, ';'), count(*)
from test_table
group by 1
order by 1;
You can use a lateral join and handy set-returning function regexp_split_to_table() to unnest the strings to rows, then aggregate and count:
select x.selection, count(*) cnt
from mytable t
cross join lateral regexp_split_to_table(t.selections, ';') x(selection)
group by x.selection

Postgres union of queries in loop

I have a table with two columns. Let's call them
array_column and text_column
I'm trying to write a query to find out, for K ranging from 1 to 10, in how many rows does the value in text_column appear in the first K elements of array_column
I'm expecting results like:
k | count
________________
1 | 70
2 | 85
3 | 90
...
I did manage to get these results by simply repeating the query 10 times and uniting the results, which looks like this:
SELECT 1 AS k, count(*) FROM table WHERE array_column[1:1] #> ARRAY[text_column]
UNION ALL
SELECT 2 AS k, count(*) FROM table WHERE array_column[1:2] #> ARRAY[text_column]
UNION ALL
SELECT 3 AS k, count(*) FROM table WHERE array_column[1:3] #> ARRAY[text_column]
...
But that doesn't looks like the correct way to do it. What if I wanted a very large range for K?
So my question is, is it possible to perform queries in a loop, and unite the results from each query? Or, if this is not the correct approach to the problem, how would you do it?
Thanks in advance!
You could use array_positions() which returns an array of all positions where the argument was found in the array, e.g.
select t.*,
array_positions(array_column, text_column)
from the_table t;
This returns a different result but is a lot more efficient as you don't need to increase the overall size of the result. To only consider the first ten array elements, just pass a slice to the function:
select t.*,
array_positions(array_column[1:10], text_column)
from the_table t;
To limit the result to only rows that actually contain the value you can use:
select t.*,
array_positions(array_column[1:10], text_column)
from the_table t
where text_column = any(array_column[1:10]);
To get your desired result, you could use unnest() to turn that into rows:
select k, count(*)
from the_table t, unnest(array_positions(array_column[1:10], text_column)) as k
where text_column = any(array_column[1:10])
group by k
order by k;
You can use the generate_series function to generate a table with the expected number of rows with the expected values and then join to it within the query, like so:
SELECT t.k AS k, count(*)
FROM table
--right join ensures that you will get a value of 0 if there are no records meeting the criteria
right join (select generate_series(1,10) as k) t
on array_column[1:t.k] #> ARRAY[text_column]
group by t.k
This is probably the closest thing to using a loop to go through the results without using something like PL/SQL to do an actual loop in a user-defined function.

QGis SQL Query - "Deleting almost duplicates entries"

I have a table with a distance matrix between all the points of an other table. On the distance matrix, I just kept the lignes with a distance less than 100m.
I call the points placed less than 100 m away from eachother duplicates entries. But on the distance matrix, each duplicates entry takes 2 lines
The distance matrix presents like this :
InputID TargetID Distance
1 2 75
1 3 35
2 1 75
3 1 35
I’d like to keep just one of those duplicates entry, which means that on the previous exemple I’d like to keep only the ligne of the 1, because the 2 and the 3 are placed less than 100m away of the 1. But if I only keep the 1 on the distance matrix, I also need to keep only the 1 on my original table.
I use the SQL Query tool of QGis but I don’t really know how to program. Can anyone help me please ?
Thanks !
You could use some subquery in join for retrive the value to delete
delete from my_table m2
inner join (
select m.distance, min(m.InputId) min_id
from my_table m.
inner join (
select distance, count(*)
from my_table
group by Distance
having count(*) > 1
) t on t.distance = m.distance
group by distance
) t2 on t2.distance = m2.distance and t2.min_id = m2.InputId

Show Null Values for SQL Grid in Virtual Table

I asked this question yesterday on how to calculate the number of customers in a grid square, and the solution I got was:
SELECT 10 * (customer_x / 10), 10 * (customer_y / 10), COUNT (*) FROM t_customer GROUP BY customer_x / 10, customer_y / 10 ORDER BY 3 DESC;
Now I need to present the grid squares that contain zero customers, and I'm not sure how to do this as the query is based on calculations of the usual numbers and the grid doesn't actually exist in the table. Should I use an ISNULL() function? The results for this query in my current database are:
90|90|7
30|20|4
-20|-40|2
-10|-20|2
-10|-10|2
-40|-40|1
-40|-30|1
-40|30|1
-30|0|1
-20|0|1
-20|30|1
-10|-30|1
-10|40|1
0|-20|1
0|-10|1
0|0|1
0|10|1
0|40|1
10|20|1
20|20|1
30|-40|1
30|30|1
But given that there are 100 grid squares in the area there are many without customers. I just need a query that will show all but the above grid squares. I'm using SQLite3 and any help would be greatly appreciated. Those in the (90,90) grid square are to be ignored.
Sample data:
I need to have a list of grid squares with no customers in from the 100 grid squares in a 10 x 10 grid (-50 to +50 in increments of 10). It may be easier to use a virtual table of all possible grid squares and to subtract the above query from it?
To make an (empty) grid square show up, add a dummy row for it, with a customer_id of NULL. NULL values are not counted:
SELECT ...x...,
...y...,
COUNT(customer_id)
FROM t_customer
GROUP BY ...x..., ...y...;
If you cannot change the database, you have to generate the empty rows with a recursive common table expression:
WITH RECURSIVE range(i) AS (
-- from -90 to +90 in steps of 10
SELECT -90
UNION ALL
SELECT i + 10
FROM range
LIMIT 19
), empty(x, y, customer_id) AS (
SELECT x.i,
y.i,
NULL
FROM range AS x
CROSS JOIN range AS y
)
SELECT ...x...,
...y...,
COUNT(customer_id)
FROM (SELECT * FROM t_customer
UNION ALL
SELECT * FROM empty)
GROUP BY ...x..., ...y...;