Using TOP in ORACLE SQL 9 - oracle

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.

Related

How to add decode but also have count, group by and order by - not a group by expression

I'm rather new to Oracle. I'm trying to do a count of ports for a vendor/model. However, I only want ones with port_addr_status of 3 or 4. For some reason, I'm getting error
ORA-00979 not a group by expression
This is what I have so far. It works without the decode part, but I don't think the part with pi.port_addr.status in ('3','4') works without it. I'm open to working around that issue as well.
select
count(pi.port) as cnt, d.VENDOR, trim(d.model) as model,
decode(pi.PORT_ADDR_STATUS, '1', 'Unassigned', '2','Pending','3','In Service',
'4','Pending Disco', '5','Trouble', '6','Reserved',
'7','Reserved Capacity', pi.PORT_ADDR_STATUS)
from
table1 pi,
table2 d,
table3 c
where
pi.id = d.id and
pi.circuit_id = c.circuit_id
and pi.port_addr_status in ('3','4')
and (d.dslam_type_desc not in ('AGGREGATOR') or d.dslam_type_desc is null)
and d.DSLAM not like '%#%'
group by
d.VENDOR, d.model --, trim(d.model), pi.RACK, pi.SHELF, pi.SLOT, pi.PORT, pi.BROADBAND_CIRCUIT_ID, d.DSLAM,
order by
d.VENDOR asc, cnt desc
This is sample output:
1031 Adtran TA5000
10 Adtran TA1248V
3 Adtran TA1248
Since the decoded port type is not required in your output, just remove it from the select list. Group By should include everything in the select list except the aggregate functions. The below query should do the job:
select
count(pi.port) as cnt, d.VENDOR, trim(d.model) as model
from
table1 pi,
table2 d,
table3 c
where
pi.id = d.id and
pi.circuit_id = c.circuit_id
and pi.port_addr_status in ('3','4')
and (d.dslam_type_desc not in ('AGGREGATOR') or d.dslam_type_desc is null)
and d.DSLAM not like '%#%'
group by
d.VENDOR, trim(d.model)
order by
d.VENDOR asc, cnt desc
Note that the filter down to specific port types is achieved here:
and pi.port_addr_status in ('3','4')
in the where clause - the decode statement in your original query did nothing to solve your requirements and was the direct cause of the error. In a basic aggregate query like this, group by should have every item that is in the select list that is not an aggregate function.
You might use decode statement in the select list with an aggregation function preferably max as
max( decode(pi.PORT_ADDR_STATUS, '1', 'Unassigned', '2','Pending', .... )
The reason you're getting that error is:
when using a GROUP BY clause you are asking the DBMS to give you a single row corresponding to your GROUP BY list
in your case, you are asking for one row per (Vendor, Model) combination
since each of these rows represents possibly many "underlying" rows (say, e.g. 5 rows for Vendor=A and Model=B), the only other fields you can have in the select list must be aggregates (that's why the "count" field works, it counts the rows, i.e. it can take many row values and return a single value)
the DECODE function is not an aggregate function - it can't "collapse" multiple values into one value (like, say, MAX() can)
It looks to me as if your DECODE function returns strings, so I recommend you aggregate the strings with the LISTAGG function (so if your 5 rows have values of 'asdf', 'fdsa', 'qwer', 'rewq', and 'zxcv' the LISTAGG function would return a single string: 'asdf,fdsa,qwer,rewq,zxcv')
So you would replace your DECODE(...) call with, e.g. LISTAGG(DECODE(...), ',')
Reference for LISTAGG function: https://docs.oracle.com/cd/E11882_01/server.112/e41084/functions089.htm#SQLRF30030

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 stored procedure - gradual building of of out variable

I'm sorry for my strange title, but I don't know what exactly I'm looking for. The task is quite simple. I have the table of competitions. Another table groups. In every group there are several contestants. In the last table are stored the results of contestants. The task is to get the first three of the contestants of every group.
So I have to loop through the groups, get the first three contestants (according to achieved points) of every group and append them into some variable.
Here is the pseudocode:
CREATE OR REPLACE PROCEDURE get_first_three_of_all(contestants OUT SOME_TYPE) AS
CURSOR groups SELECT...
BEGIN
FOR group IN groups LOOP
APPEND(contestants, get_first_three_of_one_group(group.id))
END LOOP;
END;
I have no idea, how to solve this task. I even don't know what should I look for. Would you be so kind and help me, please? Thanks.
Edited: simplified structure of my tables:
Competition: competition_id
Contestant: contestant_id
GroupContestant: contestant_group_id, competition_d, group_number, contestant_id
Result: contestant_group_id, juror, points
Select to get data of one group (group number YYY) is here:
SELECT * FROM (
SELECT res.contestant_group_id, SUM(res.points) AS points
FROM Result res
WHERE res.couple_group_id IN (SELECT couple_group_id
FROM GroupContestant
WHERE competition_id = XXX
AND group_number = YYY)
GROUP BY res.contestant_group_id
ORDER BY points DESC
)
WHERE ROWNUM <= 3;
Analytic functions to the rescue. To select top 3 results for each group, each competition:
SELECT * FROM (
SELECT grp.competition_id, grp.group_number, res.contestant_group_id, res.points,
row_number() over (partition by grp.competition_id, grp.group_number
order by res.points desc) rn
FROM (SELECT contestant_group_id, SUM(points) AS points
FROM Result
GROUP BY contestant_group_id) res
JOIN GroupContestant grp ON (grp.contestant_group_id = res.contestant_group_id)
)
WHERE rn <= 3;
Pay attention to how you resolve ties (consider using rank or dense_rank instead of row_number).
You can use RANK() analytic function to achieve the goal:
select *
from (select group_num,
points,
rank() over(partition by group_num order by points desc) rank
from results
inner join group_contestant
using (contestant_group_id))
where rank <= 3
order by group_num, points desc;
Here is SQLFiddle to play with.

Vertica subquery with limit

I found problem in writing subquery with limit 1 to get the top record.
Here is my problem example.
Table Master(id,set)
Table Detail(id,set,code)
i am trying to get the latest code for each set in Master table.
Following is the query which i tried but got an error that limit 1 is not supported for correlated subqueries and it should contain GROUP By clause.
select id,set,(select code from detail where set=master.set order by id desc limit 1) from master;
And the result wolud be like
please help me if this is wrong way, i am new to this vertica database.
thnk you.
I'm not sure about that particular error, but you can use the rank() analytic function to produce results like this:
select id, set, code from (
select M.id, D.set, D.code, rank() over (partition by D.set order by D.id desc) as rank
from detail as D
right outer join master as M
on D.set = M.set) as ranks
where rank = 1
order by id;
The inner subquery uses the rank() function to assign a rank to each row within a set. The outer query just picks out rows with rank 1.

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.

Resources