How to use SDO_GEOM.SDO_CLOSEST_POINTS as Query with Oracle Spatial - sql

I am trying to use SDO_GEOM.SDO_CLOSEST_POINTS purely as a sql query. All the examples use it in conjunction with pl/sql. Is it possible to use it in a query without pl/sql and could anyone provide an example of the syntax for how to do that?
My specific task is trying to return the vertex on a line that is closest to a point.
http://docs.oracle.com/cd/B28359_01/appdev.111/b28400/sdo_objgeom.htm#SPATL1113
Thank you.

It's not possible to call a PL/SQL procedure from an SQL query.
I would suggest that you create an Oracle object type that wraps the OUT parameters from SDO_GEOM.SDO_CLOSEST_POINTS and then define your own PL/SQL function that calls the procedure that returns an instance of your object type.
Something like this:
CREATE TYPE closest_points_type AS OBJECT (
dist NUMBER
, geoma mdsys.sdo_geometry
, geomb mdsys.sdo_geometry
)
/
CREATE FUNCTION sdo_closest_points_sql (
p_geom1 IN sdo_geometry
, p_geom2 IN sdo_geometry
, p_tolerance IN NUMBER
, p_unit IN VARCHAR2
)
RETURN closest_points_type
IS
l_dist NUMBER;
l_geoma mdsys.sdo_geometry;
l_geomb mdsys.sdo_geometry;
BEGIN
sdo_geom.sdo_closest_points(
geom1 => p_geom1
, geom2 => p_geom2
, tolerance => p_tolerance
, unit => p_unit
, dist => l_dist
, geoma => l_geoma
, geomb => l_geomb
);
RETURN closest_points_type(l_dist, l_geoma, l_geomb);
END sdo_closest_points_sql;
/
You should then be able to call this function from a SELECT statement, and interrogate the resulting object like so:
WITH q1 AS (
SELECT
sdo_closest_points_sql(
mdsys.sdo_geometry(2002, NULL, NULL, sdo_elem_info_array(1,2,1), sdo_ordinate_array(1,1, 1,10, 1,20))
, mdsys.sdo_geometry(2002, NULL, NULL, sdo_elem_info_array(1,2,1), sdo_ordinate_array(2,5, 2,15, 2,25))
, 0.05
, NULL
) result
FROM dual
)
SELECT
(q1.result).dist dist
, (q1.result).geoma geoma
, (q1.result).geomb geomb
FROM q1

Related

HANA database - Injection of a Table User Defined Function into a lateral join

In short
On an HANA database, I have set a Table User Defined Function which returns a 1-row table with 3 columns ;
I would like to use it inside a lateral join but so far my attempts have been to no avail.
The problem
Let's say we have the following dummy Table User Defined Function :
CREATE OR REPLACE FUNCTION PBANALYST. F__ITEM_MBEW(
IN
p_str_MATNR NVARCHAR(18)
, p_str_BWKEY NVARCHAR(02)
, p_str_VALDATE NVARCHAR(08)
)
RETURNS
TABLE(
VALDATE NVARCHAR(08)
, LBKUM INTEGER
, VERPR DECIMAL
, STPRS DECIMAL
)
LANGUAGE SQLSCRIPT
SQL SECURITY INVOKER
AS
BEGIN
RETURN
SELECT
'20220928' AS VALDATE
, 10 AS LBKUM
, 5.3 AS VERPR
, 10.5 AS STPRS
FROM DUMMY
;
END;
It works fine on its own.
But when I try to inject it inside a lateral join, I get an error :
DO
BEGIN
tbl_MATNR_LIST =
SELECT '000000000000824151' AS MATNR , '92' AS div , '20220715' AS VALDATE FROM dummy
;
SELECT
tbl_MATNR_LIST. *
FROM :tbl_MATNR_LIST tbl_MATNR_LIST ,
LATERAL(
SELECT *
FROM F__ITEM_MBEW(
'000000000000824151'
, '92'
, '20220715'
)
) MBEW
;
END;
DataSource.Error : ODBC: ERROR [S1000] [SAP AG][LIBODBCHDB DLL][HDBODBC] General error;318 decimal precision specifier is out of range: -1: (1 to 38)
How can I fix it?
Thank you for your help.
The error message
DataSource.Error : ODBC: ERROR [S1000] [SAP AG][LIBODBCHDB DLL][HDBODBC] General error;318 decimal precision specifier is out of range: -1: (1 to 38)
can easily be fixed by specifying the decimal data type lengths in the user defined function:
TABLE(
VALDATE NVARCHAR(08)
, LBKUM INTEGER
, VERPR DECIMAL (10, 2)
, STPRS DECIMAL (10, 2)
)
Unfortunately, that does not help with the overall goal: to use the UDF in the LATERAL join in a meaningful way.
What does work is something like this:
DO
BEGIN
tbl_MATNR_LIST =
SELECT '000000000000824151' AS MATNR
, '92' AS div
, '20220715' AS VALDATE
FROM dummy;
SELECT
tml. *
FROM :tbl_MATNR_LIST tml
CROSS JOIN LATERAL(
F__ITEM_MBEW( '000000000000824151'
, '92'
, '20220715')
) MBEW;
END;
This does return the result, but obviously does not use any "lateral" references.
If one tries to make "grab stuff side-ways" this is the result:
[...]
CROSS JOIN LATERAL(
F__ITEM_MBEW( '000000000000824151'
, tml.div
, '20220715')
) MBEW;
[...]
SQL Error [7] [HY000]: SAP DBTech JDBC: [7] (at 292): feature not supported: non-field expression with LATERAL: line 13 col 29 (at pos 292)
At this point, I would guess that this way of using the LATERAL JOIN is just not supported (on version 2.00.057).

Could someone help me understand the ambiguity here in Postgres?

So I've been trying out PG for a few days, specifically through NpgSQL in dotnet core, but I don't believe that is relevant to my question. I've been writing a couple of update functions. The first one was easy:
CREATE OR REPLACE FUNCTION "Api"."UpdateExpenseReceipt" ( "vReceiptID" UUID , "vTotal" DOUBLE PRECISION , "vTaxPercent" DOUBLE PRECISION , "vShippingCost" DOUBLE PRECISION , "vReceiptDate" TIMESTAMP , "vReference" VARCHAR , "vCurrentToken" UUID )
RETURNS TABLE ( ReceiptID UUID , Total DOUBLE PRECISION , TaxPercent DOUBLE PRECISION , ShippingCost DOUBLE PRECISION , Reference VARCHAR(96) , ReceiptDate TIMESTAMP )
LANGUAGE PLPGSQL
AS $$
DECLARE "iValidReceipt" INTEGER;
DECLARE "iValidUser" INTEGER;
BEGIN
"iValidReceipt" := ( SELECT COUNT("ReceiptID") FROM "Users"."ExpenseReceipt" WHERE "ReceiptID" = "vReceiptID" );
"iValidUser" := ( SELECT COUNT("AccountID") FROM "Users"."Account" WHERE "CurrentToken" = "vCurrentToken" LIMIT 1 );
IF "iValidUser" = 0 THEN
RAISE 'Error' USING ERRCODE = '10001';
END IF;
IF "iValidReceipt" > 0 THEN
UPDATE "Users"."ExpenseReceipt" SET
"Total" = COALESCE( "vTotal" , "Total" )
, "TaxPercent" = COALESCE( "vTaxPercent" , "TaxPercent" )
, "ShippingCost" = COALESCE( "vShippingCost" , "ShippingCost" )
, "Reference" = COALESCE( CAST( "vReference" AS VARCHAR ) , "Reference" )
, "ReceiptDate" = COALESCE( "vReceiptDate" , "ReceiptDate" )
, "EditDate" = current_timestamp at time zone 'utc'
WHERE "ReceiptID" = "vReceiptID";
RETURN QUERY
SELECT
"ReceiptID"
, "Total"
, "TaxPercent"
, "ShippingCost"
, "Reference"
, "ReceiptDate"
FROM "Users"."ExpenseReceipt"
WHERE "ReceiptID" = "vReceiptID";
ELSE
RAISE 'Error' USING ERRCODE = '10101';
END IF;
END; $$
--I'll include the table itself in case its relevant
CREATE TABLE "Users"."ExpenseReceipt"
(
"ReceiptID" UUID NOT NULL DEFAULT uuid_generate_v4(),
"AccountID" UUID NOT NULL ,
"Total" DOUBLE PRECISION NOT NULL ,
"TaxPercent" DOUBLE PRECISION DEFAULT 0.0 ,
"ShippingCost" DOUBLE PRECISION DEFAULT 0.0 ,
"Reference" VARCHAR(96) ,
"ReceiptDate" TIMESTAMP ,
"EditDate" TIMESTAMP DEFAULT ( current_timestamp at time zone 'utc' )
);
Easy update function, uses coalesce to not update the value if the API call doesn't set them. Everything works fine (after dealing through the numerous naming issues I've run into with postgres, and there I think NpgSQL is relevant). I know how to get it right. I made a second one, basically exactly the same:
CREATE OR REPLACE FUNCTION "Api"."UpdateSupplyItem" ( "vItemID" UUID , "vDescription" VARCHAR , "vSize" VARCHAR , "vNetCost" DOUBLE PRECISION , "vPackageQuantity" DOUBLE PRECISION , "vNetWeight" DOUBLE PRECISION , "vCurrentToken" UUID )
RETURNS TABLE ( "ItemID" UUID , "Description" VARCHAR , "Size" VARCHAR , "NetCost" DOUBLE PRECISION , "PackageQuantity" DOUBLE PRECISION , "NetWeight" DOUBLE PRECISION )
LANGUAGE PLPGSQL
AS $$
DECLARE "iValidItem" INTEGER;
DECLARE "iValidUser" INTEGER;
BEGIN
"iValidItem" := ( SELECT COUNT(usi."ItemID") FROM "Users"."SupplyItem" usi WHERE usi."ItemID" = "vItemID" );
"iValidUser" := ( SELECT COUNT("AccountID") FROM "Users"."Account" WHERE "CurrentToken" = "vCurrentToken" LIMIT 1 );
IF "iValidUser" = 0 THEN
RAISE 'Error' USING ERRCODE = '10001';
END IF;
IF "iValidItem" > 0 THEN
UPDATE "Users"."SupplyItem"
SET
"Description" = COALESCE( "vDescription" , "Users"."SupplyItem"."Description" )
, "Size" = COALESCE( "vSize" , "Users"."SupplyItem"."Size" )
, "NetCost" = COALESCE( "vNetCost" , "Users"."SupplyItem"."NetCost" )
, "PackageQuantity" = COALESCE( "vPackageQuantity" , "Users"."SupplyItem"."PackageQuantity")
, "NetWeight" = COALESCE( "vNetWeight" , "Users"."SupplyItem"."NetWeight" )
WHERE "Users"."SupplyItem"."ItemID" = "vItemID";
RETURN QUERY SELECT usi."ItemID" , usi."Description" , usi."Size" , usi."NetCost" , usi."PackageQuantity" , usi."NetWeight" FROM "Users"."SupplyItem" usi WHERE usi."ItemID" = "vItemID" LIMIT 1;
ELSE
RAISE 'Error' USING ERRCODE = '10401';
END IF;
END; $$
--Again, the table in case it helps
CREATE TABLE "Users"."SupplyItem"
(
"ItemID" UUID NOT NULL DEFAULT uuid_generate_v4() ,
"AccountID" UUID NOT NULL ,
"Description" VARCHAR ,
"Size" VARCHAR ,
"NetCost" DOUBLE PRECISION ,
"PackageQuantity" DOUBLE PRECISION ,
"NetWeight" DOUBLE PRECISION
);
but it's quite different. You can see clearly that I've had to fully qualify the right hand side of every equals (where the earlier function had no need). I get ambiguity all the way down the update statement. It starts at ItemID, then Description, then Size... every attribute I think. First thing I did was alias the table
UPDATE usi [...] FROM "Users"."SupplyItem" usi
but that failed because you can't do short aliasing in an UPDATE in PG (relation usi."[...]" does not exist") which actually kind of sucks. I only figured out that it needed to be fully qualified when someone asked a similar question and the answer was "It must be a quirk of RETURNS TABLE."
So why is my second update "a quirk" but my first update works perfectly? I've had a tough time with PG (and I'm not a slouch), but having two functions that seem identical having entirely different results (at runtime no less) makes me uncomfortable. I'm posting here because I know the two functions must be markedly different; 99.9% of the time, there is no such thing as a "quirk." There is something I need to understand to work around to avoid in the future. What is the "gotcha" that I've missed in the second UPDATE function?
The problem is that you have a function variable "Size" (an OUT parameter defined in the RETURNS TABLE clause) and a column "Size" in "Users"."SupplyItem". So you have to qualify the reference to indicate what you mean.
I recommend using an alias for simplicity:
UPDATE "Users"."SupplyItem" AS si
SET "Size" = COALESCE("vSize" , si."Size")
...
There is no such ambiguity in your first example, because you didn't double quote the parameter TaxPercent, so it gets case folded to taxpercent and is different from the column "TaxPercent".

Oracle named parameters in regular functions

Can you use named parameters in regular oracle functions like for instance REGEXP_REPLACE ? I like the named parameters notation and always use it to call self defined pieces of PL/SQL code, but the following doesn't seem to work :
select regexp_replace( string => 'TechOnTheNet'
, pattern => 'a|e|i|o|u'
, replacement_string => 'Z'
, start_position => 1
, nth_appearance => 2
, match_parameter => 'i') from dual;
Not for built in functions called from SQL; but you can from PL/SQL with assignment (and the right formal parameter names):
declare
x varchar2(30);
begin
x := regexp_replace( srcstr => 'TechOnTheNet'
, pattern => 'a|e|i|o|u'
, replacestr => 'Z'
, position => 1
, occurrence => 2
, modifier => 'i');
end;
/
TechZnTheNet
PL/SQL procedure successfully completed.
Even there you can't select the function result directly.
There is a workaround but it's a bit messy. The PL/SQL assignment is using the STANDARD package version of the function, so if you're using a function that is available from PL/SQL you could call that instead:
select sys.standard.regexp_replace( srcstr => 'TechOnTheNet'
, pattern => 'a|e|i|o|u'
, replacestr => 'Z'
, position => 1
, occurrence => 2
, modifier => 'i') from dual;
SYS.STANDARD.REGEXP_REPLACE(SRCSTR=>'TECHONTHENET',PATTERN=>'A|E|I|O|U',REPLACES
--------------------------------------------------------------------------------
TechZnTheNet
As well as being longer, it's possible you might see different performance between the two versions - I don't know if it's safe to assume they're ultimately implemented the same.
You can see the available functions with a simple query like:
select object_name, position, argument_name, in_out, data_type
from all_arguments
where owner = 'SYS' and package_name = 'STANDARD'
order by object_name, overload, position;
For regexp_replace that shows the three versions of the function that are available for different argument data types. (An unnamed argument in position 0 is the function return type).

Error creating function in DB2 with params

I have a problem with a function in db2
The function finds a record, and returns a number according to whether the first and second recorded by a user
The query within the function is this
SELECT
CASE
WHEN NUM IN (1,2) THEN 5
ELSE 2.58
END AS VAL
FROM (
select ROW_NUMBER() OVER() AS NUM ,s.POLLIFE
from LQD943DTA.CAQRTRML8 c
INNER JOIN LSMODXTA.SCSRET s ON c.MCCNTR = s.POLLIFE
WHERE s.NOEMP = ( SELECT NOEMP FROM LSMODDTA.LOLLM04 WHERE POLLIFE = '0010111003')
) AS T WHERE POLLIFE = '0010111003'
And works perfect
I create the function with this code
CREATE FUNCTION LIBWEB.BNOWPAPOL(POL CHAR)
RETURNS DECIMAL(7,2)
LANGUAGE SQL
NOT DETERMINISTIC
READS SQL DATA
RETURN (
SELECT
CASE
WHEN NUM IN (1,2) THEN 5
ELSE 2.58
END AS VAL
FROM (
select ROW_NUMBER() OVER() AS NUM ,s.POLLIFE
from LQD943DTA.CAQRTRML8 c
INNER JOIN LSMODXTA.SCSRET s ON c.MCCNTR = s.POLLIFE
WHERE s.NOEMP = ( SELECT NOEMP FROM LSMODDTA.LOLLM04 WHERE POLLIFE = POL)
) AS T WHERE POLLIFE = POL
)
The command runs executed properly
WARNING: 17:55:40 [CREATE - 0 row(s), 0.439 secs] Command processed.
No rows were affected
When I want execute the query a get a error
SELECT LIBWEB.BNOWPAPOL('0010111003') FROM DATAS.DUMMY -- dummy has only one row
I get
[Error Code: -204, SQL State: 42704] [SQL0204] BNOWPAPOL in LIBWEB
type *N not found.
I detect, when I remove the parameter the function works fine!
With this code
CREATE FUNCTION LIBWEB.BNOWPAPOL()
RETURNS DECIMAL(7,2)
LANGUAGE SQL
NOT DETERMINISTIC
READS SQL DATA
RETURN (
SELECT
CASE
WHEN NUM IN (1,2) THEN 5
ELSE 2.58
END AS VAL
FROM (
select ROW_NUMBER() OVER() AS NUM ,s.POLLIFE
from LQD943DTA.CAQRTRML8 c
INNER JOIN LSMODXTA.SCSRET s ON c.MCCNTR = s.POLLIFE
WHERE s.NOEMP = ( SELECT NOEMP FROM LSMODDTA.LOLLM04 WHERE POLLIFE = '0010111003')
) AS T WHERE POLLIFE = '0010111003'
)
Why??
This statement:
SELECT LIBWEB.BNOWPAPOL('0010111003') FROM DATAS.DUMMY
causes this error:
[Error Code: -204, SQL State: 42704] [SQL0204] BNOWPAPOL in LIBWEB
type *N not found.
The parm value passed into the BNOWPAPOL() function is supplied as a quoted string with no definition (no CAST). The SELECT statement assumes that it's a VARCHAR value since different length strings might be given at any time and passes it to the server as a VARCHAR.
The original function definition says:
CREATE FUNCTION LIBWEB.BNOWPAPOL(POL CHAR)
The function signature is generated for a single-byte CHAR. (Function definitions can be overloaded to handle different inputs, and signatures are used to differentiate between function versions.)
Since a VARCHAR was passed from the client and only a CHAR function version was found by the server, the returned error fits. Changing the function definition or CASTing to a matching type can solve this kind of problem. (Note that a CHAR(1) parm could only correctly handle a single-character input if a value is CAST.)

Why am I getting PLS - 00382?

Here is my object def:
CREATE OR REPLACE TYPE FALCON.contacts AS OBJECT (phone VARCHAR2(50)
,phoneusage VARCHAR2(25)
,phonetype VARCHAR2(25)
,email VARCHAR2(150)
,phoneext VARCHAR2(25)
,anytext VARCHAR2(250))
Here is the table def:
CREATE OR REPLACE TYPE FALCON.contacttbl AS TABLE OF contacts
Here is my pipelined function
FUNCTION get_pcontacts(p_conttbl IN xmltypedefs_spec.conttbl)
RETURN falcon.contacttbl
PIPELINED
IS
l_contact falcon.contacts;
BEGIN
FOR n IN 1 .. p_conttbl.count
LOOP
PIPE ROW(**falcon.contacts**(p_conttbl(n).phone, p_conttbl(n).phoneusage, p_conttbl(n).phonetype, p_conttbl(n).email, p_conttbl(n).phoneext, p_conttbl(n).anytext));
END LOOP;
RETURN;
END get_pcontacts;
I am getting the error when I call the table function here:
FUNCTION get_pidxml(p_pidrec xmltypedefs_spec.pidtyp)
RETURN CLOB
IS
l_tmprec CLOB;
l_pxml xmltype;
l_bxml xmltype;
l_pcontacts xmltypedefs_spec.conttbl := p_pidrec.personalcont;
l_bcontacts xmltypedefs_spec.conttbl := p_pidrec.businesscont;
BEGIN
-- l_pxml := get_contacts(p_pidrec, 'p');
-- l_bxml := get_contacts(p_pidrec, 'b');
SELECT xmlelement("pid"
,xmlforest(p_pidrec.setid AS "setID"
,p_pidrec.patidexternal AS "patientIDExternal"
,p_pidrec.patientid AS "patientID"
,p_pidrec.patintasgnauth AS "patientIDInterAssignAuthority"
,p_pidrec.patinttypecd AS "patientIDInternalIDTypeCode"
,p_pidrec.patidalternate1 AS "patientIDAlernate1"
,p_pidrec.patlastname AS "patientLastName"
,p_pidrec.patfirstname AS "patientFirstName"
,p_pidrec.patmiddleinit AS "patientMiddleInitial"
,p_pidrec.patsuffix AS "patientSuffix"
,p_pidrec.patprefix AS "patientPrefix"
,p_pidrec.degree AS "degree"
,p_pidrec.familyname AS "familyName"
,p_pidrec.givenname AS "givenName"
,p_pidrec.mothermaidname AS "mothersMaidenName"
,p_pidrec.dob AS "dateOfBirth"
,p_pidrec.adminsex AS "administrativeSex"
,p_pidrec.patientalias AS "patientAlias"
,p_pidrec.race AS "race"
,p_pidrec.racetext AS "raceText"
,p_pidrec.pataddr1 AS "patientAddress1"
,p_pidrec.pataddr2 AS "patientAddress2"
,p_pidrec.patcity AS "patientCity"
,p_pidrec.patstate AS "patientState"
,p_pidrec.patzip AS "patientZip"
,p_pidrec.countrycode AS "countryCode"
,p_pidrec.addresstype AS "addressType"
,p_pidrec.othgeodesig AS "otherGeographicDesignation"
,p_pidrec.county AS "county"
,(SELECT xmlagg(xmlelement("contactInfo",
xmlforest(phone AS "phoneNumber",
phoneusage AS "telecomUseCode",
phonetype AS "telecomequiptype",
email AS "email",
phoneext AS "phonenumberextension",
anytext AS "anytext")))
FROM TABLE(**get_pcontacts(l_pcontacts**))) AS "personalContact"
http://pls-00382.ora-code.com/
PLS-00382: expression is of wrong type
Since I don't know how xmltypedefs_spec.conttbl is defined, I removed the input parameter from the pipelined function and just had it generate fake data on the fly:
CREATE OR REPLACE FUNCTION get_contacts
RETURN contacttbl PIPELINED
IS
-- converts some structure to pipe of contacts
BEGIN
FOR n IN 1 .. 5 LOOP
PIPE ROW(
contact(
'877-867-5309',
'Work',
'Cell',
'jenny#gmail.com',
n,
'WTF?'
)
);
END LOOP;
RETURN;
END get_contacts;
The subquery now executes without error:
SELECT
xmlagg(
xmlelement("contactInfo",
xmlforest(
phone AS "phoneNumber",
phoneusage AS "telecomUseCode",
phonetype AS "telecomequiptype",
email AS "email",
phoneext AS "phonenumberextension",
anytext AS "anytext"
)
)
)
FROM
TABLE( get_contacts( ) )
This tells me there is probably something wrong with xmltypedefs_spec.conttbl, perhaps in using a collection type within an SQL statement? Not sure. What if you changed xmltypedefs_spec.pidtyp to use the falcon.contacttbl instead of xmltypedefs_spec.conttbl. Seems like you've got one package type and one object type that are doing the same thing?
xmltypedefs_spec defines record types that correspond to XML elements. These record types are used to shred and build XML. Originally, the XML did not use repeating elements, but now must. I am attempting to take a table of xmltypedefs_spec.pidtyp and use the pipelined function to return 'rows' of data from an associative table. It is in this fashion that I want to send rows of array records to build xml.