Oracle - object order - how does it work? - sql

Oracle doc says as follows: "As with map methods, an order method, if one is defined, is called automatically whenever two objects of that type need to be compared."
So I assume it will sort when using 'order by' clausule.
Lets say, I have following object:
create or replace type height as object
(
val number,
constructor function height return self as result,
constructor function height (val number) return self as result,
order member function compare (other height) RETURN INTEGER
);
and its body implementation:
create or replace type body height is
constructor function height return self as result is
begin
self.val := null;
end;
constructor function height (val number) return self as result is
begin
self.val := val;
return;
end;
order member function compare (other height) return integer is
begin
if self.val > self.val then return 1;
elsif self.val < self.val then return -1;
else return 0;
end if;
end;
end;
Then my table looks like this:
CREATE TABLE people (
name varchar2(50),
height height
);
And some records ...
insert into people values ('Foo', height(150));
insert into people values ('Bar', height(130));
insert into people valueS ('Baz', height(180));
Then I want to sort it:
select p.height.val from people p order by height ASC;
select p.height.val from people p order by height DESC;
But result is the same for 2 queries, no matter if order is asc or desc.

I experimented with your code and found a solution.
The solution that works looks like this
I have added a second compare function with two parameters
create or replace type height as object
(
val number,
constructor function height return self as result,
constructor function height (val number) return self as result,
order member function compare2 (other height) return integer,
member function compare (o1 height,o2 height) return integer
);
/
create or replace type body height is
constructor function height return self as result is
begin
self.val := null;
end;
constructor function height (val number) return self as result is
begin
self.val := val;
return;
end;
order member function compare2 (other height) return integer is
begin
RETURN compare(SELF,other);
end;
member function compare (o1 height,o2 height) return integer is
begin
if o1.val > o2.val then return 1;
elsif o1.val < o2.val then return -1;
else return 0;
end if;
end;
end;
/
select p.height.val from people p order by height ASC;
| HEIGHT.VAL |
| ---------: |
| 130 |
| 150 |
| 180 |
select p.height.val from people p order by height DESC;
| HEIGHT.VAL |
| ---------: |
| 180 |
| 150 |
| 130 |
db<>fiddle here

In turned out to be a simple mistake...
Implementation of compare function compares self with self instead with other.

Related

Boolean return value of some function inside SELECT

I am working on a package in PL/SQL.
This is my spec:
TYPE outrec IS RECORD(
tw_m_id NUMBER,
tw_m_dealer_id NUMBER,
tw_number NUMBER,
check_uid NUMBER);
TYPE outrecset IS TABLE OF outrec;
FUNCTION report
(
p_watermark IN NUMBER,
p_param IN NUMBER,
p_index IN NUMBER
) RETURN outrecset
PIPELINED;
This is my body:
FUNCTION func
(
p_watermark => p_watermark,
p_param => p_param,
p_index => p_index
)
RETURN outrecset
PIPELINED IS
temp outrec;
BEGIN
before_report(p_watermark => p_watermark,
p_param => p_param,
p_index => p_index);
FOR c_rec IN (SELECT tw_m_id,
tw_m_dealer_id,
tw_number,
package_name.somefunction(tw_number) AS check_uid
FROM table1
JOIN table2 rk ON id1 = rk.id2
WHERE 1 = 1
AND id1 = rk.id2
AND id1 = p_param)
LOOP
temp.tw_m_tw_rechnungskopf_id := c_rec.tw_m_tw_rechnungskopf_id;
temp.tw_m_haendler_id_rechnung := c_rec.tw_m_haendler_id_rechnung;
temp.check_uid := c_rec.check_uid;
PIPE ROW(temp);
END LOOP;
END;
I am trying to get value from package_name.somefunction(tw_number) AS check_uid. The problem is that somefunction returns BOOLEAN value.
When I set check_uid to BOOLEAN I get Error: PLS-00382: expression is of the wrong type because of course SQL doesn't support BOOLEAN. I tried :
CASE
WHEN package_name.somefunction(tw_number) THEN true
else false
END as check_uid
inside SELECT then I get Error: PL/SQL: ORA-00920: invalid relational operator.
Can someone tell me how to do this PL/SQL is not my strongest side :(
EDIT: I can't change somefunction to return for an example varchar2 it needs to stay the way it is
Write a simple wrapper function to convert the PL/SQL BOOLEAN to a NUMBER data type that is valid in SQL and expected by your record type and you can move the function call outside the SQL.
CREATE FUNCTION MAP_BOOLEAN( truthy IN BOOLEAN ) RETURN NUMBER DETERMINISTIC
IS
BEGIN
RETURN CASE truthy
WHEN TRUE THEN 1
WHEN FALSE THEN 0
ELSE NULL
END;
END map_boolean;
/
So your specification would be:
CREATE PACKAGE package_name IS
TYPE outrec IS RECORD(
tw_m_id NUMBER,
tw_m_dealer_id NUMBER,
tw_number NUMBER,
check_uid NUMBER
);
TYPE outrecset IS TABLE OF outrec;
-- Note: This may be in another package but is here for convenience.
FUNCTION somefunction(value IN NUMBER) RETURN BOOLEAN;
FUNCTION report
(
p_watermark IN NUMBER,
p_param IN NUMBER,
p_index IN NUMBER
) RETURN outrecset PIPELINED;
END;
/
and the corresponding body would be:
CREATE PACKAGE BODY package_name IS
FUNCTION somefunction(value IN NUMBER) RETURN BOOLEAN
IS
BEGIN
RETURN TRUE;
END;
PROCEDURE before_report(
p_watermark IN NUMBER,
p_param IN NUMBER,
p_index IN NUMBER
)
IS
BEGIN
NULL;
END;
FUNCTION report(
p_watermark IN NUMBER,
p_param IN NUMBER,
p_index IN NUMBER
) RETURN outrecset PIPELINED
IS
temp outrec;
BEGIN
before_report(
p_watermark => p_watermark,
p_param => p_param,
p_index => p_index
);
FOR c_rec IN (
SELECT tw_m_id,
tw_m_dealer_id,
tw_number
FROM table1
JOIN table2 rk ON id1 = rk.id2
WHERE id1 = p_param
)
LOOP
temp.tw_m_id := c_rec.tw_m_id;
temp.tw_m_dealer_id := c_rec.tw_m_dealer_id;
temp.check_uid := MAP_BOOLEAN(
PACKAGE_NAME.SOMEFUNCTION( c_rec.tw_number )
);
PIPE ROW(temp);
END LOOP;
END;
END;
/
(Note: you also need to update the cursor loop as the values you were selecting did not match the fields of the record.)
db<>fiddle here
Typically you would create an overloaded function in your package that returns 1/0 or Y/N. But since you have no access to the package you can use an inline function in your sql query to do this for you.
create or replace function func (parameter_i VARCHAR2) RETURN BOOLEAN
AS
BEGIN
return true;
END;
/
WITH
FUNCTION func_yn(parameter_i VARCHAR2)
RETURN NUMBER
IS
l_return_value BOOLEAN;
BEGIN
l_return_value :=func(parameter_i => parameter_i);
RETURN CASE l_return_value WHEN TRUE THEN 1 WHEN FALSE THEN 0 END;
END func_yn;
SELECT
func_yn('test')
FROM dual;

Make a query to both "id" or "slug" with a single variable

I have a table "articles" where there're "id" and "slug" among other things. On an html page I have a list of links to articles. A link can contain either "id" or "slug" in it.
But if in a URL there's only a number, it doesn't still mean that it's an id -- therefore, casting to int to determine whether or not it's slug or id, won't work.
/articles/my_article
/articles/35
/articles/666 --> still may be slug
I have this sql query:
import (
"github.com/jackc/pgx/v4"
//.........
)
// [..........]
vars := mux.Vars(req)
q1 := `
SELECT
ar.id,
[.........]
FROM
articles AS ar
WHERE ar.slug = $1 OR ar.id = $1`
ar := Article{}
row := db.QueryRow(context.Background(), q1, vars["id_or_slug"])
switch err := row.Scan(&ar.Id, /*[.......]*/); err {
case pgx.ErrNoRows:
wrt.WriteHeader(http.StatusNotFound)
wrt.Write([]byte("article not found"))
case nil:
// good, article found
I get:
ERROR: operator does not exist: bigint = text (SQLSTATE 42883)
You can "attempt" to convert the value to an integer and if the conversion fails just ignore the error and provide an id value known to not be present in the db.
Doing the conversion with Go:
slug := mux.Vars(req)["id_or_slug"]
// option 1:
id, err := strconv.ParseInt(slug, 10, 64)
if err != nil {
id = -1 // provide a value that you're certain will not be present in the db
}
// option 2:
// if id 0 is good enough, you can skip error checking
// and use the following instead of the above.
id, _ := strconv.ParseInt(slug, 10, 64)
query := `SELECT ... FROM articles AS a
WHERE a.slug = $1
OR a.id = $2`
row := db.QueryRow(query, slug, id)
Doing the conversion with postgres: (the following postgres snippet was taken from here.
)
-- first create a postgres function that will do the conversion / cast
create or replace function cast_to_int(text, integer) returns integer as $$
begin
return cast($1 as integer);
exception
when invalid_text_representation then
return $2;
end;
$$ language plpgsql immutable;
... and then utilizing that in go:
slug := mux.Vars(req)["id_or_slug"]
query := `SELECT ... FROM articles AS a
WHERE a.slug = $1
OR a.id = cast_to_int($1::text, -1)` // use the postgres function in the go query string
row := db.QueryRow(query, slug)

How do procedures work with object types in PL/SQL?

I don't fully understand the purpose of procedures for PL/SQL object types. From reading online, the impression I got was that functions should not modify the data and should only return a value, instead procedures should be used, however it appears that for object types, changes in a procedure don't propagate outside the local context:
CREATE OR REPLACE TYPE TestType FORCE
AS OBJECT
(
TestValue NUMBER
, CONSTRUCTOR FUNCTION TestType
RETURN SELF AS RESULT
, MEMBER PROCEDURE IncrementValueAsProcedure
, MEMBER FUNCTION IncrementValueAsFunction
RETURN TestType
)
/
CREATE OR REPLACE TYPE BODY TestType
AS
CONSTRUCTOR FUNCTION TestType
RETURN SELF AS RESULT
IS
BEGIN
SELF.TestValue := 0;
RETURN;
END;
MEMBER PROCEDURE IncrementValueAsProcedure
IS
MyType TestType := SELF;
BEGIN
MyType.TestValue := TestValue + 1;
END;
MEMBER FUNCTION IncrementValueAsFunction
RETURN TestType
IS
MyType TestType := SELF;
BEGIN
MyType.TestValue := TestValue + 1;
RETURN MyType;
END;
END;
/
DECLARE
MyTest TestType;
BEGIN
MyTest := TestType();
DBMS_OUTPUT.PUT_LINE(MyTest.TestValue);
MyTest.IncrementValueAsProcedure();
DBMS_OUTPUT.PUT_LINE(MyTest.TestValue);
MyTest := MyTest.IncrementValueAsFunction();
DBMS_OUTPUT.PUT_LINE(MyTest.TestValue);
END;
This gives the output:
0
0
1
So the change in the update procedure hasn't been picked up. What am I doing wrong?
Oh, worked this out.
MEMBER PROCEDURE IncrementValueAsProcedure
IS
MyType TestType := SELF;
BEGIN
MyType.TestValue := TestValue + 1;
END;
This creates a copy of the current object and updates it. The following works as expected:
MEMBER PROCEDURE IncrementValueAsProcedure
IS
BEGIN
TestValue := TestValue + 1;
END;

Oracle aggregate functions and how to concatenate all values in column

All,
My Oracle Database is version 10g Enterprise Edition Release 10.2.0.5.0 - 64bit
I have the following statement which usefully gets me the max (or min or count etc) values in each case as expected however what I would like is to get and to concatenate all of the values rather than the max, min or count - is there an elegant way to do this please ?
SELECT lla.id,
max(decode(lla.attrid, 2, lla.valstr, null)) "Attribute Name 2",
min(decode(lla.attrid, 3, lla.valstr, null)) "Attribute Name 3",
count(decode(lla2.attrid, 5, lla2.valstr, null)) "Attribute Name 5"
FROM llattrdata lla, llattrdata lla2
WHERE lla.id = lla2.id
AND lla.defid = 111111 --category id 1
AND lla2.defid = 222222 --category id 2
AND lla.id = 48212327 and lla2.id = 48212327
GROUP BY lla.id
Hoping for a row that looks something like this:
12121212 | fred, jack, gill | 56 | 29,10
To be clearer it is the values that the 'Attribute Name 3' (for example) contains that I want to see all of and not just the max or the minimum. In other words for that attribute I can get the max or the min value or even the count but cannot see a way to get all of the values ? In other words I can get 10 as the min and 29 as the max - even 2 as the count but not 29 and 10 in the same column !
Many thanks in advance,
SELECT e.department_id,
listagg(e.first_name) within group (order by e.department_id) "Attribute Name 2"
FROM employees e join
departments d
on e.department_id = d.department_id
GROUP BY e.department_id;
you can use above example and alter your query
Try this:
SELECT lla.id || ' | ' ||
max(decode(lla.attrid, 2, lla.valstr, null)) || ' | ' ||
min(decode(lla.attrid, 3, lla.valstr, null)) || ' | ' ||
count(decode(lla2.attrid, 5, lla2.valstr, null))
FROM llattrdata lla, llattrdata lla2
WHERE lla.id = lla2.id
AND lla.defid = 111111 --category id 1
AND lla2.defid = 222222 --category id 2
AND lla.id = 48212327 and lla2.id = 48212327
GROUP BY lla.id
Use wmsys.wm_concat function learn more here about it. This is a non-documented function in Oracle 10.
It returns you comma-separated list, you can use replace function to replace comma with the thing you need.
Unfortunately this function does not have order clause so you cannot specify the order in the list.
EDIT:
As far as this function is not available for you, you can simply create it:
CREATE OR REPLACE TYPE wm_concat_impl
AUTHID CURRENT_USER
AS OBJECT (
curr_str VARCHAR2 (32767),
STATIC FUNCTION odciaggregateinitialize (sctx IN OUT wm_concat_impl)
RETURN NUMBER,
MEMBER FUNCTION odciaggregateiterate (
SELF IN OUT wm_concat_impl,
p1 IN VARCHAR2
)
RETURN NUMBER,
MEMBER FUNCTION odciaggregateterminate (
SELF IN wm_concat_impl,
returnvalue OUT VARCHAR2,
flags IN NUMBER
)
RETURN NUMBER,
MEMBER FUNCTION odciaggregatemerge (
SELF IN OUT wm_concat_impl,
sctx2 IN wm_concat_impl
)
RETURN NUMBER
);
/
CREATE OR REPLACE TYPE BODY wm_concat_impl
IS
STATIC FUNCTION odciaggregateinitialize (sctx IN OUT wm_concat_impl)
RETURN NUMBER
IS
BEGIN
sctx := wm_concat_impl (NULL);
RETURN odciconst.success;
END;
MEMBER FUNCTION odciaggregateiterate (
SELF IN OUT wm_concat_impl,
p1 IN VARCHAR2
)
RETURN NUMBER
IS
BEGIN
IF (curr_str IS NOT NULL)
THEN
curr_str := curr_str || ',' || p1;
ELSE
curr_str := p1;
END IF;
RETURN odciconst.success;
END;
MEMBER FUNCTION odciaggregateterminate (
SELF IN wm_concat_impl,
returnvalue OUT VARCHAR2,
flags IN NUMBER
)
RETURN NUMBER
IS
BEGIN
returnvalue := curr_str;
RETURN odciconst.success;
END;
MEMBER FUNCTION odciaggregatemerge (
SELF IN OUT wm_concat_impl,
sctx2 IN wm_concat_impl
)
RETURN NUMBER
IS
BEGIN
IF (sctx2.curr_str IS NOT NULL)
THEN
SELF.curr_str := SELF.curr_str || ',' || sctx2.curr_str;
END IF;
RETURN odciconst.success;
END;
END;
/
CREATE OR REPLACE FUNCTION wm_concat (p1 VARCHAR2)
RETURN VARCHAR2
AGGREGATE USING wm_concat_impl;
/
The query is taken from this website, it is, unfortunately, in Russian, but just use this custom aggregate function for your purposes.
I had the same problem and used the STRAGG ( as in STRing AGGregate) function created by Tom Kyte.
https://asktom.oracle.com/pls/asktom/f?p=100:11:::::P11_QUESTION_ID:15637744429336
create or replace type stragg_type as object
(
string varchar2(4000),
static function ODCIAggregateInitialize
( sctx in out stragg_type )
return number ,
member function ODCIAggregateIterate
( self in out stragg_type ,
value in varchar2
) return number ,
member function ODCIAggregateTerminate
( self in stragg_type,
returnvalue out varchar2,
flags in number
) return number ,
member function ODCIAggregateMerge
( self in out stragg_type,
ctx2 in stragg_type
) return number
);
/
create or replace type body stragg_type
is
static function ODCIAggregateInitialize
( sctx in out stragg_type )
return number
is
begin
sctx := stragg_type( null ) ;
return ODCIConst.Success ;
end;
member function ODCIAggregateIterate
( self in out stragg_type ,
value in varchar2
) return number
is
begin
self.string := self.string || ',' || value ;
return ODCIConst.Success;
end;
member function ODCIAggregateTerminate
( self in stragg_type ,
returnvalue out varchar2 ,
flags in number
) return number
is
begin
returnValue := ltrim( self.string, ',' );
return ODCIConst.Success;
end;
member function ODCIAggregateMerge
( self in out stragg_type ,
ctx2 in stragg_type
) return number
is
begin
self.string := self.string || ctx2.string;
return ODCIConst.Success;
end;
end;
/
create or replace function stragg
( input varchar2 )
return varchar2
deterministic
parallel_enable
aggregate using stragg_type
;
/
Run the three create statements one after the other in sqlplus or sqldev. Now the stragg() function is created in your user schema. Then you can do:
SELECT lla.id,
max(decode(lla.attrid, 2, lla.valstr, null)) "Attribute Name 2",
STRAGG(decode(lla.attrid, 3, lla.valstr, null)) "Attribute Name 3 List",
count(decode(lla2.attrid, 5, lla2.valstr, null)) "Attribute Name 5"
FROM llattrdata lla, llattrdata lla2
WHERE lla.id = lla2.id
AND lla.defid = 111111 --category id 1
AND lla2.defid = 222222 --category id 2
AND lla.id = 48212327 and lla2.id = 48212327
GROUP BY lla.id

Query to find an average weighted price

I have a table in Oracle with multiple rows per a given part. Each row has a quantity and a price associated with it. There is also a total quantity that the set of rows for a given part adds up to. Below is a sample of the data. What I need is to get the average weighted price for the part. For example if a quantity of 100 of a part has a price of 1 and a quantity of 50 has a price of 2 the weighted average price is 1.33333333
PART TOTAL_QTY QTY PRICE_PER
----------------------------------
part1 317 244 27
part1 317 40 53.85
part1 317 33 24.15
Ideas?
Try this:
SELECT part, SUM(qty*price_per)/SUM(qty)
FROM <YOUR_TABLE>
GROUP BY part
create a user defined aggregate function to calculate the weighted average:
CREATE OR REPLACE TYPE WEIGHTED_AVG_O AS OBJECT (
sum_of_weights NUMBER,
sum_of_weights_times_value NUMBER,
STATIC FUNCTION ODCIAGGREGATEINITIALIZE(cs_ctx IN OUT WEIGHTED_AVG_O) RETURN NUMBER,
MEMBER FUNCTION ODCIAGGREGATEITERATE (self IN OUT WEIGHTED_AVG_O, value IN WEIGHTED_AVG_O) RETURN NUMBER,
MEMBER FUNCTION ODCIAGGREGATEMERGE (self IN OUT WEIGHTED_AVG_O, ctx2 IN OUT WEIGHTED_AVG_O) RETURN NUMBER,
MEMBER FUNCTION ODCIAGGREGATETERMINATE (self IN OUT WEIGHTED_AVG_O, returnvalue OUT NUMBER, flags IN NUMBER) RETURN NUMBER
);
/
CREATE OR REPLACE TYPE BODY WEIGHTED_AVG_O
AS
STATIC FUNCTION ODCIAGGREGATEINITIALIZE(cs_ctx IN OUT WEIGHTED_AVG_O) RETURN NUMBER
IS
BEGIN
cs_ctx := WEIGHTED_AVG_O(0, 0);
RETURN odciconst.success;
END;
MEMBER FUNCTION ODCIAGGREGATEITERATE (self IN OUT WEIGHTED_AVG_O, value IN WEIGHTED_AVG_O) RETURN NUMBER
IS
BEGIN
self.sum_of_weights := self.sum_of_weights + value.sum_of_weights;
self.sum_of_weights_times_value := self.sum_of_weights_times_value + value.sum_of_weights * value.sum_of_weights_times_value;
RETURN odciconst.success;
END;
MEMBER FUNCTION ODCIAGGREGATEMERGE (self IN OUT WEIGHTED_AVG_O, ctx2 IN OUT WEIGHTED_AVG_O) RETURN NUMBER
IS
BEGIN
RETURN odciconst.success;
END;
MEMBER FUNCTION ODCIAGGREGATETERMINATE (self IN OUT WEIGHTED_AVG_O, returnvalue OUT NUMBER, flags IN NUMBER) RETURN NUMBER
IS
BEGIN
IF sum_of_weights = 0 THEN
returnvalue := NULL;
ELSE
returnvalue := sum_of_weights_times_value / sum_of_weights;
END IF;
RETURN odciconst.success;
END;
END;
/
CREATE OR REPLACE FUNCTION WEIGHTED_AVG (input WEIGHTED_AVG_O)
RETURN NUMBER PARALLEL_ENABLE
AGGREGATE USING WEIGHTED_AVG_O;
/
query with your data:
SELECT part, WEIGHTED_AVG(WEIGHTED_AVG_O(qty, price_per))
FROM <YOUR_TABLE>
GROUP BY part;