Could someone help me understand the ambiguity here in Postgres? - sql

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".

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).

PostgreSQL: How to pass and array to a function and using it in a query with the IN operator

I have a problem, I want to pass an array to a postgres function and use that array so returns values in a SELECT IN clause.
But It shows me this error:
An error occurred executing the SQL command :
SELECT
*
FROM
get_invtransferences_porders_fporders (300001300 , array [ 300093753 , 300094126 , 300093349 , 300093838 , 300094128 ] ...
ERROR: operator does not exist : integer = integer [ ]
Hint : No operator matches the name and type of arguments. You may need to add explicit type conversions .
Where : PL / pgSQL get_invtransferences_porders_fporders (numeric , integer []) function on line 8 FOR loop around rows of a SELECT
This is my function:
CREATE OR REPLACE FUNCTION public.get_invtransferences_porders_fporders(p_product_id numeric, P_INVTRANSFERENCES_IDS integer[])
RETURNS SETOF record
LANGUAGE plpgsql
AS
$body$
DECLARE
PORDER_PRODUCT RECORD;
COMPONENT RECORD;
COMPONENT2 RECORD;
COMPONENT3 RECORD;
BEGIN
FOR PORDER_PRODUCT IN (
SELECT
'porder' AS "operation_type"
,porders.id AS "porder_id"
,porders.user_id AS "porder_user_id"
,(SELECT name FROM users WHERE users.id = porders.user_id) AS "porder_user_name"
,porders.delivery_datetime AS "porder_delivery_datetime"
,porders_products.requested AS "product_requested"
,porders_products.produced AS "product_produced"
,products.code AS "product_code"
,products.NAME AS "product_name"
,(
SELECT products.name
FROM products
WHERE id = product_components.component_product_id
) AS "component_product_name"
,product_components.quantity AS "component_quantity"
,(
SELECT products.um_id
FROM products
WHERE id = product_components.component_product_id
) AS "component_um_id"
,(product_components.quantity / products.production_base) * porders_products.requested AS "total"
FROM porders
,porders_products
,products
,product_components
WHERE porders.id = porders_products.porder_id
AND porders_products.product_id = products.id
AND porders_products.product_id = product_components.product_id
AND porders.id IN (
SELECT rawm_audit_porders.porder_id
FROM rawm_audit_invtransferences
,rawm_audits
,rawm_audit_porders
WHERE rawm_audit_invtransferences.rawm_audits_id = rawm_audits.id
AND rawm_audit_porders.rawm_audits_id = rawm_audits.id
AND rawm_audit_invtransferences.invtransference_id IN
(
SELECT
invtransferences.id
FROM invtransferences
,invtransferences_products
,products
WHERE invtransferences.id = invtransferences_products.invtransference_id
AND products.id = invtransferences_products.product_id
AND invtransferences.id IN (P_INVTRANSFERENCES_IDS)
)
)
AND product_components.component_product_id = p_product_id
) LOOP
IF(PORDER_PRODUCT.porder_id IS NOT NULL)THEN
RETURN NEXT PORDER_PRODUCT;
END IF;
END LOOP;
RETURN;
END;
$body$
VOLATILE
COST 100
ROWS 1000
I think the error it here `invtransferences.id IN (P_INVTRANSFERENCES_IDS)
This is the select that calls the function:
SELECT
*
FROM
get_invtransferences_porders_fporders(300001300 , array[300093753, 300094126, 300093349, 300093838, 300094128] )
AS
(
"operation_type" varchar,
"porder_id" numeric,
"porder_user_id" numeric,
"porder_user_name" varchar,
"porder_delivery_datetime" date,
"product_requested" numeric,
"product_produced" numeric,
"product_code" varchar,
"product_name" varchar,
"component_product_name" varchar,
"component_quantity" numeric,
"component_um_id" varchar,
"total" numeric
)
ORDER BY
"porder_id";
EDIT: I remove the VARIADIC words that were in the function and in the select that calls the function
Can you hep me Please.
You don't need to declare your function as VARIADIC to pass array to it.
Try this
CREATE OR REPLACE FUNCTION xxx(
p_product_id integer,
P_INVTRANSFERENCES_IDS integer[])
RETURNS SETOF record
LANGUAGE sql
AS
$body$
select p_product_id = ANY(P_INVTRANSFERENCES_IDS)
$body$;
Note there is no VARIADIC before P_INVTRANSFERENCES_IDS.
You also need to use ANY instead of IN to check membership in array.
SqlFiddle

"Insert Into Select" writing to table but contains sub-query reading from same table

I am adding records into my table "SampleTestLimits" using an "Insert Into Select", but which also has a sub-query reading from the same table to perform a count for me.
I don't think the sub-query is seeing the earlier records added by my "Insert Into Select". It's the same for Oracle and SQL Server. The code for SQL Server is shown below (my sub-query begins with "SELECT COALESCE...").
I have another stored procedure which does work in a similar situation.
Would appreciate it if anybody could tell if what I'm doing is a no no.
ALTER PROCEDURE [dbo].[CreateSampleTestLimits]
#SampleCode as NVARCHAR(80),
#TestPosition as smallint,
#TestCode NVARCHAR(20),
#TestVersion smallint,
#EnterDate as integer,
#EnterTime as smallint,
#EnterUser as NVARCHAR(50)
AS
BEGIN
INSERT INTO SampleTestLimits
([AuditNumber]
,[LimitNumber]
,[ComponentRow]
,[ComponentColumn]
,[ComponentName]
,[TestPosition]
,[SampleCode]
,[AuditFlag]
,[LimitSource]
,[LimitType]
,[UpperLimitEntered]
,[UpperLimitValue]
,[LowerLimitEntered]
,[LowerLimitValue]
,[LimitTextColour]
,[LimitPattern]
,[LimitForeColour]
,[LimitBackColour]
,[CreatedDate]
,[CreatedTime]
,[CreatedUser]
,[LimitText]
,[FilterName]
,[deleted]
,IsRuleBased)
SELECT 1 --starting auditnumber
,(SELECT COALESCE(MAX(LimitNumber), 0) + 1 AS NextLimitNumber FROM SampleTestLimits WHERE SampleCode=#SampleCode AND TestPosition=#TestPosition AND ComponentRow=1 AND ComponentColumn=1 AND AuditFlag=0) -- TFS bug# 3952: Calculate next limit number.
,ComponentRow
,ComponentColumn
,(select ComponentName from TestComponents TC where TC.TestCode=#TestCode and TC.ComponentColumn=TestLimits.ComponentColumn and TC.ComponentRow = TestLimits.ComponentRow and TC.AuditNumber=TestLimits.AuditNumber)
,#TestPosition
,#SampleCode
,0 --auditflag
,1 --limitsource = test
,[LimitType]
,[UpperLimitEntered]
,[UpperLimitValue]
,[LowerLimitEntered]
,[LowerLimitValue]
,[LimitTextColour]
,[LimitPattern]
,[LimitForeColour]
,[LimitBackColour]
,#EnterDate
,#EnterTime
,#EnterUser
,[LimitText]
,[FilterName]
,0 --deleted
,0 --rule based
FROM TestLimits join Tests on Tests.TestCode=TestLimits.TestCode and Tests.AuditNumber= TestLimits.AuditNumber WHERE Tests.TestCode=#TestCode and Tests.auditnumber=#TestVersion and ([TestLimits].FilterString is null or DATALENGTH([TestLimits].FilterString)=0)
END
Assuming that I understand your logic correctly (ie. that you want the nextlimitnumber to increase by 1 for each row being added), in Oracle, I'd do it by using the analytic function row_number() to work out what number to add to the previous max value, something like:
INSERT INTO sampletestlimits (auditnumber,
limitnumber,
componentrow,
componentcolumn,
componentname,
testposition,
samplecode,
auditflag,
limitsource,
limittype,
upperlimitentered,
upperlimitvalue,
lowerlimitentered,
lowerlimitvalue,
limittextcolour,
limitpattern,
limitforecolour,
limitbackcolour,
createddate,
createdtime,
createduser,
limittext,
filtername,
deleted,
isrulebased)
SELECT 1, --starting auditnumber
(SELECT COALESCE (MAX (limitnumber), 0) + 1 AS nextlimitnumber
FROM sampletestlimits
WHERE samplecode = p_samplecode
AND testposition = p_testposition
AND componentrow = 1
AND componentcolumn = 1
AND auditflag = 0)
+ row_number() over (partition by testposition, componentrow, componentcolumn, auditflag) as nextlimitnumber, -- TFS bug# 3952: Calculate next limit number.
componentrow,
componentcolumn,
(SELECT componentname
FROM testcomponents tc
WHERE tc.testcode = p_testcode
AND tc.componentcolumn = testlimits.componentcolumn
AND tc.componentrow = testlimits.componentrow
AND tc.auditnumber = testlimits.auditnumber),
p_testposition,
p_samplecode,
0, --auditflag
1, --limitsource = test
limittype,
upperlimitentered,
upperlimitvalue,
lowerlimitentered,
lowerlimitvalue,
limittextcolour,
limitpattern,
limitforecolour,
limitbackcolour,
p_enterdate,
p_entertime,
p_enteruser,
limittext,
filtername,
0, --deleted
0 --rule based
FROM testlimits
JOIN tests
ON tests.testcode = testlimits.testcode
AND tests.auditnumber = testlimits.auditnumber
WHERE tests.testcode = p_testcode
AND tests.auditnumber = p_testversion
AND ( testlimits.filterstring IS NULL
OR datalength (testlimits.filterstring) = 0);
I had to guess at what the partition by clause would need to contain - adjust that as necessary for your requirements.

Why does this function always return 0

I don't know why this function always returns 0
CREATE OR REPLACE FUNCTION QTYDEPOT(
p_org_id IN NUMBER,
p_product_id IN NUMBER,
p_datefrom IN DATE,
p_dateto IN DATE)
RETURN NUMBER
AS
qty NUMBER;
BEGIN
SELECT COALESCE(SUM(C_InvoiceLine.qtyinvoiced), 0)
INTO qty
FROM C_InvoiceLine
INNER JOIN C_invoice
ON (c_invoiceline.C_INVOICE_ID = c_invoice.C_INVOICE_ID)
INNER JOIN C_BPartner
ON (c_invoice.C_BPARTNER_ID = c_bpartner.C_BPARTNER_ID)
WHERE C_BPartner.ISSALESREP = 'N'
AND C_BPartner.ISEMPLOYEE = 'N'
AND c_bpartner.ISCUSTOMER = 'Y'
AND c_invoiceline.AD_org_id = p_org_id
AND c_invoiceline.m_product_id= p_product_id
AND c_invoice.DateInvoiced BETWEEN p_datefrom AND p_dateto;
RETURN qty ;
END;
P.S : if I remove the date part of the close where
c_invoice.DateInvoiced BETWEEN p_datefrom AND p_dateto;
The function returns the real values.
I call it like this
SELECT
..
QTYDEPOT( 1000000, p.m_product_id,'7/7/2014','24/7/2014') as qtyDepot
try this:
SELECT
..
QTYDEPOT( 1000000, p.m_product_id,to_date('7/7/2014','dd/mm/yyyy'),to_date('24/7/2014','dd/mm/yyyy')) as qtyDepot
you have to specify the date format you are passing to the function,
hope this helps!
You have problems with DATE manipulation. I would suggest you to try calling your function like this :
SELECT
..
QTYDEPOT( 1000000, p.m_product_id,DATE('2014-07-07'),DATE('2014-07-24')) as qtyDepot
AS you can see, Oracle standard format is 'yyyy-mm-dd' I don't know if DATE(...) is needed, but I use to manipulate DATE like this: code is clearer.

How to use SDO_GEOM.SDO_CLOSEST_POINTS as Query with Oracle Spatial

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