How to get count by using UNION operator - oracle

i'm trying to get total count by using UNION operator but it gives wrong count.
select count(*) as companyRatings from (
select count(*) hrs from (
select distinct hrs from companyA
)
union
select count(*) financehrs from (
select distinct finance_hrs from companyB
)
union
select count(*) hrids from (
select regexp_substr(hr_id,'[^/]+',1,3) hrid from companyZ
)
union
select count(*) cities from (
select regexp_substr(city,'[^/]+',1,3) city from companyY
)
);
individual query's working fine but total count not matching.
individual results here: 12 19 3 6
present total count: 31
Actual total count:40.
so there is any alternate solution without UNION operator?

To add values you'd use +. UNION is to add data sets.
select
(select count(distinct hrs) from companyA)
+
(select count(distinct finance_hrs) from companyB)
+
(select count(regexp_substr(hr_id,'[^/]+',1,3)) from companyZ)
+
(select count(regexp_substr(city,'[^/]+',1,3)) from companyY)
as total
from dual;
But I agree with juergen d; you should not have separate tables per company in the first place.

Edit. Updated query using Sum
select sum(cnt) as companyRatings from
(
select count(*) as cnt from (select distinct hrs from companyA)
union all
select count(*) as cnt from (select distinct finance_hrs from companyB)
union all
select count(*) as cnt from (select regexp_substr(hr_id,'[^/]+',1,3) hrid from companyZ)
union all
select count(*) as cnt from (select regexp_substr(city,'[^/]+',1,3) city from companyY)
)
Previous answer:
Try this
SELECT (
SELECT count(*) hrs
FROM (
SELECT DISTINCT hrs
FROM companyA
)
)
+
(
SELECT count(*) financehrs
FROM (
SELECT DISTINCT finance_hrs
FROM companyB
)
)
+
(
SELECT count(*) hrids
FROM (
SELECT regexp_substr(hr_id, '[^/]+', 1, 3) hrid
FROM companyZ
)
)
+
(
SELECT count(*) cities
FROM (
SELECT regexp_substr(city, '[^/]+', 1, 3) city
FROM companyY
)
)
AS total_count
FROM dual

Related

oracle how to generate a list of period from a list of date

i would like to write a query to transform a list of date
list of date
15/02/2021
12/04/2021
28/07/2021
31/08/2021
to a list of period
start
end
15/02/2021
11/04/2021
12/04/2021
27/07/2021
28/07/2021
31/08/2021
Is it possible to do it in a oracle query ? Thanks for your help
Variant 1:
select *
from (
select
dt as dt_start,
case when lead(dt,2)over(order by dt) is not null
then lead(dt)over(order by dt)-1
else lead(dt)over(order by dt)
end as dt_end
from t
)
where dt_end is not null;
Full test with test data:
alter session set nls_date_format='dd/mm/yyyy';
with t(dt) as (
select to_date('15/02/2021','dd/mm/yyyy') from dual union all
select to_date('12/04/2021','dd/mm/yyyy') from dual union all
select to_date('28/07/2021','dd/mm/yyyy') from dual union all
select to_date('31/08/2021','dd/mm/yyyy') from dual
)
select *
from (
select
dt as dt_start,
case when lead(dt,2)over(order by dt) is not null
then lead(dt)over(order by dt)-1
else lead(dt)over(order by dt)
end as dt_end
from t
)
where dt_end is not null;
DT_START DT_END
---------- ----------
15/02/2021 11/04/2021
12/04/2021 27/07/2021
28/07/2021 31/08/2021
Variant 2: match_recognize (Oracle 12+):
select
dt_start,dt_end
from t
match_recognize(
order by dt
MEASURES
prev(dt) as dt_start,
nvl2(next(dt),e.dt-1,e.dt) as dt_end
all rows per match
PATTERN (e+)
DEFINE
e AS dt > prev(dt)
);
Full example with test data:
with t(dt) as (
select to_date('15/02/2021','dd/mm/yyyy') from dual union all
select to_date('12/04/2021','dd/mm/yyyy') from dual union all
select to_date('28/07/2021','dd/mm/yyyy') from dual union all
select to_date('31/08/2021','dd/mm/yyyy') from dual
)
select
dt_start,dt_end
from t
match_recognize(
order by dt
MEASURES
prev(dt) as dt_start,
nvl2(next(dt),e.dt-1,e.dt) as dt_end
all rows per match
PATTERN (e+)
DEFINE
e AS dt > prev(dt)
);
DT_START DT_END
---------- ----------
15/02/2021 11/04/2021
12/04/2021 27/07/2021
28/07/2021 31/08/2021
DBFiddle: https://dbfiddle.uk/?rdbms=oracle_18&fiddle=a9f9e256762c87e67ce3783f51f8d7c8
That's exactly what I'm looking for !
thanks !
I've just changed few things because I'm in Oracle 10g probably
with t as (
select to_date('15/02/2021','dd/mm/yyyy') dt from dual union all
select to_date('12/04/2021','dd/mm/yyyy') from dual union all
select to_date('28/07/2021','dd/mm/yyyy') from dual union all
select to_date('31/08/2021','dd/mm/yyyy') from dual
)
select *
from (
select
dt as dt_start,
case when lead(dt,2)over(order by dt) is not null
then lead(dt)over(order by dt)-1
else lead(dt)over(order by dt)
end as dt_end
from t
)
where dt_end is not null;

Oracle adding a subquery in a CTE

I have the following setup, which works fine and generates output as expected.
I'm trying to add the locations subquery into the CTE so my output will have a random location_id for each row.
The subquery is straight forward and should work but I am getting syntax errors when I try to place it into the 'data's CTE. I was hoping someone could help me out.
CREATE TABLE employees(
employee_id NUMBER(6),
emp_name VARCHAR2(30)
);
INSERT INTO employees(
employee_id,
emp_name
) VALUES
(1, 'John Doe');
INSERT INTO employees(
employee_id,
emp_name
) VALUES
(2, 'Jane Smith');
INSERT INTO employees(
employee_id,
emp_name
) VALUES
(3, 'Mike Jones');
CREATE TABLE locations AS
SELECT level AS location_id,
'Door ' || level AS location_name
FROM dual
CONNECT BY level <=
with rws as (
select level rn from dual connect by level <= 5 ),
data as ( select e.*,round (dbms_random.value(1,5)
) n from employees e)
select employee_id,
emp_name,
trunc (sysdate) + dbms_random.value (0, 5) AS random_date
from rws
join data d on rn <= n
order by employee_id;
-- trying to make this work
with rws as ( select level rn from dual connect by level <= 5 ),
data as ( select e.*, loc.location_id = (
select location_id
from locations order by dbms_random.value()
fetch first 1 row only
),
round (dbms_random.value(1,5)
) n from employees e )
select employee_id,
emp_name,
trunc (sysdate) + dbms_random.value (0, 5) AS random_date
from rws
join data d on rn <= n
order by employee_id;
You need to alias the subquery column expression, rather than trying to assign it to a [variable] name. So instead of this:
with rws as ( select level rn from dual connect by level <= 5 ),
data as ( select e.*, loc.location_id = (
select location_id
from locations order by dbms_random.value()
fetch first 1 row only
),
round (dbms_random.value(1,5)
) n from employees e )
you would do this:
with rws as (
select level rn
from dual
connect by level <= 5
),
data as (
select e.*,
(
select location_id
from locations
order by dbms_random.value()
fetch first 1 row only
) as location_id,
round (dbms_random.value(1,5)) as n
from employees e
)
db<>fiddle
But yes, you'll get the same location_id for each row, which probably isn't what you want.
There are probably better ways to avoid it (or to approach whatever you're actually trying to achieve) but one option is to force the subquery to be correlated by adding something like:
where location_id != -1 * e.employee_id
db<>fiddle
although that might be expensive. It's probably worth asking a new question about that specific aspect.
I am getting the same location_id for every employee_id, which I don't want either.
The subquery is in the wrong place then; move it to the main query, and correlate against both ID and n:
with rws as (
select level rn
from dual
connect by level <= 5
),
data as (
select e.*,
round (dbms_random.value(1,5)) as n
from employees e
)
select d.employee_id,
d.emp_name,
(
select location_id
from locations
where location_id != -1 * d.employee_id * d.n
order by dbms_random.value()
fetch first 1 row only
) as location_id,
trunc (sysdate) + dbms_random.value (0, 5) AS random_date
from rws r
join data d on r.rn <= d.n
order by d.employee_id;
db<>fiddle
Or move the location part to a new CTE, I suppose, with its own row number; and join that on one of your other generated values.

sql placeholder rows

I have an apex item P_USERS which can have a value higher than the amount of rows returning from the query below.
I have a classic report which has the following query:
select
first_name,
last_name
from accounts
where account_role = 'Author'
order by account_nr;
I want placeholder rows to be added to the query (first_name = null, last_name = null etc.), if the total rows from the query is lesser than the value in the apex_item P_USERS.
Any tips on how to achieve this? Maybe with a LEFT join?
If you have more result than the minima you defined, you must add the rest with union.
Here is what you could try to adapt to your case:
SELECT i,c FROM (
select rownum i, c from (
select 'a' c from dual union all select 'b' from dual union all select 'd' from dual union all select 'be' from dual
)), (Select Rownum r From dual Connect By Rownum <= 3)
where (i(+)= r)
union select i,c from (select rownum i, c from (
select 'a' c from dual union all select 'b' from dual union all select 'd' from dual union all select 'be' from dual
)) where i>3
You may try to use a LEFT JOIN.
First, create a list of number until the limit you want like suggested here:
-- let's say you want 300 records
Select Rownum r From dual Connect By Rownum <= 300
Then you can use this to left join and have empty records:
SELECT C, R FROM
( select rownum i, c from (select 'a' c from dual union all select 'b' from dual) )
, ( Select Rownum r From dual Connect By Rownum <= 300)
where i(+)= r order by r
The above gives you an ordered list starting with 'a', 'b', then null until the end.
So you could adapt it to your case so:
SELECT F,L FROM
( select rownum i, f, l from (
select first_name f, last_name l
from accounts where account_role = 'Author'
order by account_nr) )
, ( Select Rownum r From dual Connect By Rownum <= 300)
where i(+)= r

Pre-filter pivot data in Oracle

I'm trying to pivot a dataset. I need to be able to filter the values going into the pivot, but the pivot itself will be part of a larger SELECT statement.
So, for instance:
WITH TEST_DATA AS (
SELECT 1 AS ID, 'ALUM' AS DONOR_CODE, 1 AS PRIORITY FROM DUAL
UNION
SELECT 1 AS ID, 'STAFF' AS DONOR_CODE, 2 AS PRIORITY FROM DUAL
UNION
SELECT 8 AS ID, 'ALUM' AS DONOR_CODE, 1 AS PRIORITY FROM DUAL
UNION
SELECT 8 AS ID, 'ALMG' AS DONOR_CODE, 2 AS PRORITY FROM DUAL
UNION
SELECT 8 AS ID, 'STAF' AS DONOR_CODE, 3 AS PRORITY FROM DUAL
)
, PIVOT_DATA AS (
SELECT *
FROM TEST_DATA
PIVOT (MAX(DONOR_CODE) AS DONOR_CODE FOR PRIORITY IN (1,2,3)
)
)
SELECT * FROM PIVOT_DATA;
returns...
ID 1_DONOR_CODE 2_DONOR_CODE 3_DONOR_CODE
1 ALUM STAFF
8 ALUM ALMG STAF
What I need to be able to do is filter TEST_DATA before it gets pivoted. So if I only wanted to see IDs that had 'ALMG' how can I get a result set that looks like...
ID 1_DONOR_CODE 2_DONOR_CODE 3_DONOR_CODE
8 ALMG
Thanks.
You could subquery the TEST_DATA table:
PIVOT_DATA AS (
SELECT *
FROM
(
SELECT *
FROM TEST_DATA
WHERE DONOR_CODE = 'ALMG'
)
PIVOT (MAX(DONOR_CODE) AS DONOR_CODE FOR PRIORITY IN (1,2,3)
)
)
SELECT * FROM PIVOT_DATA;

Select Maximum record

Here is my Table EMP_EARN_DETAILS.
Emp_Ern_No is the primary key.
I need to get the amount for each emp_no for each earn_no where the emp_earn_no is the maximum.
The output should be as follows.
0004321 ERN001 2345 11
0004321 ERN002 345 10
0004321 ERN003 345 9
000507 ER-01 563 4
000732 ERN001 2345 12
000732 ERN002 9 13
000732 ERN003 678 8
Please help me with the query
You can aggregate by the fields you need and, at the same time, order by the EMP_EARN_NO value; this can be a solution, by analytic functions:
WITH TEST(emp_no, earn_no, amount, emp_earn_no) AS
(
SELECT '0004321' , 'ERN001' ,2345 ,11 FROM DUAL UNION ALL
SELECT '0004321' , 'ERN002' ,345 , 10 FROM DUAL UNION ALL
SELECT '0004321' , 'ERN003' ,345 ,9 FROM DUAL UNION ALL
SELECT '000507' , 'ER-01' ,56 ,1 FROM DUAL UNION ALL
SELECT '000507' , 'ER-01' ,563 , 2 FROM DUAL UNION ALL
SELECT '000507' , 'ER-01' ,563 ,3 FROM DUAL UNION ALL
SELECT '000507' , 'ER-01' ,563 ,4 FROM DUAL UNION ALL
SELECT '00732' , 'ERN001' ,123 ,7 FROM DUAL UNION ALL
SELECT '00732' , 'ERN001' ,2345 ,12 FROM DUAL UNION ALL
SELECT '00732' , 'ERN002' ,9 ,13 FROM DUAL UNION ALL
SELECT '00732' , 'ERN003' ,67 ,5 FROM DUAL UNION ALL
SELECT '00732' , 'ERN003' ,456 ,6 FROM DUAL UNION ALL
SELECT '00732' , 'ERN003' ,678 ,8 FROM DUAL
)
SELECT emp_no, earn_no, amount, emp_earn_no
FROM (
SELECT emp_no,
earn_no,
amount,
emp_earn_no, ROW_NUMBER() OVER ( PARTITION BY EMP_NO, EARN_NO ORDER BY emp_earn_no DESC) AS ROW_NUM
FROM TEST
)
WHERE ROW_NUM = 1
Give this a shot,
SELECT EMP_NO, SUM(AMOUNT)
FROM EMP_EARN_DETAILS
GROUP BY EMP_NO
HAVING EMP_EARN_NO = MAX(EMP_EARN_NO)
Try this query:
select emp_no, earn_no,
sum(amount) keep (dense_rank last order by emp_earn_no) as sum_amount
from emp_earn_details
group by emp_no, earn_no
First by following query , your conditions achieved :
select t.emp_no a ,t.earn_no b ,max(t.amount) c
from EMP_EARN_DETAILS t
group by t.emp_no,t.earn_no
order by t.emp_no
Only things that you must specify , in a same record with different EMP_EARN_NO. You have to specify in same record which must be in result.
So if you want maximum EMP_EARN_NO be in result you can use following query as final query (exactly your target in question):
select t.emp_no a ,t.earn_no b ,max(t.amount) c, max(t.emp_earn_no) emp_earn_no
from EMP_EARN_DETAILS t
group by t.emp_no,t.earn_no
order by t.emp_no
If you want minimum or others EMP_EARN_NO be in result you can above query replace max function by your conditions.

Resources