Use a sub-select in the PIVOT's FOR clause? - oracle

The standard PIVOT syntax uses a static FOR list:
SELECT *
FROM (
SELECT log_id, event_id, event_time
FROM patient_events
WHERE event_id IN (10,20,30,40,50)
) v
PIVOT (
max(event_time) event_time
FOR event_id IN( 10,20,30,40,50 )
)
Is there a way to make this dynamic?
I know the sub-select in the WHERE clause will work, but can I use one in the FOR?
SELECT *
FROM (
SELECT log_id, event_id, event_time
FROM patient_events
WHERE event_id IN ( sub-select to generate list of IDs )
) v
PIVOT (
max(event_time) event_time
FOR event_id IN( sub-select to generate list of IDs )
)

You can't in pure SQL, but I don't think quite because of the reason suggested - it's not that the IN clause needs to be ordered, it's that it has to be constant.
When given a query, the database needs to know the shape of the result set and the shape needs to be consistent across queries (assuming no other DDL operations have taken place that might affect it). For a PIVOT query, the shape of the result is defined by the IN clause - each entry becomes a column, with a data type corresponding to the aggregation clause.
Hypothetically if you were to allow a sub-select for the IN clause then you could alter the shape of the result set just by performing DML operations. Imagine your sub-select worked and got you a list of all event_ids known to the system - by inserting a new record into whatever drives that sub-select, your query returns a different number of columns even though no DDL has occurred.
Now we're stuck - any view built on that query is invalid because its shape wouldn't match that of the query, but Oracle couldn't know that it's invalid because none of the objects it depends on have been changed by DDL.
Depending on where you're consuming the result, dynamic SQL's your only option - either at the application level (build the IN list yourself) or via a ref cursor in a database function or procedure.

Interesting question.
On the face of it, it shouldn't work, since the list of values (which will become column names) must be ordered. This is not the case for an "IN" list in the WHERE clause. But perhaps it would work with an ORDER BY condition in the sub-SELECT?
Unfortunately, no. This is easy to test. Got the same error message with or without ORDER BY. (And the query works fine if the IN list is just 10, 20, 30, 40 - the actual department numbers from the DEPT table.) Using tables from the standard SCOTT schema.
SQL> select deptno from scott.dept;
DEPTNO
----------
10
20
30
40
4 rows selected.
SQL> select * from (
2 select sal, deptno
3 from scott.emp
4 )
5 pivot (sum(sal) as total_sal
6 for deptno in (10, 20, 30, 40))
7 ;
10_TOTAL_SAL 20_TOTAL_SAL 30_TOTAL_SAL 40_TOTAL_SAL
------------ ------------ ------------ ------------
8750 10875 9400
1 row selected.
SQL> select * from (
2 select sal, deptno
3 from scott.emp
4 )
5 pivot (sum(sal) as total_sal
6 for deptno in (select deptno from scott.dept order by deptno))
7 ;
for deptno in (select deptno from scott.dept order by deptno))
*
ERROR at line 6:
ORA-00936: missing expression

Related

(Oracle)Correlated subquery usage

Subquery query1 below works fine.
But when I put equi condition in -sort of- nested clause like query2, it shows error ORA-00904.
Is this wrong usage of correlated subquery or it is because of other reason?
--Query1: It shows expected result.
SELECT
O.ENAME
O,SAL
,(SELECT COUNT(*)
FROM SCOTT.EMP I
WHERE I.SAL>O.SAL --correlated to outer
) AS RESULT
from SCOTT.EMP O;
--Query2:ORA-00904: "O"."SAL": invalid identifier shows. How to modify to use correlated subquery?
SELECT
O.ENAME
O,SAL
,(
WITH TEMP AS
(
SELECT COUNT(*)
FROM SCOTT.EMP I
WHERE I.SAL>O.SAL --I have put equi condistion here
)
SELECT * FROM TEMP
) AS RESULT
from SCOTT.EMP O;
I believe the second option is a wrong use of correlated subqueries, not because of the comparison, but for the use of the with clause. I would like to remember that you should avoid correlated subqueries as much as possible.
The WITH clause, or subquery factoring clause, may be processed as an inline view or resolved as a temporary table. The advantage of the latter is that repeated references to the subquery may be more efficient as the data is easily retrieved from the temporary table, rather than being requeried by each reference.
In your third column of your second query you want to get the result from the inline view. The problem is that parsing of the inline view is done independently and therefore cannot have references to anything in the outer query.
SQL> create table emp ( ename varchar2(10) , sal number ) ;
Table created.
SQL> insert into emp values ( 'AAA' , 1000 ) ;
insert into emp values ( 'BBB' , 1000 ) ;
insert into emp values ( 'CCC' , 1000 ) ;
insert into emp values ( 'DDD' , 1000 ) ;
1 row created.
SQL> SQL>
1 row created.
SQL> SQL>
1 row created.
SQL> SQL>
1 row created.
SQL> select * from emp ;
ENAME SAL
---------- ----------
AAA 1000
BBB 1000
CCC 1000
DDD 1000
To write the query with inline view, the filter must be done in the outer query
SELECT
O.ENAME
O,SAL
,(
WITH TEMP AS
(
SELECT * FROM EMP
)
SELECT count(*) FROM TEMP t WHERE t.SAL>O.SAL
) AS RESULT
from EMP O;
O SAL RESULT
---------- ---------- ----------
AAA 1000 0
BBB 1000 0
CCC 1000 0
DDD 1000 0
It has been explained that the second query does not uses with correctly.
Let me suggest, however, that your query can be simpler and more efficiently phrased. For each employee, you want to count how many employees have a greater salary. Window functions are the way to go here:
select e.*, rank() over(order by salary desc) - 1 result
from scott.emp e

Okay so I am trying to a rownum into a variable but I need it to give me only one value, so 2 if it's the second number in the row

select rownum into v_rownum
from waitlist
where p_callnum=callnum
order by sysdate;
tried doing this but gives too many values.
and if I do p_snum=snum, it will keep returning 1. I need it to return 2 if it's #2 on the waitlist.
select rn into v_rownum
from (select callnum,
row_number() over (order by sysdate) rn
from waitlist)
where p_snum=snum;
Almost got it to work. Running into issues in the first select. I believe I might have to use v_count instead. Also Ordering by Sysdate even if a second apart will order it correctly.
SNU CALLNUM TIME
--- ---------- ---------
101 10125 11-DEC-18
103 10125 11-DEC-18
BTW time is = date which I entered people into waitlist using sysdate. So I suppose ordering by time could work.
create table waitlist(
snum varchar2(3),
callnum number(8),
time date,
constraint fk_waitlist_snum foreign key(snum) references students(snum),
constraint fk_waitlist_callnum foreign key(callnum) references schclasses(callnum),
primary key(snum,callnum)
);
is the waitlist table.
I used Scott's DEPT table to create your WAITLIST; department numbers represent CALLNUM column:
SQL> select * From waitlist;
CALLNUM WAITER
---------- --------------------
10 ACCOUNTING
20 RESEARCH
30 SALES
40 OPERATIONS
How to fetch data you need?
using analytic function (ROW_NUMBER) which orders values by CALLNUMs, you'll know the order
that query will be used as an inline view for the main query that returns number in the waitlist for any CALLNUM
Here's how:
SQL> select rn
2 from (select callnum,
3 row_number() over (order by callnum) rn
4 from waitlist
5 )
6 where callnum = 30;
RN
----------
3
SQL>
rownum in oracle is a generated column, it does not refer to any specific row, it is just the nth row in a set.
With a select into it can only return one row (hence the two many rows error) so rownum will always be 1.
Without more details about your table structure, and how you are uniquely identifying records it is hard to give assist you further with a solution.

Oracle, performance issue

I have a Insert Into ... SELECT statement. This statement consists of 3 selects join by UNION ALL CLAUSE.
Insert into
(Select... (1)
UNION ALL
Select... (2)
UNION ALL
Select... (3)
)
Table, which i'am inserting is in nologging mode, Parallel degree = 8, and using /*+ APPEND*/ clause.
Selects 1 and 2 are just simple selects over 1 table. I ran this 2 selects in pl/sql block like:
Select count(1) into x From
(Select (1) UNION ALL Select... (2))
It returned about 1.5 million records in 3 seconds. Then i put these selects into insert statement:
INSERT INTO TABLE_NAME
(Select (1) UNION ALL Select... (2))
And insert lasted same time as above.
Now, last (3) Select is From 1 table and few joins (including 1 left join).
When i ran this in Select Count(1) Into x It returned 80 000 records in about 3 minutes.
I expect that insert of 80 000 records would be no longer than time of select count(1) into, in the meantime insert into ... select has already half an hour of execution time.
1.5 milion rec - 3secs
80 000 rec - ...
Can Anyone enlight me, whats going on? Am i doing something wrong?

Best practice for pagination in Oracle?

Problem: I need write stored procedure(s) that will return result set of a single page of rows and the number of total rows.
Solution A: I create two stored procedures, one that returns a results set of a single page and another that returns a scalar -- total rows. The Explain Plan says the first sproc has a cost of 9 and the second has a cost of 3.
SELECT *
FROM ( SELECT ROW_NUMBER() OVER ( ORDER BY D.ID DESC ) AS RowNum, ...
) AS PageResult
WHERE RowNum >= #from
AND RowNum < #to
ORDER BY RowNum
SELECT COUNT(*)
FROM ...
Solution B: I put everything in a single sproc, by adding the same TotalRows number to every row in the result set. This solution feel hackish, but has a cost of 9 and only one sproc, so I'm inclined to use this solution.
SELECT *
FROM ( SELECT ROW_NUMBER() OVER ( ORDER BY D.ID DESC ) RowNum, COUNT(*) OVER () TotalRows,
WHERE RowNum >= from
AND RowNum < to
ORDER BY RowNum;
Is there a best-practice for pagination in Oracle? Which of the aforementioned solutions is most used in practice? Is any of them considered just plain wrong? Note that my DB is and will stay relatively small (less than 10GB).
I'm using Oracle 11g and the latest ODP.NET with VS2010 SP1 and Entity Framework 4.4. I need the final solution to work within the EF 4.4. I'm sure there are probably better methods out there for pagination in general, but I need them working with EF.
If you're already using analytics (ROW_NUMBER() OVER ...) then adding another analytic function on the same partitioning will add a negligible cost to the query.
On the other hand, there are many other ways to do pagination, one of them using rownum:
SELECT *
FROM (SELECT A.*, rownum rn
FROM (SELECT *
FROM your_table
ORDER BY col) A
WHERE rownum <= :Y)
WHERE rn >= :X
This method will be superior if you have an appropriate index on the ordering column. In this case, it might be more efficient to use two queries (one for the total number of rows, one for the result).
Both methods are appropriate but in general if you want both the number of rows and a pagination set then using analytics is more efficient because you only query the rows once.
In Oracle 12C you can use limit LIMIT and OFFSET for the pagination.
Example -
Suppose you have Table tab from which data needs to be fetched on the basis of DATE datatype column dt in descending order using pagination.
page_size:=5
select * from tab
order by dt desc
OFFSET nvl(page_no-1,1)*page_size ROWS FETCH NEXT page_size ROWS ONLY;
Explanation:
page_no=1
page_size=5
OFFSET 0 ROWS FETCH NEXT 5 ROWS ONLY - Fetch 1st 5 rows only
page_no=2
page_size=5
OFFSET 5 ROWS FETCH NEXT 5 ROWS ONLY - Fetch next 5 rows
and so on.
Refrence Pages -
https://dba-presents.com/index.php/databases/oracle/31-new-pagination-method-in-oracle-12c-offset-fetch
https://oracle-base.com/articles/12c/row-limiting-clause-for-top-n-queries-12cr1#paging
This may help:
SELECT * FROM
( SELECT deptno, ename, sal, ROW_NUMBER() OVER (ORDER BY ename) Row_Num FROM emp)
WHERE Row_Num BETWEEN 5 and 10;
A clean way to organize your SQL code could be trough WITH statement.
The reduced version implements also total number of results and total pages count.
For example
WITH SELECTION AS (
SELECT FIELDA, FIELDB, FIELDC FROM TABLE),
NUMBERED AS (
SELECT
ROW_NUMBER() OVER (ORDER BY FIELDA) RN,
SELECTION.*
FROM SELECTION)
SELECT
(SELECT COUNT(*) FROM NUMBERED) TOTAL_ROWS,
NUMBERED.*
FROM NUMBERED
WHERE
RN BETWEEN ((:page_size*:page_number)-:page_size+1) AND (:page_size*:page_number)
This code gives you a paged resultset with two more fields:
TOTAL_ROWS with the total rows of your full SELECTION
RN the row number of the record
It requires 2 parameter: :page_size and :page_number to slice your SELECTION
Reduced Version
Selection implements already ROW_NUMBER() field
WITH SELECTION AS (
SELECT
ROW_NUMBER() OVER (ORDER BY FIELDA) RN,
FIELDA,
FIELDB,
FIELDC
FROM TABLE)
SELECT
:page_number PAGE_NUMBER,
CEIL((SELECT COUNT(*) FROM SELECTION ) / :page_size) TOTAL_PAGES,
:page_size PAGE_SIZE,
(SELECT COUNT(*) FROM SELECTION ) TOTAL_ROWS,
SELECTION.*
FROM SELECTION
WHERE
RN BETWEEN ((:page_size*:page_number)-:page_size+1) AND (:page_size*:page_number)
Try this:
select * from ( select * from "table" order by "column" desc ) where ROWNUM > 0 and ROWNUM <= 5;
I also faced a similar issue. I tried all the above solutions and none gave me a better performance. I have a table with millions of records and I need to display them on screen in pages of 20. I have done the below to solve the issue.
Add a new column ROW_NUMBER in the table.
Make the column as primary key or add a unique index on it.
Use the population program (in my case, Informatica), to populate the column with rownum.
Fetch Records from the table using between statement. (SELECT * FROM TABLE WHERE ROW_NUMBER BETWEEN LOWER_RANGE AND UPPER_RANGE).
This method is effective if we need to do an unconditional pagination fetch on a huge table.
Sorry, this one works with sorting:
SELECT * FROM (SELECT ROWNUM rnum,a.* FROM (SELECT * FROM "tabla" order by "column" asc) a) WHERE rnum BETWEEN "firstrange" AND "lastrange";

select rows from third row to n th row in oracle [duplicate]

This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
Paging with Oracle
I try to select data starting from 11 row. and i used
select e_name from copy where rownum>10;
this will not display's anything..
please help me to select 11th row to 15th row in my table
You cannot use rownum like that, you need to wrap everything into a derived table:
select *
from (
select *,
rownum as rn
form your_table
order by some_column
)
where rn between 11 and 15
You should use an order by in the inner query because otherwise you will not get consistent results over time. Rows in a relational table do not have any ordering so the database is free to return the rows in any order it feels approriate.
Please read the manual for more details. The reason your query isn't working is documented there with examples.
http://docs.oracle.com/cd/E11882_01/server.112/e26088/pseudocolumns009.htm#i1006297
You have to use like
select e_name
from (select e_name,rownum rno from copy)
where rno > 10 and rno < 16
Sample Example
you could use analytic function row_number() as well. Please consider http://docs.oracle.com/cd/B19306_01/server.102/b14200/functions137.htm
First write the query which will select all the rows like this:-
select ename from employee
Now add a rownum column to your query, rownum will help you to identify the number of row in your query result
select rownum r,ename from employee
Now make your query as a sub query and apply the range on 'r' (rownum)
select * from (selecr rownum r, ename from employee) subq where subq.r between 11 and 15

Resources