What is difference between CROSS APPLY and LATERAL in oracle? - oracle

I am able to understand the difference between Cross apply and Outer apply.
But what is the difference between Cross apply and Lateral?
Both seem to correlate an inline view and join the left table with the right table.

It's not a general answer. It's only an example of something you can do with lateral but not with apply:
you have a table t (has only 1 lines in this example). For each line of t, you make 2 differents types of computations (X and Y) which returns severals (3) lines.
you don't want to make a cross product of X and Y. You want that the first lines X to be next to the first line of Y and so on.
X
Y
1
11
2
22
3
33
If you use cross apply. the only way (witout creating subqueries) to that is to add a condition (in the where block)
WITH
t (X, Y)
AS
(SELECT json_array (1, 2, 3), json_array (11, 22, 33) FROM DUAL),
step2
AS
(SELECT t1.idx, t1.x, t2.Y
FROM t
CROSS APPLY
(SELECT *
FROM JSON_TABLE (
t.X,
'$'
COLUMNS (
NESTED PATH '$[*]'
COLUMNS (idx FOR ORDINALITY, x PATH '$'))))
t1
CROSS APPLY
JSON_TABLE (
t.Y,
'$'
COLUMNS (
NESTED PATH '$[*]'
COLUMNS (idx FOR ORDINALITY, y PATH '$'))) t2
WHERE t1.idx = t2.idx) --on t1.idx=t2.idx
SELECT step2.x, step2.Y
FROM step2;
But with lateral you can do that with a jointure. It easier to read.
WITH
t (X, Y)
AS
(SELECT json_array (1, 2, 3), json_array (11, 22, 33) FROM DUAL),
step2
AS
(SELECT t1.idx, t1.x, t2.Y
FROM t
CROSS APPLY
(SELECT *
FROM JSON_TABLE (
t.X,
'$'
COLUMNS (
NESTED PATH '$[*]'
COLUMNS (idx FOR ORDINALITY, x PATH '$'))))
t1
INNER JOIN
LATERAL (
SELECT *
FROM JSON_TABLE (
t.Y,
'$'
COLUMNS (
NESTED PATH '$[*]'
COLUMNS (idx FOR ORDINALITY,
y PATH '$')))--where t1.idx=t2.idx
) t2
ON t1.idx = t2.idx)
SELECT *
FROM step2;
code
where the example comes from

Related

How to change rownum only if the next row is distinct

I have this
RowNum
Value
1
X
2
X
3
Y
4
Z
5
Z
6
Z
7
V
and I want something like this
RowNum
Value
1
X
1
X
2
Y
3
Z
3
Z
3
Z
4
V
How can I do that in Oracle?
Thanks
Here is one way - using the match_recognize clause, available since Oracle 12.1. Note that rownum is a reserved keyword, so it can't be a column name; I changed it to rnum in the input and rn in the output, adapt as needed.
The with clause is not part of the query - it's there just to include your sample data. Remove it before using the query on your actual data.
with
inputs (rnum, value) as (
select 1, 'X' from dual union all
select 2, 'X' from dual union all
select 3, 'Y' from dual union all
select 4, 'Z' from dual union all
select 5, 'Z' from dual union all
select 6, 'Z' from dual union all
select 7, 'V' from dual
)
select rn, value
from inputs
match_recognize (
order by rnum
measures match_number() as rn
all rows per match
pattern ( a+ )
define a as value = first(value)
);
RN VALUE
-- -----
1 X
1 X
2 Y
3 Z
3 Z
3 Z
4 V
In Oracle 11.2 and earlier, you can use the start-of-group method (the flags created in the subquery and counted in the outer query):
select count(flag) over (order by rnum) as rn, value
from (
select rnum, value,
case lag(value) over (order by rnum)
when value then null else 1 end as flag
from inputs
)
;

Oracle: How functional table works in cross join?

I explored technique to unfold comma separated list in column into rows:
with tbl as (
select 1 id, 'a,b' lst from dual
union all select 2 id, 'c' lst from dual
union all select 3 id, 'e,f,g' lst from dual)
select
tbl.ID
, regexp_substr(tbl.lst, '[^,]+', 1, lvl.column_value) elem
, lvl.column_value lvl
from
tbl
, table(cast(multiset(
select level from dual
connect by level <= regexp_count(tbl.lst, ',')+1) as sys.odcinumberlist)) lvl;
Result is:
ID ELEM LVL
1 a 1
1 b 2
2 c 1
3 e 1
3 f 2
3 g 3
As you can see LVL depends on value of regexp_count, so second functional table in cross join is parametrized by first table.
How is it working? How is it called? Can I paramertize third table based on two preceding in cross join and so forth?
Is parametrization limited to cross join or can be applied in join syntax too?
Reference: Splitting string into multiple rows in Oracle
From the documentation:
LATERAL
Specify LATERAL to designate subquery as a lateral inline
view. Within a lateral inline view, you can specify tables that appear
to the left of the lateral inline view in the FROM clause of a query.
You can specify this left correlation anywhere within subquery (such
as the SELECT, FROM, and WHERE clauses) and at any nesting level.
-- a variation of the query in your question ...
select
dt.id
, dt.list
, regexp_substr( dt.list, '[^,]+', 1, dt2.lvl ) elements
, dt2.lvl
from (
select 1 id, 'a,b' list from dual union all
select 2, 'c' from dual union all
select 3, 'e,f,g' from dual
) dt, lateral (
select level lvl from dual
connect by level <= regexp_count(dt.list, ',') + 1
) dt2
;
-- output
ID LIST ELEMENTS LVL
1 a,b a 1
1 a,b b 2
2 c c 1
3 e,f,g e 1
3 e,f,g f 2
3 e,f,g g 3
Example with 3 tables:
--drop table t1 ;
--drop table t2 ;
--drop table t3 ;
-- tables/data
create table t1
as
select 1 id, 'a' letter from dual union all
select 2, 'b' from dual union all
select 3, 'c' from dual ;
create table t2
as
select 1 id, 'd' letter from dual union all
select 2, 'e' from dual union all
select 3, 'f' from dual ;
create table t3
as
select 1 id, 'g' letter from dual union all
select 2, 'h' from dual union all
select 3, 'i' from dual ;
-- query
select *
from
t1
, lateral ( select letter from t2 where id = t1.id ) t2
, lateral ( select letter from t3 where id = t2.id )
;
-- output
ID LETTER LETTER LETTER
1 a d g
2 b e h
3 c f i
Also (using the same tables)
-- reference t1 <- t2,
-- reference t1 and t2 <- t3
select *
from
t1
, lateral ( select letter from t2 where id = t1.id ) t2
, lateral ( select letter || t1.letter from t3 where id = t2.id )
;
-- output
ID LETTER LETTER LETTER||T1.LETTER
1 a d ga
2 b e hb
3 c f ic
Whereas a "standard" cross join would give us ...
select *
from
t1 cross join t2 cross join t3
;
ID LETTER ID LETTER ID LETTER
1 a 1 d 1 g
1 a 1 d 2 h
1 a 1 d 3 i
1 a 2 e 1 g
1 a 2 e 2 h
1 a 2 e 3 i
...
-- 27 rows
Related topics: CROSS APPLY (see documentation and examples here).

Generate List for label with barcode printing from tablem with number quantity of rows

I try to generate list of products for printing labels, but all of my attempt fail (with connect by level)!
My table:
CREATE TABLE LABELS
(
PRODUCT VARCHAR2(8 BYTE),
Q_ROWS NUMBER
);
Information in the table:
INSERT INTO LABELS (PRODUCT, Q_ROWS) VALUES('D', 3);
INSERT INTO LABELS (PRODUCT, Q_ROWS) VALUES('A', 1);
INSERT INTO LABELS (PRODUCT, Q_ROWS) VALUES('C', 4);
INSERT INTO LABELS (PRODUCT, Q_ROWS) VALUES('B', 2);
Expected Result in a oracle select
PRODUCT
A
B
B
C
C
C
C
D
D
D
Results: (1 row for A, 2 rows for B, 4 rows to C and 3 rows to D)
Can someone help me?
Use LEVEL to get a "table" that counts from 1 to the maximum number of rows:
SELECT LEVEL AS LabelNum
FROM DUAL
CONNECT BY LEVEL <= (SELECT MAX(Q_Rows) FROM Labels)
This will give you the following table:
LabelNum
--------
1
2
3
4
Next, join this to your LABELS table where LabelNum <= Q_Rows. Here's the whole query:
WITH Mult AS (
SELECT LEVEL AS LabelNum
FROM DUAL
CONNECT BY LEVEL <= (SELECT MAX(Q_Rows) FROM Labels)
)
SELECT Product
FROM Labels
INNER JOIN Mult ON LabelNum <= Q_Rows
ORDER BY Product, LabelNum
There's a working SQLFiddle here.
Finally, good job including the create/populate scripts :)
Another approach, using model clause:
select product
from labels
model
partition by (product)
dimension by (1 as indx)
measures(q_rows)
rules(
q_rows[for indx from 1 to q_rows[1] increment 1] = q_rows[1]
)
order by product
result:
PRODUCT
----------
A
B
B
C
C
C
C
D
D
D
SQLFiddle Demo

oracle matrix report filling null cells

I have made a matrix report in oracle report builder like this
And here is my query from which report is being calling
SELECT A.p_date,
L.sup_name,
Decode(A.perc_typ, 1, 'Buff',
2, 'Cow') PERC_TYPE,
A.sup_rate RATE,
Decode(A.perc_typ,
1, Round(( Nvl(A.fat_perc, 0) * Nvl(A.gross_vol, 0) ) / 6, 5),
2, Round(
( Nvl(A.fat_perc, 0) + (
( Nvl(A.fat_perc, 0) * 0.22 ) + (
Nvl(A.lr_perc, 0) * 0.25 ) + 0.72 ) ) *
Nvl(A.gross_vol, 0) / 13, 5)) VOL
FROM mlk_purchase A,
supplier L
WHERE A.sup_cod = L.sup_cod
AND A.p_date <= Trunc(SYSDATE)
AND a.p_date >= Trunc(SYSDATE) - 7
ORDER BY 1
Problem is that there are are showing empty cells where no data is coming from query. I want to show zero cells instead of empty space. Is there any way to do this in oracle report builder.
There are at least two solutions.
Solution 1 -- In Oracle Reports, create a boilerplate text object that displays the zero, and arrange this object so that it displays behind the matrix field. This way, the boilerplate is hidden when the field is displayed, but is revealed when the field is not displayed. This solution is described in the documentation.
Solution 2 -- Rewrite your query to return rows with zero values for combinations of your row and column fields that have no data. For example, you might find all the possible combinations of the matrix row and column fields (supplier and date in this case), outer join your data to the combinations, and use NVL to convert null values to zeroes. It might look something like this:
SELECT
L.P_DATE,
L.SUP_NAME,
DECODE(A.PERC_TYP, 1, 'Buff', 2, 'Cow') PERC_TYPE,
A.SUP_RATE RATE,
NVL
(
DECODE
(
A.PERC_TYP,
1,
ROUND
(
(NVL(A.FAT_PERC, 0) * NVL(A.GROSS_VOL, 0)) / 6,
5
),
2,
ROUND
(
(NVL(A.FAT_PERC, 0) +
(
(NVL(A.FAT_PERC, 0) * 0.22) +
(NVL(A.LR_PERC, 0) * 0.25) + 0.72)
) * NVL(A.GROSS_VOL, 0) / 13,
5
)
),
0
) VOL
FROM
MLK_PURCHASE A,
(
SELECT
L1.SUP_CODE,
L1.SUP_NAME,
L2.P_DATE
FROM
(
SELECT DISTINCT
SUPPLIER.SUP_CODE,
SUPPLIER.SUP_NAME
FROM
SUPPLIER
) L1,
(
SELECT DISTINCT
MLK_PURCHASE.P_DATE
FROM
MLK_PURCHASE
WHERE
MLK_PURCHASE.P_DATE <= TRUNC(SYSDATE)
AND
MLK_PURCHASE.P_DATE >= TRUNC(SYSDATE) - 7
) L2
) L
WHERE
A.SUP_COD (+) = L.SUP_COD
AND
A.P_DATE (+) = L.P_DATE
ORDER BY
1
A more efficient (and simpler) way to rewrite the query to the same effect might be to use a partitioned outer join between MLK_PURCHASE and SUPPLIER that partitions by SUP_CODE, but I don't know to what extent your version of Oracle Reports supports this syntax.

Interpolation between two values in a single query

I want to calculate a value by interpolating the value between two nearest neighbours.
I have a subquery that returns the values of the neighbours and their relative distance, in the form of two columns with two elements.
Let's say:
(select ... as value, ... as distance
from [get some neighbours by distance] limit 2) as sub
How can I calculate the value of the point by linear interpolation? Is it possible to do that in a single query?
Example: My point has the neighbour A with value 10 at distance 1, and the neighbour B with value 20 at distance 4. The function should return a value 10 * 4 + 20 * 1 / 5 = 12 for my point.
I tried the obvious approach
select sum(value * (sum(distance)-distance)) / sum(distance)
which will fail because you cannot work with group clauses inside group clauses. Using another subquery returning the sum is not possible either, because then I cannot forward the individual values at the same time.
This is an ugly hack (based on a abused CTE ;). The crux of it is that
value1 * distance2 + value2 * distance1
Can, by dividing by distance1*distance2, be rewritten to
value1/distance1 + value2/distance2
So, the products (or divisions) can stay inside their rows. After the summation, multiplying by (distance1*distance2) rescales the result to the desired output. Generalisation to more than two neighbors is left as an exercise to the reader.YMMV
DROP TABLE tmp.points;
CREATE TABLE tmp.points
( pname VARCHAR NOT NULL PRIMARY KEY
, distance INTEGER NOT NULL
, value INTEGER
);
INSERT INTO tmp.points(pname, distance, value) VALUES
( 'A' , 1, 10 )
, ( 'B' , 4, 20 )
, ( 'C' , 10 , 1)
, ( 'D' , 11 , 2)
;
WITH RECURSIVE twin AS (
select 1::INTEGER AS zrank
, p0.pname AS zname
, p0.distance AS dist
, p0.value AS val
, p0.distance* p0.value AS prod
, p0.value::float / p0.distance AS frac
FROM tmp.points p0
WHERE NOT EXISTS ( SELECT * FROM tmp.points px
WHERE px.distance < p0.distance)
UNION
select 1+twin.zrank AS zrank
, p1.pname AS zname
, p1.distance AS dist
, p1.value AS val
, p1.distance* p1.value AS prod
, p1.value::float / p1.distance AS frac
FROM tmp.points p1, twin
WHERE p1.distance > twin.dist
AND NOT EXISTS ( SELECT * FROM tmp.points px
WHERE px.distance > twin.dist
AND px.distance < p1.distance
)
)
-- SELECT * from twin ;
SELECT min(zname) AS name1, max(zname) AS name2
, MIN(dist) * max(dist) *SUM(frac) / SUM(dist) AS score
FROM twin
WHERE zrank <=2
;
The result:
CREATE TABLE
INSERT 0 4
name1 | name2 | score
-------+-------+-------
A | B | 12
Update: this one is a bit cleaner ... ties are still not handled (need a window function or a LIMIT 1 clause in the outer query for that)
WITH RECURSIVE twin AS (
select 1::INTEGER AS zrank
, p0.pname AS name1
, p0.pname AS name2
, p0.distance AS dist
FROM tmp.points p0
WHERE NOT EXISTS ( SELECT * FROM tmp.points px
WHERE px.distance < p0.distance)
UNION
select 1+twin.zrank AS zrank
, twin.name1 AS name1
, p1.pname AS name2
, p1.distance AS dist
FROM tmp.points p1, twin
WHERE p1.distance > twin.dist
AND NOT EXISTS ( SELECT * FROM tmp.points px
WHERE px.distance > twin.dist
AND px.distance < p1.distance
)
)
SELECT twin.name1, twin.name2
, (p1.distance * p2.value + p2.distance * p1.value) / (p1.distance+p2.distance) AS score
FROM twin
JOIN tmp.points p1 ON (p1.pname = twin.name1)
JOIN tmp.points p2 ON (p2.pname = twin.name2)
WHERE twin.zrank =2
;
If you actually want the point in between, there is a built-in way of doing that (but not an aggregate function):
SELECT center(box(x.mypoint,y.mypoint))
FROM ([get some neighbours by distance] order by value limit 1) x
,([get some neighbours by distance] order by value offset 1 limit 1) y;
If you want the mean distance:
SELECT avg(x.distance)
FROM ([get some neighbours by distance] order by value limit 2) as x
See geometrical function and aggregate functions in the manual.
Edit:
For the added example, the query could look like this:
SELECT (x.value * 4 + y.value) / 5 AS result
FROM ([get some neighbours by distance] order by value limit 1) x
,([get some neighbours by distance] order by value offset 1 limit 1) y;
I added missing () to get the result you expect!
Or, my last stab at it:
SELECT y.x, y.x[1], (y.x[1] * 4 + y.x[2]) / 5 AS result
FROM (
SELECT ARRAY(
SELECT value FROM tbl WHERE [some condition] ORDER BY value LIMIT 2
) x
) y
It would be so much easier, if you provided the full query and the table definitions.

Resources