ORACLE PL/SQL: ORDER BY CASE Performance - oracle

I currently have a server side paging query as such:
SELECT * FROM (SELECT a.*, rownum rnum FROM (
( /* Insert Query Here */ ) ) a
WHERE rownum <= ((page_number + 1) * page_size))
WHERE rnum >= (((page_number + 1) * page_size)) - (page_size - 1);
The problem, however, is trying to determine what the user is sorting on as this is tied to a gridview. Currently, I'm using:
ORDER BY
CASE sort_direction
WHEN 'ASC' THEN
CASE sort_column
WHEN 'PRIMARY_KEY' THEN
primary_key
ELSE key
END
END
ASC,
CASE sort_direction
WHEN 'DESC' THEN
CASE sort_column
WHEN 'PRIMARY_KEY' THEN
primary_key
ELSE key
END
END
DESC
I'm using this on every query I stick into the server side paging scheme. The problem is that when I have a grid that has quite a few fields, the performance degrades substantially. Is there any other way to do this? Do I need to simply set less fields to allow paging?

Use Dynamic SQL and build the ORDER BY at runtime. Oracle's SQL engine will see a simple ORDER BY.

I suspect the optimizer can't figure this out, and thus can't use indices. Check what EXPLAIN PLAN says.
Obvious solution is to have your app "evaluate" that case, and send a much simpler query. I think you'll find ORDER BY primary_key ASC to be much faster.

If the primary_key and key are numbers then a descending query is the same as an ascending multiplied by -1.
order by direction * CASE sort_column
WHEN 'PRIMARY_KEY' THEN primary_key
ELSE key END
In the above, direction is an integer. Pass in 1 or -1. This is a much simpler expression and should run faster.

Related

can I use `=` sign operator with sub-query instead of `IN`

I am just wondering if use = sign operator with sub-query instead of IN
Is it correct way ? and meet the oracle standard ?
Example
select column_name from my_table_1 where id = (select max(id) from my_table_2);
The Difference is related to the number of rows returned. If you have only one row returned from nested sql you may prefer both = or in operators. But if multiple rows returned from nested query, use in operator.
So, in your sql example you may prefer using any of the operators. Since, max functions returns only one row.
As you are fetching maximum value from subquery to compare with id, Both(= and IN )will work fine. But If you are trying to fetch more than one row then you have to use IN keyword.
If you have 1 result in sub query you are fine with using = sign, except when data type is wrong, for example , checking with same data type of dummy VARCHAR2(1)
select * from dual where 'X' = (select max(dual.dummy) from dual);
Is similar to using in (also same explain plain)
select * from dual where 'X' in (select max(dual.dummy) from dual);
But checking with different/wrong data type will result with exception ORA-01722 Invalid number
select * from dual where 1 =(select max(dual.dummy) from dual);

Using TOP in ORACLE SQL 9

Hello I'am very new to writing SQL and I am trying to find the appropriate way to use TOP in Oracle SQl 9:
My example:
select * from example e, test t
where e.id = t.id
and country = 'USA'
order by state ASC;
What I am trying to do is take the bottom 20 % of my query but I know you cannot use TOP. After researching I still have not found an applicable answer. I know you have to first order them but am unsure of how to then take the bottom 20%(which would be TOP since the order is ASC)
In general (like if you want the top or bottom 17.2% of the rows) you can use row_number() and count() (analytic functions) to get the result.
20% is easier - you are looking for the top (or bottom) quintile. For this, you can use the ntile() function, like so:
select [column_names]
from (
select e.*, t.*, ntile(5) over (order by state) as nt
from ..... etc
)
where nt = 1;
The subquery is your query. The column_names in the outer query are whatever you actually need; you could also use select * but that will show the ntile too (which will be 1 in all rows).
If sorting something in ASCending order gives us the top set then surely sorting in DESCending order can give us the bottom set.
This solution uses the function NTILE() to divide the records into five buckets. The first bucket is the set we want (because sorted in descending order). Sorting in ascending order and taking the fifth quintile would have the same outcome.
select * from (
select e.*
, t.*
, ntile(5) over (order by state desc) nt
from example e, test t
where e.id = t.id
and country = 'USA'
)
where nt = 1
order by state desc
/
You don't say what your sort criteria are, so I've guessed.

Mathematical operation between two columns in a comparison

We're using Oracle 10g XE and we found that the following query didn't return any value:
SELECT ref.referencia,
ref.descripcio,
stock_reservat,
stock,
stock_p_rebre,
(SELECT count(*)
FROM ref_numeros_serie num
WHERE num.empresa=ref.empresa AND
num.referencia=ref.referencia AND
num.diposit=1 AND
nvl(num.actiu,'N')='S') cnt_nums_serie
FROM emp_referencies ref,
ref_stk_dip_acu stk
WHERE ref.empresa=1 AND
ref.referencia='1B' AND
stk.empresa=ref.empresa AND
stk.referencia=ref.referencia AND
stk.diposit=1 AND
-- Relevant part
(stk.stock - stk.stock_reservat) <> (SELECT count(*)
FROM ref_numeros_serie num
WHERE num.empresa=ref.empresa AND
num.referencia=ref.referencia AND
num.diposit=1 AND
nvl(num.actiu,'N')='S')
-- End of relevant part
GROUP BY ref.empresa,
ref.referencia,
ref.descripcio,
stk.stock,
stk.stock_reservat,
stock,
stock_p_rebre
So the comparison between the subtraction and the subquery was false. But if we swapped the subquery and the subtraction like this:
SELECT ref.referencia,ref.descripcio,
stock_reservat,
stock,
stock_p_rebre,
(SELECT count(*)
FROM ref_numeros_serie num
WHERE num.empresa=ref.empresa AND
num.referencia=ref.referencia AND
num.diposit=1 AND
nvl(num.actiu,'N')='S') cnt_nums_serie
FROM emp_referencies ref,
ref_stk_dip_acu stk
WHERE ref.empresa=1 AND
ref.referencia='1B' AND
stk.empresa=ref.empresa AND
stk.referencia=ref.referencia AND
stk.diposit=1 AND
-- Relevant part
(SELECT count(*)
FROM ref_numeros_serie num
WHERE num.empresa=ref.empresa AND
num.referencia=ref.referencia AND
num.diposit=1 AND
nvl(num.actiu,'N')='S') <> (stk.stock - stk.stock_reservat)
-- End of relevant part
GROUP BY ref.empresa,
ref.referencia,
ref.descripcio,
stk.stock,
stk.stock_reservat,
stock,
stock_p_rebre
The comparison is true and we get results.
We have tried the following cases:
Removing the second part of the subtraction so the left part of the comparison is stk.stock: we get results, correct
Changing the second part of the subtraction by a number like this stk.stock-2: we get results, correct
Swapping the left and right part of the comparison, as explained above: we get results, correct
Changing the arithmetic operator like this (stk.stock+stk.stock_reservat) <> subquery: no results, incorrect
Changing the subquery by a number like this (stk.stock-stk.stock_reservat) <> 2: we get results, correct
We have tried these cases with a 10g non-XE database and it has the same behaviour. With a 11g, on the other hand, it works perfectly fine.
So our conclusion is that with oracle 10g a comparison between an arithmetic operation between two columns and a subquery works only if the subquery is on the left side and the operation on the right side. Has anybody had a similar problem and how did you work around/fix it?
EDIT: I want to add that this happens when the subquery result is 0, we haven't had issues with it otherwise, i.e. it behaves as expected.
Your query could be simplified by using a subquery factoring clause, as in:
WITH ns AS (SELECT r.empresa, r.referencia, count(*) as CNT_NUMS_SERIE
FROM ref_numeros_serie n
INNER JOIN emp_referencies r
r.empresa = n.empresa AMD
r.referencia = n.referencia
WHERE n.diposit = 1 AND
NVL(n.actiu, 'N') = 'S'
GROUP BY r.empresa,
r.referencia)
SELECT ref.referencia,
ref.descripcio,
stock_reservat,
stock,
stock_p_rebre,
ns.CNT_NUMS_SERIE
FROM emp_referencies ref
INNER JOIN ref_stk_dip_acu stk
ON stk.empresa = ref.empresa AND
stk.referencia = ref.referencia
INNER JOIN ns
ON ns.empresa = ref.empresa AND
ns.referencia = ref.referencia
WHERE ref.empresa = 1 AND
ref.referencia = '1B' AND
stk.diposit = 1 AND
-- Relevant part
(stk.stock - NVL(stk.stock_reservat, 0)) <> ns.CNT_NUMS_SERIE
-- End of relevant part
GROUP BY ref.empresa,
ref.referencia,
ref.descripcio,
stk.stock,
stk.stock_reservat,
stock,
stock_p_rebre
This lets you pull the common expression (SELECT COUNT(*)...) out and treat it as a separate table. I think it makes the query easier to read and might save some execution time.
Share and enjoy.

Oracle: MIN() Statement causes empty row returns

I'm having a small issue with sorting the data returned from a query, with the aim of getting the oldest updated value in dataset so that I can update only that record. Here's what I'm doing:
WHERE ROWNUM = 1 AND TABLE1.ID != V_IGNOREID
AND TABLE1.LASTREADTIME = (SELECT MIN(TABLE1.LASTREADTIME) FROM TABLE1)
ORDER BY TABLE1.LASTREADTIME DESC;
It makes no difference as to whether the ORDER BY statement is included or not. If I only use the ROWNUM and equality checks, I get data, but it alternates between only two rows, which is why I'm trying to use the LASTREADTIME data (so that I can modify more than these two rows). Anybody have any thoughts on this, or any suggestions as to how I can use the MIN function effectively?
Cheers
select * from (
-- your original select without rownum and with order by
)
WHERE ROWNUM = 1
EDIT some explanation
I think the order by clause is applied on the resultset after the where clause. So if the rownum = 1 is in the same select statement with the order by, then it will be applied first and the order by will order only 1 row, which will be the first row of the unordered resultset.

Efficient Alternative to Outer Join

The RIGHT JOIN on this query causes a TABLE ACCESS FULL on lims.operator. A regular join runs quickly, but of course, the samples 'WHERE authorised_by IS NULL' do not show up.
Is there a more efficient alternative to a RIGHT JOIN in this case?
SELECT full_name
FROM (SELECT operator_id AS authorised_by, full_name
FROM lims.operator)
RIGHT JOIN (SELECT sample_id, authorised_by
FROM lims.sample
WHERE sample_template_id = 200)
USING (authorised_by)
NOTE: All columns shown (except full_name) are indexed and the primary key of some table.
Since you're doing an outer join, it could easily be that it actually is more efficient to do a full table scan rather than use the index.
If you are convinced the index should be used, force it with a hint:
SELECT /*+ INDEX (lims.operator operator_index_name)*/ ...
then see what happens...
No need to nest queries. Try this:
select s.full_name
from lims.operator o, lims.sample s
where o.operator_id = s.authorised_by(+)
and s.sample_template_id = 200
I didn't write sql for oracle since a while, but i would write the query like this:
SELECT lims.operator.full_name
FROM lims.operator
RIGHT JOIN lims.sample
on lims.operator.operator_id = lims.sample.authorized_by
and sample_template_id = 200
Does this still perform that bad?

Resources