select query with if in oracle - 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.

Related

I'd like to return a single row in a query that joins two tables through a one to many relationship

This is an oracle system. I have a client table (one) and an account table (many). Frankly, I really just want to query the client table but due to poor design, there is a data element that I need for the client table that is only present on the account table - it will be the same value for all accounts for that client so I really just need to return one account row but I'm having problems accomplishing this. Here is the query I tried:
Select
c.client_num,
c.client_name,
a.agency_value
from client c
inner join account a on c.client_num = a.client_num
where a.account_num in (select a2.account_num from account a2 where rownum = 1)
You need to explicitly designate how to pick one record out of many. Here's one method - use MAX
Select
c.client_num,
c.client_name,
(SELECT
MAX(a.agency_value) agency_value
FROM account a
where c.client_num = a.client_num
-- not sure if this line is required - if not please remove
and a.account_num in (select a2.account_num from account a2 where rownum = 1)
) agency_value
from client c
Keep in mind that by implementing this you are "cementing" your bad table design.
Are you absolutely certain that there is only ever one agency_value? use query to find any clients that have more than one agency:
SELECT
a.client_num,
COUNT(DISTINCT a.agency_value) CountOfAgencyValues,
MAX(a.agency_value) max_agency_value,
MIN(a.agency_value) max_agency_value
FROM account a
GROUP BY a.client_num
HAVING COUNT(DISTINCT a.agency_value) > 1
With your input and doing some playing around on my own, here is the code that ultimately addressed my need:
select
c. client_num,
(select a.agency_value
from account a
where a.client_num = c.client_num
and rownum = 1)
from client c

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

How to use GROUP BY clause with COUNT(*)

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.

SQL: Recursion With a Join Confusion

I'll start by laying out what I'm trying to do and then I'll list the code I've made so far.
I'm coding in Oracle PL/SQL on the Application Express Platform
I have two tables: USERS and LEADS.
Leads Columns: LEADID, COMPANYNAME, CONTACTNAME, OWNER
Users Columns: EMAIL, SUPER, ROLE
Foreign Keys:
OWNER is a foreign key that refers to EMAIL in USERS
SUPER is a foreign key that refers to EMAIL in USERS
SUPER is the supervisor of a given person. ROLE is their position in the company
There are about 5 levels. 'PEON','MGR','DIR','SRDIR','VP'
Peons are the only people with leads assigned to them.
I'm trying to generate a report that returns rows containing the following
SUBORDINATE, NUMLEADS
Subordinate is anyone directly under the user using the application. I have code for that
select U.EMAIL as Subordinate
from USERS U
WHERE lower(v('APP_USER')) = U.SUPER
Numleads is all the leads created by peons under the subordinate's organization. I currently have code to list the number of peons under the current user
select count(*)
from USERS U2
where U2.ROLE = 'PEON'
start with lower(v('APP_USER')) = U2.EMAIL
connect by NOCYCLE prior U2.email = U2.super
I'm part of the way there, but I'm confused how to reference the result of a query in a recursive sequence. I know I need to query all PEONS under the subordinates of the current user, JOIN them with all leads they're associated with, and then count the number of leads. But i'm not sure how to order that in SQL.
Your help is much appreciated
EDIT: Answer figured out thanks to JBrooks
select U.EMAIL as Sub, count(*) as CreatedAllTime
from USERS U
left join USERS UPEON
on UPEON.EMAIL in
(
select UPEON2.EMAIL
from USERS UPEON2
where UPEON2.ROLE = 'PEON'
start with U.EMAIL = UPEON2.EMAIL
connect by NOCYCLE prior UPEON2.email = UPEON2.super
)
left join LEADS L
on UPEON.EMAIL = L.OWNER
where U.EMAIL in
(
select U2.EMAIL as Sub
from USERS U2
WHERE lower(v('APP_USER')) = U2.SUPER
)
group by U.EMAIL
select U2.Email as Subordinate,
count(*) as NumLeads
from USERS U2
left join LEADS l
on U2.Email = l.Owner
where U2.ROLE = 'PEON'
and lower(v('APP_USER')) in
(select EMAIL
from USERS S
START WITH lower(v('APP_USER')) = lower(S.SUPPER)
CONNECT BY PRIOR EMAIL = SUPPER)
group by U2.Email
order by U2.Email

SQL Query Performance with count

I have 2 tables, COMPANY and EMPLOYEE.
COMPANY_ID is the primary key of the COMPANY table and foreign key for EMPLOYEE table. The COMPANY_ID is a 10 digit number. We are generate a 3 number combination and query the database.
The select statement has regex to bulk load the company based on COMPANY_ID. The query is executed multiple times with different patterns
i.e.
regexp_like(COMPANY_ID, '^(000|001|002|003|004|005|006|007|008|009)') .
Existing query looks something like this -
select *
from COMPANY company
where regexp_like(company.COMPANY_ID, '^(000|001|002|003|004|005|006|007|008|009)')
The new requirement is to retrieve the company information along with the employee count. For example if a company has 10 employees, then the query should return all the columns of the COMPANY table, along with employee count i.e. 10
This is the select statement that I came up with -
select
nvl(count_table.cont_count, 0), company.*
from
COMPANY company,
(select company.COMPANY_ID, count(company.COMPANY_ID) as cont_count
from COMPANY company, EMPLOYEE employee
where regexp_like(company.COMPANY_ID, '^(000|001|002|003|004|005|006|007|008|009)')
and company.CONTACT_ID = employee.CONTACT_ID
group by (company.COMPANY_ID)) count_table
where
regexp_like(company.COMPANY_ID, '^(000|001|002|003|004|005|006|007|008|009)')
and count_table.COMPANY_ID(+)= company.COMPANY_ID
Above query works, but it takes double the time compared to the previous statement. Is there a better way to retrieve the employee count?
Note: Oracle database is in use.
You don't need to execute that expensive REGEXP_LIKE twice:
select nvl(count_table.cont_count,0),company.*
from COMPANY company
,( select employee.COMPANY_ID, count(employee.COMPANY_ID) as cont_count
from EMPLOYEE employee
group by (employee.COMPANY_ID)
) count_table
where regexp_like(company.COMPANY_ID, '^(000|001|002|003|004|005|006|007|008|009)')
and count_table.COMPANY_ID(+)= company.COMPANY_ID
Or you could use a scalar subquery:
select company.*
, (select count(*)
from employee e
where e.company_id = c.company_id
)
from COMPANY c
where regexp_like(c.COMPANY_ID, '^(000|001|002|003|004|005|006|007|008|009)')
And personally I would ditch the slow REGEXP_LIKE for something like:
where substr(c.company_id,1,3) between '000' and '009'
The derived table does not add value, thus I would get rid of it and use a scalar query (because I do not know all of your columns in the company table to properly do a group by):
select c.*,
nvl(
(select count(1)
from employee emp
where emp.company_id = c.company_id
),0) employee_count
from company c
where regexp_like(c.company_id, '^(000|001|002|003|004|005|006|007|008|009)')
Also, if performance is still an issue, I would consider modifying your where statement to not use a regexp.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Addendum
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
I see that the question explicitly identifies that the employee table has company_id as a foreign key. Since this is clarified, I am removing this statement:
The data model for these tables is not intuitive (would you not have
company_id as a foreign key in the employees table?).

Resources