I have the following data
create table company (com_cd varchar2(100), act_id number(10));
insert into company values ('IBM',100);
insert into company values ('IBM',200);
insert into company values ('IBM',300);
insert into company values ('HP',50);
insert into company values ('HP',85);
Then I ran this query
select COM_CD,
REGEXP_SUBSTR(DUP_ACT_ID,'[^,]+',1,1) AS DUP_ACT_1,
REGEXP_SUBSTR(DUP_ACT_ID,'[^,]+',1,2) AS DUP_ACT_2,
REGEXP_SUBSTR(DUP_ACT_ID,'[^,]+',1,3) AS DUP_ACT_3
FROM (
SELECT COM_CD, LISTAGG(ACT_ID,',') DUP_ACT_ID
FROM COMPANY
GROUP BY COM_CD HAVING COUNT(*) > 1
);
COM_CD DUP_ACT_1 DUP_ACT_2 DUP_ACT_3
HP 50 85
IBM 100 200 300
It gave me the correct output but I won't know in a table with thousands of values whether there would be 1 or 10 duplicate ACT_ID. So I am looking for a solution where I don't use regexp_substr and instead get the output somehow based on number of values in the DUP_ACT_ID column. Any help is appreciated.
If you don't have to have a single row per com_cd, you could use something like the following:
SELECT com_cd,seq,act_id dup_act_id from
(select distinct com_cd,act_id,
dense_rank() over (partition by com_cd order by act_id) seq,
count(distinct act_id) over (partition by com_cd) cnt
from company)
where cnt > 1
order by com_cd,seq
COM_CD SEQ DUP_ACT_ID
HP 1 50
HP 2 85
IBM 1 100
IBM 2 200
IBM 3 300
You didn't mention what should happen if we see the same act_id multiple times within a com_cd, I assumed you would only want to see it once.
If you need a new column for each dup_act_id, you could pivot the above query:
WITH dup_accts as (SELECT com_cd,seq,act_id dup_act_id from
(select distinct com_cd,act_id,
dense_rank() over (partition by com_cd order by act_id) seq,
count(distinct act_id) over (partition by com_cd) cnt
from company)
where cnt > 1)
select * from dup_accts pivot(max(dup_act_id) for seq in (1 dup_act_1,2 dup_act_2,3 dup_act_3))
COM_CD DUP_ACT_1 DUP_ACT_2 DUP_ACT_3
IBM 100 200 300
HP 50 85
But in that case you'd still have to add a new section to the in() clause for each additional dup_act_id.
Related
I have an employee table as below. As you can see that second highest salary is 200
Incase the second highest salary is missing then there will be only one row as shown at last . In this case the query should fetch only 100
I have written query as but it is not working. Please help! Thanks
select salary "SecondHighestSalary" from(
(select id,salary,rank() over(order by salary desc) rnk
from employee2)
)a
where (rnk) in coalesce(2,1)
I have also tried the following but it is fetching 2 rows but i need only 1
It sounds like you'd want something like
with ranked_emp as (
select e.*,
rank() over (order by e.sal desc) rnk,
count(*) over () cnt
from employee2 e
)
select salary "SecondHighestSalary"
from ranked_emp
where (rnk = 2 and cnt > 1)
or (rnk = 1 and cnt = 1)
Note that I'm still using rank since you're using that in your approach and you don't specify how you want to handle ties. If there are two employees with the same top salary, rank will assign both a rnk of 1 and no employee would have a rnk of 2 so the query wouldn't return any data. dense_rank would ensure that there was at least one employee with a rnk of 2 if there were employees with at least 2 different salaries. If there are two employees with the same top salary, row_number would arbitrarily assign one the rnk of 2. The query I posted isn't trying to handle those duplicate situations because you haven't outlined exactly how you'd want it to behave in each instance.
If you are in Oracle 12.2 or higher, you can try:
select distinct id,
nvl
(
nth_value(salary, 2) from first
over(partition by id
order by salary desc
range between unbounded preceding and unbounded
following),
salary
) second_max_salary
from employee2
I have a table department with 3 column(department_name , department_id, department_block_number) so I want to fetch the department_block_number in which maximum number of department is located ? I have two department_block_number 303, 202 and each has 4 and 2 departments respectively? how can i do it?
select q1.department_block_number , max(c)
(select department_block_number , count(department_id)as c from department group by department_block_number)q1,
group by department_block_number ;
select q1.department_block_number , max(c)
(select department_block_number , count(department_id)as c from department group by department_block_number)q1,
group by department_block_number ;
Now i want to show only 303 as it is the block number with maximum departments in it but my query is showing both 303, 202 please help me . If you know some other way so that i can fetch the result so please help
In standard SQL if you are looking for one row, you would do:
select d.department_block_number, count(*)
from department d
group by d.department_block_number
order by count(*) desc
fetch first 1 row only;
Some databases spell fetch first 1 row only as limit 1 or select top (1) or in even more arcane ways.
In older versions of Oracle (fetch is supported in 12c+), you can do:
select department_block_number, cnt
from (select d.department_block_number, count(*) as cnt
from department d
group by d.department_block_number
order by count(*) desc
) d
where rownum = 1;
How can I reset the counter like below examples (I need to generate counter in the column named "Counter I need to generate"?
Looks like each value larger than 1 resets the counter, is that right?
If so, you could assign a group number first, based on the number of times a value > 1 occurs before the current row (including). So row 1 to 11 will be group 0, 12 and 13 will be group 1, and so on.
Then you can apply the row_number window function to generate the numbering partitioned by that group:
with VW_GROUPED as (
select
t.*,
(select count(*) from TheTable x where x.URN <= t.URN and x.GAPNOOFDAYS > 1) as GROUPNO
from
TheTable /* <- your table name here */ t)
select
g.URN,
g.CUSTOMER_ID,
g.GAPNOOFDAYS,
row_number() over (partition by GROUPNO order by URN) as "Counter I need to generate"
from
VW_GROUPED g
Here's an alternate example that generates the group in using analytic functions instead of a scalar subquery:
with grp as (
select t.*
, sum(case gapnoofdays when 1 then 0 else 1 end) over (partition by customer_id order by urn) grp
from your_table t
)
select grp.*
, row_number() over (partition by customer_id, grp order by urn) n
from grp;
So here is my problem: I need to get batches of rows (select statements) for a migration to another database (other then oracle).
Suggested solution: I take batches of rows (using rowid maybe?) example:
batch1: 0-10000,
batch2: 10000 - 20000,
batchn: 10000(n) - 10000(n+1)
So what should my query be?
batch1: select * from table_name where rownum >= 0 and rownum < 10000,
batch2: select * from table_name where rownum >= 10000 and rownum < 20000,
batch n: select * from table_name where rownum >= 10000*n and rownum < 10000*(n+1)
This does not work, (only the first select will work).
PS, I am pulling this data from a nodejs app, and thus I am sending in these batch queries in a for loop.
To illustrate my comment:
-- Between rows --
SELECT * FROM
( SELECT deptno, ename, sal, ROW_NUMBER() OVER (ORDER BY ename) Row_Num
FROM scott.emp
)
WHERE Row_Num BETWEEN 5 and 10
/
You may replace between operator with <= and >= if necessary.
Here's what I see in output:
DEPTNO ENAME SAL ROW_NUM
20 FORD 3000 5
30 JAMES 950 6
20 JONES 2975 7
10 KING 5000 8
30 MARTIN 1250 9
10 MILLER 1300 10
Using rownum is not a great idea, because there's no guarantee that the same rows will be assigned the same rownum values in different queries.
If the table has any combination of columns that uniquely identify a row, it is better to generate a ranking based on that and use that ranking to identify batches of rows. For example:
SELECT * FROM (
SELECT table.*, RANK() OVER (ORDER BY column1, column2) as my_rank
FROM table
)
WHERE my_rank >= 10000 AND my_rank < 20000
This will work with any range, and will be reproducible as long as the values in the columns used do not change and uniquely identify a row. (Actually, I think this would be usable even if they do not uniquely identify a row, as long as they work to break the rows into small enough batches.)
The downside is that MY_RANK will be included in the output. You can avoid that by explicitly listing the columns you do want to select; or it may be easier to filter it out when you are loading the data into the other database.
If you want to preserve the rowids, use the following SQL. This SQL took 4 minutes, 20 seconds to run against a 218 million row table on a 2 CPU server with 18 GB devoted to the DB.
CREATE TABLE rowids
AS
WITH
aset
AS
(SELECT ROWID AS row_id, row_number () OVER (ORDER BY ROWID) r
FROM amiadm.big_table)
SELECT *
FROM aset
WHERE MOD (r, 10000) = 0;
After creating this table, loop through it with the following:
BEGIN
FOR recs
IN ( SELECT row_id
, LAG (row_id) OVER (ORDER BY row_id) prev_row_id
, LEAD (row_id) OVER (ORDER BY row_id) next_row_id
FROM rowids
ORDER BY row_id)
LOOP
IF prev_row_id IS NULL
THEN
SELECT *
FROM big_table
WHERE ROWID <= recs.row_id;
ELSIF next_row_id IS NULL
THEN
SELECT *
FROM big_table
WHERE ROWID > row_id;
ELSE
SELECT *
FROM big_table
WHERE ROWID > prev_row_id
AND ROWID <= row_id;
END IF;
END LOOP;
END;
Table A
ID EmpNo Grade
--------------------
1 100 HIGH
2 105 LOW
3 100 MEDIUM
4 100 LOW
5 105 LOW
Query:
select *
from A
where EMPNO = 100
and rownum <= 2
order by ID desc
I tried this query to retrieve max and max-1 value; I need to compare the grade from max and max-1, if equals I need to set the flag as 'Y' or 'N' without using a cursor. Also I don't want to scan the entire record twice.
Please help me.
ROWNUM is applied before ORDER BY, so you need to nest the query like this:
select * from
(select * from A where EMPNO =100 order by ID desc)
where rownum<=2
That only performs one table scan (or it may use an index on EMPNO).
select *
from (
select id, emp_no, grade
, case
when lag(grade) over (order by emp_no desc) = grade
then 'Y'
else 'N'
end
as flag
, dense_rank() over( order by emp_no desc) as rank
from t
)
where rank <=2
;