Oracle - Coordinate extraction from vertices (first, last and all vertices) - sql

For a given feature (line or area) and for all of its members I need to extract the coordinates of (1) all the vertices, (2) the first vertex and (3) the last vertex (3 separate queries to create 3 different sets of results)
I'm using Oracle spatial.
I’ve tested this sql code for the table ARAMAL (it's a 3d line entity; Primary key column: IPID; geometry column: GEOMETRY) and it works well.
(1) - List all the vertices
SELECT A.IPID, t.X, t.Y, t.Z, t.id FROM ARAMAL A,
TABLE(SDO_UTIL.GETVERTICES(A.GEOMETRY)) t ORDER BY A.IPID, t.id;
Result (example for IPID=1479723):
IPID X Y Z id
1479723 -99340.38408 -102364.3603 10 1
1479723 -99341.21035 -102366.2701 11 2
1479723 -99342.03375 -102368.1783 12 3
1479723 -99342.86238 -102370.0875 13 4
... ... .... ... ...
(2) - List the first vertex
SELECT A.IPID, t.X, t.Y, t.Z, t.id FROM ARAMAL A,
TABLE(SDO_UTIL.GETVERTICES(A.GEOMETRY)) t where t.id=1 ORDER BY A.IPID;
Result (example for IPID=1479723)
IPID X Y Z id
1479723 -99340.38408 -102364.3603 10 1
(3) How can I obtain the last vertex purely with sql (no additional functions)?
(Expected) Result (example for IPID=1479723)
IPID X Y Z id
1479723 -99342.86238 -102370.0875 13 4
I guess this process could run faster if I use specific functions - I would also like to be able to use them.
I’ve come across a great site (Simon Greener) with some functions that I guess could do the trick
http://spatialdbadvisor.com/oracle_spatial_tips_tricks/322/st_vertexn-extracting-a-specific-point-from-any-geometry
The functions are:
ST_StartPoint
CREATE OR REPLACE
FUNCTION ST_StartPoint(p_geom IN mdsys.sdo_geometry)
RETURN mdsys.sdo_geometry
IS
BEGIN
RETURN ST_PointN(p_geom,1);
END ST_StartPoint;
/
ST_EndPoint
CREATE OR REPLACE
FUNCTION ST_EndPoint(p_geom IN mdsys.sdo_geometry)
RETURN mdsys.sdo_geometry
IS
BEGIN
RETURN ST_PointN(p_geom,-1);
END ST_EndPoint;
/
I’m a newbie to this world and I don’t really get the syntax of these functions…
For the table ARAMAL that I’ve used before how should I use/apply them to get the results (and in the format) I need?
IPID X Y Z id
1479723 -99340.38408 -102364.3603 10 1
....
Thanks in advance,
Best regards,
Pedro

It doesn't matter if you are working with spatial data, you are interested in row which has maximum id for given ipid, so you can run it like here:
select *
from (
select a.ipid, t.x, t.y, t.z, t.id,
max(t.id) over (partition by a.ipid) mx_id
from aramal a, table(sdo_util.getvertices(a.geometry)) t)
where id = mx_id;
demo
There are several ways to get last row, you can use row_number(), subquery, like in many top-n questions on this site.

Related

WHILE Window Operation with Different Starting Point Values From Column - SQL Server [duplicate]

In SQL there are aggregation operators, like AVG, SUM, COUNT. Why doesn't it have an operator for multiplication? "MUL" or something.
I was wondering, does it exist for Oracle, MSSQL, MySQL ? If not is there a workaround that would give this behaviour?
By MUL do you mean progressive multiplication of values?
Even with 100 rows of some small size (say 10s), your MUL(column) is going to overflow any data type! With such a high probability of mis/ab-use, and very limited scope for use, it does not need to be a SQL Standard. As others have shown there are mathematical ways of working it out, just as there are many many ways to do tricky calculations in SQL just using standard (and common-use) methods.
Sample data:
Column
1
2
4
8
COUNT : 4 items (1 for each non-null)
SUM : 1 + 2 + 4 + 8 = 15
AVG : 3.75 (SUM/COUNT)
MUL : 1 x 2 x 4 x 8 ? ( =64 )
For completeness, the Oracle, MSSQL, MySQL core implementations *
Oracle : EXP(SUM(LN(column))) or POWER(N,SUM(LOG(column, N)))
MSSQL : EXP(SUM(LOG(column))) or POWER(N,SUM(LOG(column)/LOG(N)))
MySQL : EXP(SUM(LOG(column))) or POW(N,SUM(LOG(N,column)))
Care when using EXP/LOG in SQL Server, watch the return type http://msdn.microsoft.com/en-us/library/ms187592.aspx
The POWER form allows for larger numbers (using bases larger than Euler's number), and in cases where the result grows too large to turn it back using POWER, you can return just the logarithmic value and calculate the actual number outside of the SQL query
* LOG(0) and LOG(-ve) are undefined. The below shows only how to handle this in SQL Server. Equivalents can be found for the other SQL flavours, using the same concept
create table MUL(data int)
insert MUL select 1 yourColumn union all
select 2 union all
select 4 union all
select 8 union all
select -2 union all
select 0
select CASE WHEN MIN(abs(data)) = 0 then 0 ELSE
EXP(SUM(Log(abs(nullif(data,0))))) -- the base mathematics
* round(0.5-count(nullif(sign(sign(data)+0.5),1))%2,0) -- pairs up negatives
END
from MUL
Ingredients:
taking the abs() of data, if the min is 0, multiplying by whatever else is futile, the result is 0
When data is 0, NULLIF converts it to null. The abs(), log() both return null, causing it to be precluded from sum()
If data is not 0, abs allows us to multiple a negative number using the LOG method - we will keep track of the negativity elsewhere
Working out the final sign
sign(data) returns 1 for >0, 0 for 0 and -1 for <0.
We add another 0.5 and take the sign() again, so we have now classified 0 and 1 both as 1, and only -1 as -1.
again use NULLIF to remove from COUNT() the 1's, since we only need to count up the negatives.
% 2 against the count() of negative numbers returns either
--> 1 if there is an odd number of negative numbers
--> 0 if there is an even number of negative numbers
more mathematical tricks: we take 1 or 0 off 0.5, so that the above becomes
--> (0.5-1=-0.5=>round to -1) if there is an odd number of negative numbers
--> (0.5-0= 0.5=>round to 1) if there is an even number of negative numbers
we multiple this final 1/-1 against the SUM-PRODUCT value for the real result
No, but you can use Mathematics :)
if yourColumn is always bigger than zero:
select EXP(SUM(LOG(yourColumn))) As ColumnProduct from yourTable
I see an Oracle answer is still missing, so here it is:
SQL> with yourTable as
2 ( select 1 yourColumn from dual union all
3 select 2 from dual union all
4 select 4 from dual union all
5 select 8 from dual
6 )
7 select EXP(SUM(LN(yourColumn))) As ColumnProduct from yourTable
8 /
COLUMNPRODUCT
-------------
64
1 row selected.
Regards,
Rob.
With PostgreSQL, you can create your own aggregate functions, see http://www.postgresql.org/docs/8.2/interactive/sql-createaggregate.html
To create an aggregate function on MySQL, you'll need to build an .so (linux) or .dll (windows) file. An example is shown here: http://www.codeproject.com/KB/database/mygroupconcat.aspx
I'm not sure about mssql and oracle, but i bet they have options to create custom aggregates as well.
You'll break any datatype fairly quickly as numbers mount up.
Using LOG/EXP is tricky because of numbers <= 0 that will fail when using LOG. I wrote a solution in this question that deals with this
Using CTE in MS SQL:
CREATE TABLE Foo(Id int, Val int)
INSERT INTO Foo VALUES(1, 2), (2, 3), (3, 4), (4, 5), (5, 6)
;WITH cte AS
(
SELECT Id, Val AS Multiply, row_number() over (order by Id) as rn
FROM Foo
WHERE Id=1
UNION ALL
SELECT ff.Id, cte.multiply*ff.Val as multiply, ff.rn FROM
(SELECT f.Id, f.Val, (row_number() over (order by f.Id)) as rn
FROM Foo f) ff
INNER JOIN cte
ON ff.rn -1= cte.rn
)
SELECT * FROM cte
Not sure about Oracle or sql-server, but in MySQL you can just use * like you normally would.
mysql> select count(id), count(id)*10 from tablename;
+-----------+--------------+
| count(id) | count(id)*10 |
+-----------+--------------+
| 961 | 9610 |
+-----------+--------------+
1 row in set (0.00 sec)

Find neighboring polygons with maximum of 3 other polygons

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

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.

UDF that returns a table

In BigQuery, how does one write a UDF that returns a table? What I would like is a CTE that is able to accept parameters. As far as I can tell, UDFs only returns scalars, am I correct?
UDF can return ARRAYS of different types including ARRAY of STRUCTs
But obviously it is not the same as returning table, which is not really supported by BigQuery UDF [yet]
P.S. If you have specific issue that you want to resolve - ask specific question and someone will help
Below are two relatively naive and useless (from practical standpoint) examples - but I hope they show concept of using ARRAYS to mimic at some extend CTE
Example #1
#standardSQL
CREATE TEMPORARY FUNCTION pseudoCTE(x INT64, y INT64) AS (
GENERATE_ARRAY(x, y)
);
SELECT * FROM UNNEST(pseudoCTE(1,5)) z
with result
Row z
1 1
2 2
3 3
4 4
5 5
Example #2
#standardSQL
CREATE TEMPORARY FUNCTION pseudoCTE(x INT64, y INT64) AS (
ARRAY(SELECT AS STRUCT z AS id, RAND() AS value
FROM UNNEST(GENERATE_ARRAY(x, y)) z)
);
SELECT * FROM UNNEST(pseudoCTE(1,5))
Row id value
1 1 0.9319445195173228
2 2 0.36404932965409453
3 3 0.4615807541752828
4 4 0.5504890432993448
5 5 0.29635275888268836
In BigQuery, you now have table functions:
A table function, also called a table-valued function (TVF), is a user-defined function that returns a table.
An example (found in the Google documentation):
CREATE OR REPLACE TABLE FUNCTION mydataset.names_by_year(y INT64)
AS
SELECT year, name, SUM(number) AS total
FROM `bigquery-public-data.usa_names.usa_1910_current`
WHERE year = y
GROUP BY year, name
To execute it:
SELECT * FROM mydataset.names_by_year(1950)
ORDER BY total DESC
LIMIT 5

please help me in building a query for the below mentioned table in sql

i have a table name conversion and i have these below mentioned columns in it i want to multiply Length\width row elements l*w of 'dimension' values and display them in another new table
Please let me know if anything changes for the same logic in ms access
probably it is simple but i dont know exact query to solve the problem waiting for your solutions
ID area length/width dimensions **new column(L*W) here**
1 1 l 3 3*5=15
2 1 w 5
3 2 l 4
4 2 w 8
5 3 l 6
6 3 w 10
7 4 l 12
8 4 w 13
9 4 W 10
waiting for your reply
You could query the table twice: once for lengths and once for widths and then join by area and multiply the values:
select length.area, length.dimension * width.dimension
from
(select area, dimension from conversion where lenwidth = 'l') length
inner join
(select area, dimension from conversion where lenwidth = 'w') width
on length.area = width.area;
Two remarks:
I suppose that it is a typo that you have two width entries for area 4? Otherwise you would have to decide which value to take in above select statement.
It would not be a good idea to keep the old table and have a new table holding the results. What if you change a value? You would have to remember to change the result accordingly every time. So either ditch the old table or use a view instead of a new table.
Try this
select *,
dimensions*(lead(dimensions) over(order by id)) product
from table1;
Or if you want for the set of area then
select *,
case when length_width='l' and (lead(length_width) over(order by id))='w'
then dimensions*(lead(dimensions) over(order by id))
else 0
end as product
from table1;
fiddle