How to use GROUP BY clause with COUNT(*) - oracle

I have two tables on Oracle database, one is named departments_table and the other is locations_table. The departments.table has dep_id, dep_name, location_id, staff_id, employer_id. The locations table consists of location_id, city_id, streetname_id and postcode_id. How do I calculate the number of departments that each location has?
This is the code below is what I have tried to replicate but have been unsuccessful. The error message below that is what shows once the code has submitted.
SELECT dep_name, location_id,
COUNT(*)
FROM departments_table
WHERE location_id => 1
GROUP BY dep_name;
The results of this is an error, " not a single group function "

If you want to count how many departments are in each location, then you must group by location, not by department name, right? Let's start with that.
Then, you don't need ANYTHING about the individual departments in the output of the query, do you? You just need the location id and the count of departments.
select location_id, count(*) as cnt
from departments_table
group by location_id
;
This does most of the work. You may want to add the location name (city, address, etc.), which is/are stored elsewhere - in the locations_table. So you will need a join. And there may be locations in that table that are not, in fact, the location of any department (their id doesn't appear in the departments_table at all). If so, you would need an OUTER join. Also for those departments you probably want to show a count of 0 (rather than null) - you can "fix" that with the nvl() function. So you will end up with something like
select l.*, nvl(g.cnt, 0) as department_count
from locations_table l
left outer join
( select location_id, count(*) as cnt
from departments_table
group by location_id
) g
on l.location_id = g.location_id
;

SELECT l.location_id, l.city, COUNT(d.DEPARTMENT_ID)
FROM OEHR_LOCATIONS l, OEHR_DEPARTMENTS d WHERE l.location_id = d.location_id
GROUP BY l.location_id, l.city ORDER BY l.city;
This method works. I created aliases and made minor changes. OEHR stands for the table names so ignore that.

Related

Query to count values from different tables

From a database composed of two tables, EMP and DEP, I need to list all locations (DEPT.LOC ),how many departments are in this location and how many employees (EMP.EMPNO) are in this departments.
Create a view listing all locations (Loc) along with the number of departments in that location and the number of employees employed in those departments.
I wrote the below query but I get the error: missing expression.
SELECT D1.LOC, COUNT(D1.DEPTNO),COUNT(SELECT * FROM EMP E,DEPT D WHERE E.DEPTNO = D.DEPTNO AND D.LOC = D1.LOC ) FROM DEPT D1 GROUP BY D1.LOC
The way you tried is almost right (although it is not the most natural way to do it) - and it can be salvaged.
Your inner select returns several rows per department. COUNT(...) applies to a single expression - it is over a column, but it counts individual expressions, not multi-row inputs.
Instead, in the inner select you should select count(*), not *; and then the outer aggregate should be sum(). Something like this:
SELECT D1.LOC, COUNT(D1.DEPTNO),
sum((SELECT count(*) FROM EMP E, DEPT D
WHERE E.DEPTNO = D.DEPTNO AND D.LOC = D1.LOC ))
FROM DEPT D1 GROUP BY D1.LOC
Note also the two sets of parentheses used with the sum() function. (This is, in fact, what caused the immediate error you reported.) You are summing numbers, and the outer parentheses are part of the function call. But the numbers you are summing are the result of a "scalar subquery" (a subquery that returns a single row and single column, which happens to be a numeric value over which you can sum; in this case, that numeric value is a count, of employees by department). A subquery, scalar or otherwise, must always also be enclosed in its own set of parentheses, even if it appears within a function call, or other kinds of parentheses.
Now: the more natural way to do the same thing is with a join; not sure if you are far enough in your intro course to have studied this though.
select d.loc, count(distinct d.deptno) as departments, count(e.empno) as employees
from dept d left outer join emp e on d.deptno = e.deptno
group by d.loc
;
The outer join is needed so that departments are counted (for each location) even if they don't have any employees. In the SCOTT schema there is, in fact, a department with no employees - and it is the only department in its location. If you used an inner join above, that location wouldn't even appear in the output.
NOTE - the homework problem asks you to create a view, not a SELECT query. That part is trivial - I assume you can do it by yourself.

Oracle select rows from a query which are not exist in another query

Let me explain the question.
I have two tables, which have 3 columns with same data tpyes. The 3 columns create a key/ID if you like, but the name of the columns are different in the tables.
Now I am creating queries with these 3 columns for both tables. I've managed to independently get these results
For example:
SELECT ID, FirstColumn, sum(SecondColumn)
FROM (SELECT ABC||DEF||GHI AS ID, FirstTable.*
FROM FirstTable
WHERE ThirdColumn = *1st condition*)
GROUP BY ID, FirstColumn
;
SELECT ID, SomeColumn, sum(AnotherColumn)
FROM (SELECT JKM||OPQ||RST AS ID, SecondTable.*
FROM SecondTable
WHERE AlsoSomeColumn = *2nd condition*)
GROUP BY ID, SomeColumn
;
So I make a very similar queries for two different tables. I know the results have a certain number of same rows with the ID attribute, the one I've just created in the queries. I need to check which rows in the result are not in the other query's result and vice versa.
Do I have to make temporary tables or views from the queries? Maybe join the two tables in a specific way and only run one query on them?
As a beginner I don't have any experience how to use results as an input for the next query. I'm interested what is the cleanest, most elegant way to do this.
No, you most probably don't need any "temporary" tables. WITH factoring clause would help.
Here's an example:
with
first_query as
(select id, first_column, ...
from (select ABC||DEF||GHI as id, ...)
),
second_query as
(select id, some_column, ...
from (select JKM||OPQ||RST as id, ...)
)
select id from first_query
minus
select id from second_query;
For another result you'd just switch the tables, e.g.
with ... <the same as above>
select id from second_query
minus
select id from first_query

Oracle 11g - Selecting multiple records from a table column

I was just wondering how you select multiple records from a table column. Please see below the query.
SELECT DISTINCT DEPARTMENT_NAME, CITY, COUNTRY_NAME
FROM OEHR_DEPARTMENTS
NATURAL JOIN OEHR_EMPLOYEES
NATURAL JOIN OEHR_LOCATIONS
NATURAL JOIN OEHR_COUNTRIES
WHERE JOB_ID = 'SA_MAN' AND JOB_ID = 'SA_REP'
;
Basically, I want to be able to select records from the table column I have, however when you use AND it only displays SA_MAN and not SA_REP. I have also tried to use OR and it displays no rows selected. How would I actually be able to select both Job ID's without it just displaying one or the other.
Sorry this may sound like a stupid question (and probably not worded right), but I am pretty new to Oracle 11g SQL.
For your own comfort while debugging, I suggest you to use inner joins instead of natual joins.
That where clause is confusing, if not utterly wrong, because you don't make clear which tables' JOB_ID should be filtered. Use inner joins, give aliases to tables, and refer to those aliases in the where clause.
select distinct DEPARTMENT_NAME, CITY, COUNTRY_NAME
from OEHR_DEPARTMENTS t1
join OEHR_EMPLOYEES t2
on ...
join OEHR_LOCATIONS t3
on ...
join OEHR_COUNTRIES t4
on ...
where tn.JOB_ID = 'SA_MAN' AND tm.JOB_ID = 'SA_REP'
After rephrasing your query somehow like this, you'll have a clearer view on the logical operator you'll have to use in the where clause, which I bet will be an OR.
EDIT (after more details were given)
To list the departments that employ staff with both 'SA_MAN' and 'SA_REP' job_id, you have to join the departments table with the employees twice, once with the filter job_id='SA_MAN' and once with job_id='SA_REP'
select distinct DEPARTMENT_NAME, CITY, COUNTRY_NAME
from OEHR_DEPARTMENTS t1
join OEHR_EMPLOYEES t2
on t1.department_id = t2.department_id --guessing column names
join OEHR_EMPLOYEES t3
on t1.department_id = t3.department_id --guessing column names
join OEHR_LOCATIONS t4
on t1.location_id = t4.location_id --guessing column names
join OEHR_COUNTRIES t5
on t4.country_id = t5.country_id --guessing column names
where t2.job_id = 'SA_MAN' and t3.job_id = 'SA_REP'
order by 1, 2, 3

select query with if in oracle

I need help! For example, there are four tables: cars, users, departments and join_user_department. Last table used for M: N relation between tables user and department because some users have limited access. I need to get the number of cars in departments where user have access. The table “cars” has a column department_id. If the table join_user_department doesn’t have any record by user_id this means that he have access to all departments and select query must be without any condition. I need do something like this:
declare
DEP_NUM number;--count of departments where user have access
CARS_COUNT number;--count of cars
BEGIN
SELECT COUNT (*) into DEP_NUM from join_user_departments where user_id=?;
SELECT COUNT(*) into CARS_COUNT FROM cars where
IF(num!=0)—it meant that user access is limited
THEN department_id IN (select dep_id from join_user_departments where user_id=?);
A user either has access to all cars (I'm assuming all cars are tied to a department, and the user has access to all departments) or the user has limited access. You can use a UNION ALL to bring these two groups together, and group by user to do a final count. I've cross joined the users with unlimited access to the cars table to associate them with all cars:
(UPDATED to also count the departments)
select user_id,
count(distinct department_id) as dept_count,
count(distinct car_id) as car_count,
from (
select ud.user_id, ud.department_id, c.car_id
from user_departments ud
join cars c on c.department_id = ud.department_id
UNION ALL
select u.user_id, v.department_id, v.car_id
from user u
cross join (
select d.department_id, c.car_id
from department d
join cars c on c.department_id = d.department_id
) v
where not exists (
select 1 from user_departments ud
where ud.user_id = u.user_id
)
)
group by user_id
A UNION ALL is more efficient that a UNION; a UNION looks for records that fall into both groups and throws out duplicates. Since each user falls into one bucket or another, UNION ALL should do the trick (doing a distinct count in the outer query also rules out duplicates).
"If the table join_user_department doesn’t have any record by user_id
this means that he have access to all departments"
This seems like very bad practice. Essentially you are using the absence of records to simulate the presence of records. Very messy. What happens if there is a User who has no access to a Car from any Department? Perhaps the current business logic doesn't allow this, but you have a "data model" which won't allow to implement such a scenario without changing your application's logic.

Rownum in the join condition

Recently I fixed the some bug: there was rownum in the join condition.
Something like this: left join t1 on t1.id=t2.id and rownum<2. So it was supposed to return only one row regardless of the “left join”.
When I looked further into this, I realized that I don’t understand how Oracle evaluates rownum in the "left join" condition.
Let’s create two sampe tables: master and detail.
create table MASTER
(
ID NUMBER not null,
NAME VARCHAR2(100)
)
;
alter table MASTER
add constraint PK_MASTER primary key (ID);
prompt Creating DETAIL...
create table DETAIL
(
ID NUMBER not null,
REF_MASTER_ID NUMBER,
NAME VARCHAR2(100)
)
;
alter table DETAIL
add constraint PK_DETAIL primary key (ID);
alter table DETAIL
add constraint FK_DETAIL_MASTER foreign key (REF_MASTER_ID)
references MASTER (ID);
prompt Disabling foreign key constraints for DETAIL...
alter table DETAIL disable constraint FK_DETAIL_MASTER;
prompt Loading MASTER...
insert into MASTER (ID, NAME)
values (1, 'First');
insert into MASTER (ID, NAME)
values (2, 'Second');
commit;
prompt 2 records loaded
prompt Loading DETAIL...
insert into DETAIL (ID, REF_MASTER_ID, NAME)
values (1, 1, 'REF_FIRST1');
insert into DETAIL (ID, REF_MASTER_ID, NAME)
values (2, 1, 'REF_FIRST2');
insert into DETAIL (ID, REF_MASTER_ID, NAME)
values (3, 1, 'REF_FIRST3');
commit;
prompt 3 records loaded
prompt Enabling foreign key constraints for DETAIL...
alter table DETAIL enable constraint FK_DETAIL_MASTER;
set feedback on
set define on
prompt Done.
Then we have this query :
select * from master t
left join detail d on d.ref_master_id=t.id
The result set is predictable: we have all the rows from the master table and 3 rows from the detail table that matched this condition d.ref_master_id=t.id.
Result Set
Then I added “rownum=1” to the join condition and the result was the same
select * from master t
left join detail d on d.ref_master_id=t.id and rownum=1
The most interesting thing is that I set “rownum<-666” and got the same result again!
select * from master t
left join detail d on d.ref_master_id=t.id and rownum<-666.
Due to the result set we can say that this condition was evaluated as “True” for 3 rows in the detail table. But if I use “inner join” everything goes as supposed to be.
select * from master t
join detail d on d.ref_master_id=t.id and rownum<-666.
This query doesn’t return any row,because I can't imagine rownum to be less then -666 :-)
Moreover, if I use oracle syntax for outer join, using “(+)” everything goes well too.
select * from master m ,detail t
where m.id=t.ref_master_id(+) and rownum<-666.
This query doesn’t return any row too.
Can anyone tell me, what I misunderstand with outer join and rownum?
ROWNUM is a pseudo-attribute of result sets, not of base tables. ROWNUM is defined after rows are selected, but before they're sorted by an ORDER BY clause.
edit: I was mistaken in my previous writeup of ROWNUM, so here's new information:
You can use ROWNUM in a limited way in the WHERE clause, for testing if it's less than a positive integer only. See ROWNUM Pseudocolumn for more details.
SELECT ... WHERE ROWNUM < 10
It's not clear what value ROWNUM has in the context of a JOIN clause, so the results may be undefined. There seems to be some special-case handling of expressions with ROWNUM, for instance WHERE ROWNUM > 10 always returns false. I don't know how ROWNUM<-666 works in your JOIN clause, but it's not meaningful so I would not recommend using it.
In any case, this doesn't help you to fetch the first detail row for each given master row.
To solve this you can use analytic functions and PARTITION, and combine it with Common Table Expressions so you can access the row-number column in a further WHERE condition.
WITH numbered_cte AS (
SELECT *, ROW_NUMBER() OVER (PARTITION BY t.id ORDER BY d.something) AS rn
FROM master t LEFT OUTER JOIN detail d ON d.ref_master_id = t.id
)
SELECT *
FROM numbered_cte
WHERE rn = 1;
if you want to get the first three values from the join condition change the select statement like this.
select *
from (select *
from master t left join detail d on d.ref_master_id=t.id)
where rownum<3;
You will get the required output. Take care on unambigiously defined column names when using *
Let me give an absolute answer which u can run directly with out making any changes to the code.
select *
from (select t.id,t.name,d.id,d.ref_master_id,d.name
from master t left join detail d on d.ref_master_id=t.id)
where rownum<3;
A ROWNUM filter doesn't make any sense in a join, but it isn't being rejected as invalid.
The explain plan will either include the ROWNUM filter or exclude it. If it includes it, it will apply the filter to the detail table after applying the other join condition(s). So if you put in ROWNUM=100 (which will never be satisfied) all the detail rows are excluded and then the outer join kicks in.
If you put in ROWNUM=1 it seems to drop the filter.
And if you query
with
a as (select rownum a_val from dual connect by level < 10),
b as (select rownum*2 b_val from dual connect by level < 10)
select * from a left join b on a_val < b_val and rownum in (1,3);
you get something totally weird.
It probably should be rejected as an error, so expect nonsensical things to happen

Resources