SDO_INSIDE returns zero records - oracle

So I have a table village:
CREATE TABLE village (
building_id integer PRIMARY KEY,
name VARCHAR2(30),
visitors integer,
building SDO_GEOMETRY
);
And a table visitors:
create table visitors(
id integer,
position SDO_GEOMETRY
);
Here are the inserts:
INSERT INTO village VALUES(2,'KircheV2', 4,
SDO_GEOMETRY(
2003,
NULL,
NULL,
SDO_ELEM_INFO_ARRAY(1,1003,1),
SDO_ORDINATE_ARRAY(100,100, 100,120, 120,100, 120,120)
)
);
INSERT INTO visitors VALUES (1,
SDO_GEOMETRY(
2001,
NULL,
SDO_POINT_TYPE(110, 110, NULL),
NULL,
NULL
)
);
For some reason when I try to get all visitors which are INSIDE the "KircheV2" the SQL statement always returns zero records:
SELECT * FROM visitors,village WHERE village.name like 'KircheV2' and (SDO_INSIDE(village.building,visitors.POSITION) = 'TRUE');
What can be the reason behind it? The coordinates 110;110 should actually be right in the middle of the building, so it should be inside the building.

Your data is incorrect. You can verify it like this:
SQL> select sdo_geom.validate_geometry_with_context (building,0.005) from village;
SDO_GEOM.VALIDATE_GEOMETRY_WITH_CONTEXT(BUILDING,0.005)
-------------------------------------------------------------------------------
13348 [Element <1>] [Ring <1>]
1 row selected.
That error means:
ORA-13348: polygon boundary is not closed
In Oracle (actually all storage systems) according to OGC rules, the polygons must close, i.e. the first vertex must repeat as last vertex. So:
INSERT INTO village VALUES(2,'KircheV2', 4,
SDO_GEOMETRY(
2003,
NULL,
NULL,
SDO_ELEM_INFO_ARRAY(1,1003,1),
SDO_ORDINATE_ARRAY(100,100, 100,120, 120,100, 120,120, 100,100)
)
);
But the select still fails to return any result. Why is that ?
SQL> select sdo_geom.validate_geometry_with_context (building,0.005) from village;
SDO_GEOM.VALIDATE_GEOMETRY_WITH_CONTEXT(BUILDING,0.005)
-------------------------------------------------------------------------------
13349 [Element <1>] [Ring <1>][Edge <2>][Edge <4>]
1 row selected.
This error means:
ORA-13349: polygon boundary crosses itself
That makes sense: the vertices clearly form a butterfly shape:
100,100, 100,120, 120,100, 120,120, 100,100
Assuming you want to form a simple rectangle, then the proper shape is:
100,100, 100,120, 120,120, 120,100, 100,100
INSERT INTO village VALUES(2,'KircheV2', 4,
SDO_GEOMETRY(
2003,
NULL,
NULL,
SDO_ELEM_INFO_ARRAY(1,1003,1),
SDO_ORDINATE_ARRAY(100,100, 100,120, 120,120, 120,100, 100,100)
)
);
Still no result. Why ?
SQL> select sdo_geom.validate_geometry_with_context (building,0.005) from village;
SDO_GEOM.VALIDATE_GEOMETRY_WITH_CONTEXT(BUILDING,0.005)
-------------------------------------------------------------------------------
13367 [Element <1>] [Ring <1>]
1 row selected.
This means:
ORA-13367: wrong orientation for interior/exterior rings
Rings in polygons must be properly orientated. Outer rings must be counter-clockwise, inner rings (holes) must be clockwise. So you need to write it like this:
100,100, 120,100, 120,120, 100,120, 100,100
INSERT INTO village VALUES(2,'KircheV2', 4,
SDO_GEOMETRY(
2003,
NULL,
NULL,
SDO_ELEM_INFO_ARRAY(1,1003,1),
SDO_ORDINATE_ARRAY(100,100, 120,100, 120,120, 100,120, 100,100)
)
);
Still no results! But that is because your query is incorrectly formulated. SDO_INSIDE(a,b) finds all occurrences of A that are fully inside B. In your case, it is like asking for buildings that are inside a visitor. Clearly you want the reverse, so either say: SDO_INSIDE(visitor, building) or SDO_CONTAINS (building,visitor), like this:
SELECT * FROM visitors,village WHERE village.name like 'KircheV2' and SDO_INSIDE(visitors.POSITION,village.building) = 'TRUE';
SELECT * FROM visitors,village WHERE village.name like 'KircheV2' and SDO_CONTAINS(village.building,visitors.POSITION) = 'TRUE';
Some additional comments:
Your example is purely artificial, I imagine ? In real life, your polygons will come from some GIS systems - like ESRI shapefiles that you load into the database, or shapes captured from some GIS tool. Either way, both will produce correct shapes since all tools apply then OGC rules for shape closure and orientation. If not the validation function will tell you the errors, and the sdo_util.rectify_geometry function will correct the fundamental errors.
You also need to understand the spatial operators (INSIDE vs CONTAINS etc) and their effects. The documentation explains what they mean. Note that the set of SDO_xxx operators is slightly different from the ST_xxx functions defined by the OGC.
You do not specify any coordinate system (SDO_SRID is NULL). While that works in your artificial example, in real life you should always use the proper coordinate system. In particular if your shapes are geodetic (in long/lat), you must say so by using the proper SRID: 4326. This guarantees that all computations are done within the context of the ellipsoidal shape of the earth. That is especially important if you want to perform distance-based queries or measure lengths, distances or areas. Using the proper SRID for projected data is equally important. It lets you perform queries irrespective of the coordinate systems used: for example find which parcel (in a local projection) a GPS point is located.
Performance and indexing. Make sure you have spatial indexes in place. While Oracle 12.2 lets you do queries without defining an index, prior releases always require one (and fail if none exists). And performing a query on a table without a spatial index can be really slow if that table is even moderately large. For example, consider your villages and visitors example. Say you want to find out all visitors in one specific building, from a table of 10 million visitors. Without any index on the visitors table, the database will need to compare each and every visitor with the selected building. That will be expensive in CPU (I/Os are not a real issue).
Finally, it is important to write the queries properly. In an operator like F(a,b), b is used to search a so b should be the smaller set. For example finding all visitors in this building must be written as SDO_INSIDE(visitors, buildings). The reverse (in which building is this customer ?) write as SDO_CONTAINS(buildings, visitors).

Related

Teradata 3848 and small int with COMPRESS

I am learning Teradata and have run into an issue when I UNION two queries. The error I run into is error 3848, "The ORDER BY clause must contain only integer constants".
I checked the table definition and all the columns I have been retrieving are Small Ints with identical definitions, except for one which uses COMPRESS with a long series of consecutive numbers starting from 3.
SELECT
COALESCE (ContractType, 'InvalidType') AS "Contract",
COALESCE (ContractStatus, 'InvalidStatus') AS "Status",
COUNT(ContractType) AS "Contract_Type_Count",
COUNT (ContractStatus) AS "Contract_Status_Count"
NULL AS "negCodeErr_count"
FROM fund_inventory_db.ContractDetail
GROUP BY CUBE (ContractType, ContractStatus)
UNION
NULL,
NULL,
NULL,
NULL,
SELECT COUNT(*)
FROM fund_inventory_db.ContractDetail
WHERE ContractSource = -2
ORDER BY ContractType, ContractStatus;
The definitions for all those fields look like this:
[...columnName...] SMALLINT NOT NULL DEFAULT 0
Except for one column, which is:
[...columnName...] SMALLINT NOT NULL DEFAULT 0 COMPRESS (3,4,5,6,7,8...)
Does using COMPRESS like this make it possible that they are not able to order normally? As in, if one column uses COMPRESS(3,4,5,6,7...) and the other either uses COMPRESS (1,2,3,4,5...) or does not use COMPRESS at all, would that make a difference?
This might be embarrassing, but is it actually possible to use a UNION where one of the queries is using CUBE()?
Sorry, this is all new to me and my mentor is moving a little fast! I sincerely appreciate your time.

Oracle Spatial - SDO_BUFFER does not work?

I have a table which has SDO_Geometries and I query all the geometries to find their start and end point, then I insert these points to another table called ORAHAN. Now my main purpose is for each point in orahan I must find if it is intersects with another point in orahan when giving 2 cm buffer to points.
So I write some pl sql using Relate and Bufer functions but when I check some records in Map Info, I saw there is points within 1 cm area from itself but no record in intersections table called ORAHANCROSSES.
Am I use these functions wrongly or what?
Note: I am using Oracle Database 11g Enterprise Edition Release 11.2.0.1.0 - 64bit Production and
PL/SQL Release 11.2.0.1.0 - and SDO_PACKAGE
ORAHAN has approximately 400 thousands records.(points and other columns.)
declare
BEGIN
for curs in (select * from ORAHAN t) loop
for curs2 in (select *
from ORAHAN t2
where SDO_RELATE(t2.geoloc,SDO_GEOM.SDO_BUFFER(curs.geoloc,0.02,0.5) ,
'mask=ANYINTERACT') = 'TRUE'
and t2.mi_prinx <> curs.mi_prinx) loop
Insert INTO ORAHANCROSSES
values
(curs.Mip, curs.Startmi, curs2.Mip, curs2.Startmi);
commit;
end loop;
end loop;
END;
And this is MapInfo map image that shows 3 points which are close to each other aproximately 1 centimeter. But in the orahancrosses there is no record matching these 3.
Note: 0,00001000km equals 1cm
Orahan Metadata:
select * from user_sdo_geom_metadata where table_name = 'ORAHAN';
And diminfo:
What is the coordinate system of your data ? And, most important, what tolerance have you set in your metadata ?
Some other comments:
1) Don't use a relate with buffer approach. Just use a within-distance approach.
2) You don't need a PL/SQL loop for that sort of query just use a simple CTAS:
create table orahancrosses as
select c1.mip mip_1, c1.startmi startmi_1, c2.mip mip_2, c2.startmi startmi_2
from orahan c1, orahan c2
where sdo_within_distance (c2.geoloc, c1.geoloc, 'distance=2 unit=cm') = 'TRUE'
and c2.mi_prinx <> c1.mi_prinx;
3) As written, couples of points A and B that are within 2 cm will be returned twice: once as (A,B) and once again as (B,A). To avoid that (and only return one of the cases), then write the query like this:
create table orahancrosses as
select c1.mip mip_1, c1.startmi startmi_1, c2.mip mip_2, c2.startmi startmi_2
from orahan c1, orahan c2
where sdo_within_distance (c2.geoloc, c1.geoloc, 'distance=2 unit=cm') = 'TRUE'
and c1.rowid < c2.rowid;
3) Processing the number of points you mention (400000+) should run better using the SDO_JOIN technique, like this:
create table orahancrosses as
select c1.mip mip_1, c1.startmi startmi_1, c2.mip mip_2, c2.startmi startmi_2
from table (
sdo_join (
'ORAHAN','GEOLOC',
'ORAHAN','GEOLOC',
'DISTANCE=2 UNIT=CM'
)
) j,
orahan c1,
orahan c2
where j.rowid1 < j.rowid2
and c1.rowid = j.rowid1
and c2.rowid = j.rowid2;
This will probably still take time to process - depending on the capacity of your database server. If you are licences for Oracle Enterprise Edition and your hardware has the proper capacity (# of cores) then parallelism can reduce the elapsed time.
4) You say you are using Oracle 11g. What exact version ? Version 11.2.0.4 is the terminal release for 11gR2. Anything older is no longer supported. By now you should really be on 12cR1 (12.1.0.2). The major benefit of 12.1.0.2 in your case s the Vector Performance Accelerator feature that speeds up a number of spatial functions and operators (only if you own the proper Oracle Spatial licenses - it is not available with the free Oracle Locator feature).
======================================
Using the two points in your example. Let's compute the distance:
select sdo_geom.sdo_distance(
sdo_geometry (2001,null,sdo_point_type(521554.782174622,4230983.08336913,null),null,null),
sdo_geometry (2001,null,sdo_point_type(521554.782174622,4230983.07336716,null),null,null),
0.005
) distance
from dual;
DISTANCE
----------
.01000197
1 row selected.
Notice I don't specify any SRID. Assuming the coordinates are expressed in meters, the distance between them is indeed a little more than 1 cm.
======================================
The reason why your original syntax does not work is, as you noticed, because of the tolerance you specify for the SDO_BUFFER() call. You pass it as 0.5 (=50cm) to produce a buffer with a radius of 0.02 (2cm). The effect is that the buffer produced effectively dissolves into the point itself.
For example at tolerance 0.5:
select sdo_geom.sdo_buffer(sdo_geometry (2001,null,sdo_point_type(521554.782174622,4230983.08336913,null),null,null),0.02,0.5) from dual;
Produces:
SDO_GEOMETRY(2001, NULL, SDO_POINT_TYPE(521554.782, 4230983.08, NULL), NULL, NULL)
At tolerance 0.005:
select sdo_geom.sdo_buffer(sdo_geometry (2001,null,sdo_point_type(521554.782174622,4230983.08336913,null),null,null),0.02,0.005) from dual;
You get the proper buffer:
SDO_GEOMETRY(2003, NULL, NULL, SDO_ELEM_INFO_ARRAY(1, 1003, 2), SDO_ORDINATE_ARRAY(521554.782, 4230983.06, 521554.802, 4230983.08, 521554.782, 4230983.1, 521554.762, 4230983.08, 521554.782, 4230983.06))
And the very close point now matches with that buffer:
select sdo_geom.relate(
sdo_geom.sdo_buffer(sdo_geometry (2001,null,sdo_point_type(521554.782174622,4230983.08336913,null),null,null),0.02,0.005),
'determine',
sdo_geometry (2001,null,sdo_point_type(521554.782174622,4230983.07336716,null),null,null),
0.005
) relation
from dual;
RELATION
-------------------------
CONTAINS
1 row selected.
======================================
Now the fact that your data does not have a proper explicit SRID means that the use of explicit units in measurements or distance-based searches will not work. Because the database does not know what coordinate system your data is in, it does not know how to determine that two points are less than a set number of cm or m apart. All you can do is assume the coordinates are in meters.
So in the examples I give above, replace 'DISTANCE=2 UNIT=CM' with 'DISTANCE=0.02'

Oracle SQL Query Performance, Function based Indexes

I have been trying to fine tune a SQL Query that takes 1.5 Hrs to process approx 4,000 error records. The run time increases along with the number of rows.
I figured out there is one condition in my SQL that is actually causing the issue
AND (DECODE (aia.doc_sequence_value,
NULL, DECODE(aia.voucher_num,
NULL, SUBSTR(aia.invoice_num, 1, 10),
aia.voucher_num) ,
aia.doc_sequence_value) ||'_' ||
aila.line_number ||'_' ||
aida.distribution_line_number ||'_' ||
DECODE (aca.doc_sequence_value,
NULL, DECODE(aca.check_voucher_num,
NULL, SUBSTR(aca.check_number, 1, 10),
aca.check_voucher_num) ,
aca.doc_sequence_value)) = " P_ID"
(P_ID - a value from the first cursor sql)
(Note that these are standard Oracle Applications(ERP) Invoice tables)
P_ID column is from the staging table that is derived the same way as above derivation and compared here again in the second SQL to get the latest data for that record. (Basically reprocessing the error records, the value of P_ID is something like "999703_1_1_9995248" )
Q1) Can I create a function based index on the whole left side derivation? If so what is the syntax.
Q2) Would it be okay or against the oracle standard rules, to create a function based index on standard Oracle tables? (Not creating directly on the table itself)
Q3) If NOT what is the best approach to solve this issue?
Briefly, no you can't place a function-based index on that expression, because the input values are derived from four different tables (or table aliases).
What you might look into is a materialised view, but that's a big and potentially difficult to solve a single query optimisation problem with.
You might investigate decomposing that string "999703_1_1_9995248" and applying the relevant parts to the separate expressions:
DECODE(aia.doc_sequence_value,
NULL,
DECODE(aia.voucher_num,
NULL, SUBSTR(aia.invoice_num, 1, 10),
aia.voucher_num) ,
aia.doc_sequence_value) = '999703' and
aila.line_number = '1' and
aida.distribution_line_number = '1' and
DECODE (aca.doc_sequence_value,
NULL,
DECODE(aca.check_voucher_num,
NULL, SUBSTR(aca.check_number, 1, 10),
aca.check_voucher_num) ,
aca.doc_sequence_value)) = '9995248'
Then you can use indexes on the expressions and columns.
You could separate the four components of the P_ID value using regular expressions, or a combination of InStr() and SubStr()
Ad 1) Based on the SQL you've posted, you cannot create function based index on that. The reason is that function based indexes must be:
Deterministic - i.e. the function used in index definition has to always return the same result for given input arguments, and
Can only use columns from the table the index is created for. In your case - based on aliases you're using - you have four tables (aia, aila, aida, aca).
Req #2 makes it impossible to build a functional index for that expression.

Oracle Spatial : Find the neighboring buildings

I'm creating an application which finds the neighboring buildings/ hydrants of a building on fire. I've created the tables:
CREATE TABLE building (
buildingno VARCHAR(40) PRIMARY KEY
, buildingname VARCHAR2(32),noofvertices INT
, shape MDSYS.SDO_GEOMETRY)
and
CREATE TABLE hydrant (hydrantno VARCHAR(40) PRIMARY KEY
, point MDSYS.SDO_GEOMETRY)
and
CREATE TABLE firebuilding(hydrantno VARCHAR(40) PRIMARY KEY)
I want to find the nearest neighbors of a particular building (both hydrants and buildings). Can I do this without creating a spatial index on the column name?
I am learning spatial querying and the dataset I'm working on is small (about 20 entries in each table and won't grow).
Do you have a good reason not to create a spatial index?
If you do, and if the number of shapes is small, you might get acceptable results and performance with a "brute force" approach that uses SDO_GEOM.SDO_DISTANCE to calculate the distance between the given point and each of the other points points and then picks the smallest distance. For example, if firebuilding identifies the given building, the following query identifies the closest building(s) using a tolerance of 1 metre (if the coordinates are geodetic) or 1 coordinate unit (if the coordinates are non-geodetic):
SELECT
B.*
FROM
(
SELECT
A.*,
DENSE_RANK () OVER (ORDER BY A.DISTANCE) AS RANKING
FROM
(
SELECT
OTHER_BUILDINGS.*,
SDO_GEOM.SDO_DISTANCE(BUILDING.SHAPE, OTHER_BUILDINGS.SHAPE, 1) DISTANCE
FROM
FIREBUILDING,
BUILDING,
BUILDING OTHER_BUILDINGS
WHERE
BUILDING.BUILDINGNO = FIREBUILDING.BUILDINGNO
AND
OTHER_BUILDINGS.BUILDINGNO <> BUILDING.BUILDINGNO
) A
) B
WHERE
B.RANKING = 1;

Oracle Spatial - select objects falling within area

this is probably simple to those who know (I hope!)
I have an Oracle spatial database with a geometry column containing a list of node points, in northing/easting format (if it's relevent!)
I need to select those objects which fall within a given radius of a given point.
Northings and Eastings are 1 meter apart which makes it a bit easier.
Ideally this should include objects which cross the area even if their node points fall outside it.
Is this an easy-ish query? Maybe using SDO_WITHIN_DISTANCE?
The table looks like this:
MyTable
ID NUMBER
NAME VARCHAR2(20)
DESC VARCHAR2(50)
GEOM SDO_GEOMETRY
Thanks for any help!
You can do this one of two ways. First, as you mentioned, SDO_WITHIN_DISTANCE is a valid approach.
select
*
from center_point a
inner join target_points b
on a.id = 1
and sdo_within_distance( b.shape, a.shape, 'distance = 10' ) = 'TRUE'
;
In this case, the distance is in linear units defined by a's spatial reference. Oracle treats the coordinates as Cartesian so you will need to make sure you have a linear coordinate system before using this operator (as opposed to angular lat/lon units). Since you are working with northings/eastings, I think you'll be okay as long as the points you are comparing against are in the same spatial reference.
This approach uses an inner-loop to solve the query so not very efficient if you have a lot of points to compare against. Also, Oracle Spatial is VERY picky about the order of operands in the SDO functions so you might need to play around with parameter order to find the sweetspot. If your query runs for a long period, try switching the first and second parameter of your sdo operator. You can also play with the order of the 'from' and 'inner join' tables using the /*+ ORDERED */ hind after SELECT.
Another approach is to buffer the geometry and compare against the buffer.
select
*
from center_point a
inner join target_points b
on a.id = 1
and sdo_relate( b.shape, sdo_buffer(a.shape, 0.05 ), 'mask=anyinteract' ) = 'TRUE'
;
Keep in mind that whatever is in the second parameter of the SDO_RELATE (called the window) will not have a spatial index if you transform it like we are here with the buffer.
If you plan on doing this with several points, it is recommended to build a table where all of the source points are buffered. Then create a spatial index against the buffered areas and compare that to your target points.
For example:
create table point_bufs unrecoverable as
select sdo_buffer (a.shape, b.diminfo, 1.35)
from centerpoint a, user_sdo_geom_metadata b
where table_name='CENTERPOINT'
and column_name='SHAPE';
select
a.gif,
b.gid
from target_points a,
point_bufs b
where sdo_relate(a.shape, b.shape, 'mask=anyinteract querytype=join') = 'TRUE'
;
NOTE: When intersecting points with polygons, you always want to polygon to be in the window position of the sdo_relate (which is the second parameter). This will ensure your spatial index is used correctly.
The proper way is to use SDO_WITHIN_DISTANCE, and that is the case irrespective of the coordinate systems used, i.e. whether they are projected or geodetic:
select b.*
from my_table a, my_table b
where a.id = 1
and sdo_within_distance( b.shape, a.shape, 'distance=10 unit=meter' ) = 'TRUE';
The order of the arguments to the spatial predicates is important: the first one is the points you are searching, the second is the "query window", i.e. the point you are searching for. Notice that you should always specify the unit of your distance - here 10 meters. If you don't then it will default to the unit of the coordinate system of the table you search. For geodetic data, that will always be meters. For projected data it will be the unit of your coordinate system - generally meters too, but not always. Explicitly specifying a unit lifts all ambiguities.
You could also use the buffer approach, but that makes no difference here, and is actually slower. It does not matter that the second argument to a spatial predicate is indexed or not: that index is not used. Only the index on the first argument is required and used.
To perform the operation on a collection of geometries - i.e. for a set of points, find the points within a set distance of each of them, then consider using the SDO_JOIN() function instead, like this to find all couple of points that are within 10 meters of each other:
SELECT a.id, b.id
FROM my_table a,
my_table b,
TABLE(SDO_JOIN(
'MY_TABLE', 'SHAPE',
'MY_TABLE', 'SHAPE',
'DISTANCE=10 UNIT=METER')
) j
WHERE j.rowid1 = a.rowid
AND j.rowid2 = a.rowid
AND a.rowid < a.rowid;

Resources