Oracle - Grouping Sets and Pipelined Table Functions (expected NUMBER got ROW) - oracle

I'm working on a report with summary logic using GROUPING SETS, but I'm getting this error:
SELECT c1, c2, c3, SUM(c4) AS MySum
FROM TABLE(get_data()) src
GROUP BY GROUPING SETS ((c1, c2, c3), (c1, c2), c1, c2, ());
-------------------------
ORA-00932: inconsistent datatypes: expected NUMBER got XXX.MYROW
00932. 00000 - "inconsistent datatypes: expected %s got %s"
It works fine when I only include c1 or c2 separately:
GROUP BY GROUPING SETS ((c1, c2, c3), (c1, c2), c1, ());
GROUP BY GROUPING SETS ((c1, c2, c3), (c1, c2), c2, ());
It also works fine when I source the query directly from the t1 table:
SELECT c1, c2, c3, SUM(c4) AS MySum
FROM t1 src
GROUP BY GROUPING SETS ((c1, c2, c3), (c1, c2), c1, c2, ());
What am I missing? I feel like it's something simple. Here's a simplified example of my setup:
-- Base table
CREATE TABLE t1 (c1 VARCHAR(10), c2 VARCHAR(10), c3 VARCHAR(10), c4 INTEGER);
-- Row type
CREATE TYPE myrow AS OBJECT (c1 VARCHAR(10), c2 VARCHAR(10), c3 VARCHAR(10), c4 INTEGER);
-- Table type
CREATE OR REPLACE TYPE mytable AS TABLE OF myrow;
-- Get data function
CREATE OR REPLACE FUNCTION get_mydata
RETURN mytable PIPELINED AS
BEGIN
FOR v_rec IN (
SELECT c1, c2, c3, c4
FROM t1
) LOOP
PIPE ROW (myrow(v_Rec.c1, v_Rec.c2, v_Rec.c3, v_Rec.c4));
END LOOP;
RETURN;
END;
DB version - 12.1.0
Update
Different error I get with my actual function (even with "materialize" hint):
ORA-22905: cannot access rows from a non-nested table item 22905.
00000 - "cannot access rows from a non-nested table item"
*Cause: attempt to access rows of an item whose type is not known
at parse time or that is not of a nested table type
*Action: use CAST to cast the item to a nested table type

I don't know why it won't work, but - see if this workaround helps (use a CTE and the materialize hint):
SQL> with test as
2 (select /*+ materialize */
3 c1, c2, c3, c4
4 from table(get_mydata()) src
5 )
6 select c1, c2, c3, sum(c4) as mysum
7 from test
8 group by grouping sets ((c1, c2, c3), (c1, c2), c1, c2, ());
C1 C2 C3 MYSUM
---------- ---------- ---------- ----------
1 2 3 4
1 4
2 4
4
1 2 4
SQL>

You can workaround the bug by using a non-pipelined function and BULK COLLECT:
CREATE OR REPLACE FUNCTION get_mydata2
RETURN mytable AS
v_data mytable;
BEGIN
SELECT myrow(c1, c2, c3, c4)
BULK COLLECT
INTO v_data
FROM t1;
RETURN v_data;
END;
/
I was able to reproduce your error in version 19c so it looks like you found a relatively big Oracle bug. In my experience, pipelined functions are generally buggier than regular functions and tend to be over-used. Pipelined functions can return data faster, but since your query is aggregating all the rows, you won't see that performance boost anyway.
The main problem with the non-pipelined function approach is that your session will need enough memory to store all of the results from the BULK COLLECT at once. That may not be feasible if you have to process millions of wide rows.

Related

How to update a table with a function using the same table but which uses only columns not modified by this update

CREATE TABLE A (
A1 integer,
A2 integer,
A3 integer
);
create function f(a1_ in integer) return integer
is
ret integer;
begin
select a3+1 into ret from A where a1=a1_;
return ret;
end;
INSERT INTO A SELECT 1 a1,1 a2,1 a3 FROM dual union all select 2 a1, 2 a2, 2 a3 from dual
update A t1
set a3 =
(select f(a1) from A t2 where t1.a1=t2.a1);
A is mutating, trigger/function may not see it
This code doesn't work. contrary to
update A t1
set a3=
(select f(a1) from A t2 where t1.a1=t2.a1);
The problem is the following. I'm using a function using the same table to update the table.
But the columns that I'm using in these table are not modified by f.
I'm using only a1 to return the next value of a3.
And a1 isn't modified in this statement
Is there a way to do these operation. (perhaps using a keyword or something else)
code
You can use PL/SQL:
DECLARE
v_a1s SYS.ODCINUMBERLIST;
v_fs SYS.ODCINUMBERLIST;
BEGIN
SELECT a1, f(a1)
BULK COLLECT INTO v_a1s, v_fs
FROM A
FOR UPDATE OF a3;
FORALL i IN 1 .. v_a1s.COUNT
UPDATE A
SET a3 = v_fs(i)
WHERE a1 = v_a1s(i);
END;
/
db<>fiddle here

Implementing Oracle's rownum() in Apache Impala

I am converting an Oracle query into Impala equivalent. I have a Oracle query like this:
select c1, c2 from t1
where rownum <= (select c3 from t2 where c4 = 'Some string' and c5 = 'some string')
and c2 in (1,2,3) order by c3 asc;
However Impala does not support rownum() that I came to know while researching. Please help me in implementing this in Impala.
Thank you in advance.
You dont have anything like rownum in oracle. However you can create a pseudo column using row_number() over (partition by col, order by col) function. You need to avoid partition by clause.
You can change your sql and add a subquery to calculate rownum column like below.
Also you need to change your query so it works in impala using join instead of the way you wrote above.
select c1, c2, c3 from
(select c1,c2, row_number() over (order by c1) as rownum from t1 ) t1
join (select c3 from t2 where c4 = 'Some string' and c5 = 'some string')
and c2 in (1,2,3)) t2 on
rownum<=t2.c3
order by c3 asc;
According to the documentation, you can use row_number of Impala as it is.
Which means your query would be executed successfully if you try this way:
select column from table
where row_number = 1;

How to include filters of analysis in on part of the left outer join in obiee (left join becoming inner join)

Recently we had an issue of outer joins working as inner joins. The steps we did were the following:
1.Created left outer join between dim table and fact table in BMM layer.
2.Created an analysis in OBIEE to check this join
3.Sql generated shows left outer join statement but filters that were used in OBIEE analysis are placed in where clause and left outer join is ignored.
Here is the SQL code generated:
WITH SAWITH0 AS
(select sum(T4110.SALDO_OUT_EQV) as c1,
T5520.CAL_DAY as c2,
T75347.LINE_CODE as c3,
T75347.LINE_NAME as c4,
T3160.CODE as c5
from DM_CBM_T17_V T75347 /* D4703 Dm Cbm T17 */
left outer join(DM_CALENDAR_V T5520 /* D03 Calendar */
inner join(DM_FILIALS_V T3160 /* D04 Filials */Q
inner join DM_COA_SALDO_V T4110 /* F02 Saldo Coa */
On T3160.CODE = T4110.FILIAL_CODE) On T4110.OPER_DAY = T5520.CAL_DAY) On T4110.COA_CODE = T75347.COA_CODE
where (T4110.OPER_DAY = TO_DATE('2021-05-06', 'YYYY-MM-DD') and
T5520.CAL_DAY = TO_DATE('2021-05-06', 'YYYY-MM-DD'))
group by T3160.CODE, T5520.CAL_DAY, T75347.LINE_CODE, T75347.LINE_NAME)
select D1.c1 as c1,
D1.c2 as c2,
D1.c3 as c3,
D1.c4 as c4,
D1.c5 as c5,
D1.c6 as c6
from (select 0 as c1,
D1.c2 as c2,
D1.c3 as c3,
D1.c4 as c4,
D1.c5 as c5,
D1.c1 as c6
from SAWITH0 D1
order by c2, c5, c3, c4) D1
where rownum <= 10000000
As you can see, the join between dim D4703 and fact F02 is created on coa_code column. Dimension D4703 has rows with null values on column coa_code. The logic we followed was that left outer join should return all rows from dim D4703 (including nulls) and only those rows from fact table that are matched.
However, there sql query returns only matching rows. And when you put day filter in where clause inside on statement it is returning null values.
So I wanted to ask how to make left outer join work as intended? Is there any option in Admin tool or anywhere else?
We call this "preservation of the dimensions". there are two ways to achieve this with OBIEE :
1- using the full outer join , follow this article :
https://datacadamia.com/dat/obiee/obis/full_outer_join
2- the second is using a cross Join between the dimensions by adding a Fact table, follow this article:
https://datacadamia.com/dat/obiee/obis/densification_repository?s[]=preservation&s[]=dimensions
My favorite is the second approach and this is my recommendation.

plsql: inserting Multiple rows from select and ignore duplicates

I am using oracle 10g with TOAD.
I need to insert lacs of records using INSERT FROM SELECT.
BEGIN
INSERT INTO MYTABLE(C1,C2,C3)
SELECT C1,C2,C3 FROM MYTABLE2 WHERE C1>100;
EXCEPTION
WHEN DUP_VAL_ON_INDEX THEN NULL;
COMMIT;
END;
Here, the problem , i am facing is , if this select queries return rows which is already exists in MYTABLE, THEN all transaction will be rolledback.
Is there a way to insert all non-existent rows ,ignoring duplicate rows and continuing with insertion of non-existent rows and then committing the transaction?
Use the Distinct Keyword
BEGIN
INSERT INTO MYTABLE(C1,C2,C3)
SELECT distinct C1,C2,C3 FROM MYTABLE2 WHERE C1>100;
EXCEPTION
WHEN DUP_VAL_ON_INDEX THEN NULL;
COMMIT;
END;
Instead of trying to handle the exception, you can avoid these rows in the first place, e.g., by using the minus operator:
INSERT INTO mytable (c1, c2, c3)
SELECT c1, c2, c3
FROM mytable2
WHERE c1 > 100;
MINUS
SELECT c1, c2, c3
FROM mytable
WHERE c1 > 100 -- not necessary, but should improve performance
There are many ways to do it. First of all, you can try something like this:
insert into mytable(c1, c2, c3)
select distinct c1, c2, c3 from mytable2 where c1 > 100
minus
select c1, c2, c3 from mytable;
Otherwise, you can use something like
insert into mytable(c1, c2, c3)
select c1, c2, c3 from mytable2 where c1 > 100
log errors into myerrtable reject limit unlimited;
And so on...
More detailed about error logging. Feauture introduced since 10g release 2.
SQL> create table garbage(id integer);
Table created
SQL> insert into garbage select rownum from dual connect by level <= 10;
10 rows inserted
SQL> insert into garbage values (3);
1 row inserted
SQL> insert into garbage values (5);
1 row inserted
SQL> create table uniq(id integer not null primary key);
Table created
SQL> insert into uniq select * from garbage;
ORA-00001: unique constraint (TEST.SYS_C0010568) violated
SQL> select count(*) from uniq;
COUNT(*)
----------
0
SQL> exec dbms_errlog.create_error_log('uniq', 'uniq_err');
PL/SQL procedure successfully completed
SQL> insert into uniq select * from garbage
2 log errors into uniq_err reject limit unlimited;
10 rows inserted
SQL> select * from uniq;
ID
---------------------------------------
1
2
3
4
5
6
7
8
9
10
10 rows selected
SQL> select ora_err_mesg$, id from uniq_err;
ORA_ERR_MESG$ ID
---------------------------------------------------------------------- --
ORA-00001: unique constraint (TEST.SYS_C0010568) violated 3
ORA-00001: unique constraint (TEST.SYS_C0010568) violated 5
Thought I would make this an answer, but it really depends on what your trying to achieve.
You can check to see if the data is already in table2 using:
INSERT INTO mytable2 (c1, c2, c3)
SELECT DISTINCT c1,c2,c3 FROM mytable t1
WHERE c1 > 100
AND NOT EXISTS
(select 1 from mytable2 t2 where t2.c1 = t1.c1 and t2.c2 = t1.c2 and t2.c3 = t1.c3);
or you can use a merge like this:
MERGE INTO mytable2 m2
USING (SELECT DISTINCT c1, c2, c3 FROM mytable) m1
ON (m1.c1 = m2.c1 and m1.c2 = m2.c2 and m1.c3 = m2.c3)
WHEN NOT MATCHED THEN INSERT (c1, c2, c3) VALUES (m1.c1, m1.c2, m1.c3)
where m1.c1 > 100;
In both examples, you will only insert unique rows into mytable2

Selecting all rows after a row with specific values without repeating the same subquery

I have a table t1 and t2 which I join and order to form data set set1.
Two columns c1 and c2 form a unique identifier for the rows in set1.
I want to get all values from set1 after the first row with a specific c1 and c2.
I have a query like the one below which works, but it repeats the same subquery twice, which seems superfluous and overly complex even for Oracle:
SELECT * FROM
(
SELECT row_number() OVER (ORDER BY c1, c3) myOrder, c1, c2, c3
FROM t1, t2
WHERE condition
ORDER BY conditions
) sub1,
(
SELECT sub1_again.myOrder FROM
(
SELECT row_number() OVER (ORDER BY c1, c3) myOrder, c2, c3
FROM t1, t2
WHERE condition
ORDER BY conditions
) sub1_again
WHERE sub1_again.c2 = "foo" AND sub1_again.c3 = "bar"
) sub2
WHERE sub1.myOrder >= sub2.myOrder
ORDER BY sub1.myOrder
It seems like SQL would have a simple way to do this, but I am not sure what to search for.
Is there a cleaner way to do this?
SELECT * FROM (
SELECT row_number() OVER (ORDER BY c1, c3) myOrder, c2, c3
,CASE WHEN c2 = "foo" AND c3 = "bar"
THEN row_number() OVER (ORDER BY c1, c3)
END target_rn
FROM t1, t2
WHERE condition
ORDER BY conditions
) WHERE myOrder > target_rn;
I think there is something missing in the accepted solution. However, it helped me to come up with this:
SELECT * FROM (
SELECT row_number() OVER (ORDER BY c1, c3) myOrder, c1, c2, c3,
max(case when c2 = "foo" AND c3 = "bar" then 1 else 0 end) over (order by c1, c3) rowFound,
FROM t1, t2
WHERE condition
)
WHERE rowFound > 0
ORDER BY conditions
Basically the case is selecting which row is the one to start from, and the max "drags" the value from that row onwards. The last WHERE does the filtering.
Please try to be more specific,if i understood well you have just to add the parameter 'where',like:SELECT * FROM **where** element

Resources