Unexpected result from CASE referencing another expression - sql

The following statement always returns the result from st_area(st_buffer(polygon,100)).
select st_Area(polygon) as area,
case when area>100000 then st_area(st_buffer(polygon,500))
else st_area(st_buffer(polygon,100))
end from polygons limit 10;
area | st_area
------------------+------------------
383287.287473659 | 723738.615102036
47642.5395246768 | 192575.823383778
45546.753026985 | 174122.420564731
435204.455923533 | 725419.735987631
839954.564052786 | 1268251.88626391
315213.27742828 | 630424.785088617
966620.061916605 | 1447647.57269461
38446.6010009923 | 151584.647252579
82576.1182937309 | 238095.988431594
321682.125463567 | 695462.262796463
(10 rows)
st_area should have been the result of st_buffer(polygon,500) when area>100000 as shown below:
area | st_area
------------------+------------------
383287.287473659 | 2702203.34758147
47642.5395246768 | 192575.823383778
45546.753026985 | 174122.420564731
435204.455923533 | 2507469.89929028
839954.564052786 | 3568866.96452707
315213.27742828 | 2453576.33477712
966620.061916605 | 3953365.12876066
38446.6010009923 | 151584.647252579
82576.1182937309 | 238095.988431594
321682.125463567 | 2628693.69179652
(10 rows)
Can someone explain?

It doesn't become completely clear from the question (yet), but my educated guess is you want this:
SELECT st_Area(polygon) AS area -- or pick some other name!
, CASE WHEN st_Area(polygon) > 100000
THEN st_area(st_buffer(polygon,500))
ELSE st_area(st_buffer(polygon,100)) END AS st_area
FROM polygons
LIMIT 10;
You cannot reference the column alias (name of the output column) in another item of the same SELECT list. You can only reference input column names. So you have to repeat the expression or use a subquery:
SELECT area
, CASE WHEN area > 100000
THEN st_area(st_buffer(polygon,500))
ELSE st_area(st_buffer(polygon,100)) END AS st_area
FROM (SELECT st_Area(polygon) AS area, polygon FROM polygons LIMIT 10) sub;
Normally you should get a syntax error immediately. Obviously, there is another column named area in your base table. Hence the confusion. Additional wisdom to take away from this:
It's better to use a name different from any input column when attaching an alias to an output column.
Always include table definitions in questions. Clarifies a lot.

Related

Using ST_ClosestPoint, ST_StartPoint, and ST_EndPoint to find closest object to beginning and end points of lines

I need to find the closest points to either end of a line using geometry from two separate tables. So far I was able to use the following to find the geometry of the end points:
SELECT "id", ST_StartPoint(dmp.geom) AS upstream,
ST_EndPoint(dmp.geom) AS downstream
FROM sewers.pipes,
LATERAL ST_Dump("geom") AS dmp
;
Using this, I want to use ST_ClosestPoint to find structures that are the closest to the end points of pipes. This is what I've come up with so far:
SELECT ST_ClosestPoint('POINT(SELECT ST_StartPoint(dmp.geom) AS upstream
FROM sewers.pipes,
LATERAL ST_Dump("geom") AS dmp)',
('LINESTRING(SELECT geom from sewers.pipes)'))
However, this gives the following error:
ERROR: parse error - invalid geometry
Line 1: SELECT ST_ClosestPoint('POINT(SELECT ST_STartPoint(dmp.geom)...
HINT: "POINT(SE" <-- parse error at position 8 within geometry
SQL state: XX000
Character: 24
I have provided some sample data below.
sewers.pipes <-- these are the lines
| id | geom |
| -------- | -------------- |
| 822 | 0105000020950B00000100000001020000000200000046243EC3282608418D28242D6C1B3D4128531BE88A2608418284B3EF561B3D41 |
| 6660 | 0105000020950B0000010000000102000000020000004ABF2CBC86B108413B93650696323D413C487924CCB10841925D490495323D41 |
sewers.structures <-- these are points
| id | geom |
| -------- | -------------- |
| 2014 | 0104000020950B00000100000001010000001026FA48113B07410D6A8412CF1D3D41 |
| 22979 | 0104000020950B0000010000000101000000BA1BF246E6DD0741D064CB58C2E43C41 |
I know that there may be an error that comes from selecting multiple data points, eg. more than one row returned by a subquery used as an expression, so that may be an issue as well. Any help would be greatly appreciated.
Try with distinct LATERALs for upstream and downstream:
SELECT p.id,
(dump_line).geom,
ST_EndPoint((dump_line).geom) AS downstream,
geom_closest_downstream,
ST_StartPoint((dump_line).geom) AS upstream,
geom_closest_upstream
FROM sewers.pipes p,
LATERAL ST_Dump(p.geom) dump_line,
LATERAL (SELECT s.geom
FROM sewers.structures s
ORDER BY ST_EndPoint((dump_line).geom)<->s.geom
LIMIT 1) j (geom_closest_downstream),
LATERAL (SELECT s.geom
FROM sewers.structures s
ORDER BY ST_StartPoint((dump_line).geom)<->s.geom
LIMIT 1) i (geom_closest_upstream);
To access the geometries from ST_Dump you have to wrap its output with parenthesis, e.g. (ST_Dump(multiline)).geom
The operator <-> at the ORDER BY clause corresponds to distance. So, using it combined with a LIMIT 1 you get only the closest distance.
Demo: db<>fiddle

Filter list of points using list of Polygons

Given a list of points and a list of polygons. How do you return a list of points (subset of original list of points) that is in any of the polygons on the list
I've removed other columns in the sample tables to simplify things
Points Table:
| Longitude| Latitude |
|----------|-----------|
| 7.07491 | 51.28725 |
| 3.674765 | 51.40205 |
| 6.049105 | 51.86624 |
LocationPolygons Table:
| LineString |
|----------------------|
| CURVEPOLYGON (COMPOUNDCURVE (CIRCULARSTRING (-122.20 47.45, -122.81 47.0, -122.942505 46.687131 ... |
| MULTIPOLYGON (((-110.3086 24.2154, -110.30842 24.2185966, -110.3127...
If I had row from the LocationPolygons table I could do something like
DECLARE #homeLocation geography;
SET #homeLocation = (select top 1 GEOGRAPHY::STGeomFromText(LineString, 4326)
FROM LocationPolygon where LocationPolygonId = '123abc')
select Id, Longitude, Latitude, #homeLocation.STContains(geography::Point(Latitude, Longitude, 4326))
as IsInLocation from Points PointId in (1, 2, 3,)
which would return what I want in a format like the below. However this is only true for just one location on the list
| Id | Longitude| Latitude | IsInLocation |
|----|----------|-----------|--------------|
| 1 | 7.07491 | 51.28725 | 0 |
| 2 | 3.674765 | 51.40205 | 1 |
| 3 | 6.049105 | 51.86624 | 0 |
How do I handle the scenario with multiple rows of the LocationPolygon table?
I'd like to know
if any of the points are in any of the locationPolygons?
what specific location polygon they are in? or if they are in more than one polygon.
Question 2 is more of an extra. Can someone help?
Update #1
In response to #Ben-Thul answer.
Unfortunately I don't have access/permission to make changes to the original tables, I can request access but not certain it'll be given. So not certain I'll be able to add the columns or create the index. Although I can create temp tables in a stored proc, I might be able to use test your solution that way
I stumbled on an answer like the below, but slightly worried about performance implications of using a cross join.
WITH cte AS (
select *, (GEOGRAPHY::STGeomFromText(LineString, 4326)).STContains(geography::Point(Latitude, Longitude, 4326)) as IsInALocation from
(
select Longitude, Latitude from Points nolock
) a cross join (
select LineString FROM LocationPolygons nolock
) b
)
select * from cte where IsInALocation = 1
Obviously, it's better to look at a query plan but is the solution I stumbled upon essentially the same as yours? Are there any potential issues that I missed. Apologies for this but my sql isn't very good.
Question 1 shouldn't be too bad. First, some set up:
alter table dbo.Points add Point as (GEOGRAPHY::Point(Latitude, Longitude, 4326));
create spatial index IX_Point on dbo.Points (Point) with (online = on);
alter table dbo.LocationPolygon add Polygon as (GEOGRAPHY::STGeomFromText(LineString, 4326));
create spatial index IX_Polygon on dbo.LocationPolygon (Polygon) with (online = on);
This will create a computed column on each of your tables that is of type geography that has a spatial index on it.
From there, you should be able to do something like this:
select pt.ID,
pt.Longitude,
pt.Latitude,
coalesce(pg.IsInLocation, 0) as IsInLocation
from Points as pt
outer apply (
select top(1) 1 as IsInLocation
from dbo.LocationPolygon as pg
where pg.Polygon.STContains(p.Point) = 1
) as pg;
Here, you're selecting every row from the Points table and using outer apply to see if any polygons contain that point. If one does (it doesn't matter which one), that query will return a 1 in the result set and bubble that back up to the driving select.
To extend this to Question 2, you can remove the top() from the outer apply and have it return either the IDs from the Polygon table or whatever you want. Note though that it'll return one row per polygon that contains the point, potentially changing the cardinality of your result set!

Combine query to get all the matching search text in right order

I have the following table:
postgres=# \d so_rum;
Table "public.so_rum"
Column | Type | Collation | Nullable | Default
-----------+-------------------------+-----------+----------+---------
id | integer | | |
title | character varying(1000) | | |
posts | text | | |
body | tsvector | | |
parent_id | integer | | |
Indexes:
"so_rum_body_idx" rum (body)
I wanted to do phrase search query, so I came up with the below query, for example:
select id from so_rum
where body ## phraseto_tsquery('english','Is it possible to toggle the visibility');
This gives me the results, which only match's the entire text. However, there are documents, where the distance between lexmes are more and the above query doesn't gives me back those data. For example: 'it is something possible to do toggle between the. . . visibility' doesn't get returned. I know I can get it returned with <2> (for example) distance operator by giving in the to_tsquery, manually.
But I wanted to understand, how to do this in my sql statement itself, so that I get the results first with distance of 1 and then 2 and so on (may be till 6-7). Finally append results with the actual count of the search words like the following query:
select count(id) from so_rum
where body ## to_tsquery('english','string & string . . . ')
Is it possible to do in a single query with good performance?
I don't see a canned solution to this. It sounds like you need to use plainto_tsquery to get all the results with all the lexemes, and then implement your own custom ranking function to rank them by distance between the lexemes, and maybe filter out ones with the wrong order.

Single record buffering in SAP ABAP

My table is stud.
+-----+------+-------+
| no | name | grade |
+-----+------+-------+
| 101 | naga | A |
| 102 | raj | A |
| 103 | john | A |
+-----+------+-------+
The query I'm using is:
SELECT * FROM stud WHERE no = 101 AND grade = 'A'.
If am using single record buffering, how much data is being stored in the buffer area?
This query doesn't do anything. There is no "into" clause. meaning it wont store anything selected.
You are probably looking to do something like this....
SELECT * FROM stud into wa_stud WHERE no = 101 AND grade = 'A'.
"processing of each single row is performed here
endselect.
or perhaps something like this, where only 1 row (the first rows ordered by primary key) is selected...
select single * from stud into wa_stud where no = 101 and grade = 'A' .
or perhaps you want everything brought in to a table, meaning number and grade does not include the full primary key.
select * from stud into table it_stud where no = 101 and grade = 'A'.
this is from ABAP Keyword documentation in SE38:
SAP Buffer - Single Record Buffering
Only those rows in the table are buffered that are actually accessed.
This requires less space in the buffer than when using generic or full
buffering. On the other hand, more administration work is required and
significantly more direct database accesses.
So since your query returns a single record (based on the data you displayed) it should just get one row and hold in the buffer.
I'd suggest looking at SAP help and Google - also have a look at SELECT SINGLE and incompletely specified keys - there used to be a problem with the buffer being bypassed in some situations - have a read for reference.

Moving data by length

I have to move words table's data to another tables.
For example words table is :
------------------------------
| word | type |
|------------------|---------|
| car | NA |
| home | NA |
| question | PR |
------------------------------
I have to move this data by length . For example , car's length is 3 , and car will move to 3-char table (with type column). And question will moved to 8-char .
How can i do it with SQL commands .
Sort of an incomplete question, but something like this might help point you in the right direction:
INSERT INTO words_3char SELECT word FROM all_words WHERE LENGTH(word)=3;
DELETE FROM all_words WHERE LENGTH(word)=3;
I'm not going to ask why you need to do all this moving around, but I'm not sure its a good idea. Assuming it is, take a look at the Length() function for mysql and then try something like this.
Insert into table_Char3(Word) Values (
Select Word from Words where Length(word) = 3)
You can move them to new tables like this
create table word1char as select word from words where length(trim(word)) = 1
..
create table word3chars as select word from words where length(trim(word)) = 3