relational algebra specific operations - relational-algebra

I have these tables
Employee(ssn, name, sex, address, salary, bdate, dno, superssn)
fk:superssn is ssn in Employee
fk:dno is dnumber in Department
Department(dnumber, dname, mgrssn, mgrstartdate)
fk:mgrssn is ssn in Employee
Dept_locations(dnumber, dlocation)
fk:dnumber is dnumber in Department
Project(pnumber, pname, plocation, dnum)
fk:dnum is dnumber in Department
Dependent(essn, dependent_name, sex, bdate, relationship)
fk: essn is ssn in Employee
Works_on(essn,pno,hours)
fk: essn is ssn in Employee; pno is pnumber in Project
I would like to retrieve the list of locations for the finance department using only the following relational algebra operations {σ, π, ∪, ρ, −, ×}.
so far i have:
π dlocation (σ department (dname = 'research'))
i'm really stuck, and confused... i don't know if its possible to do it without an equijoin operation.

Started writing a comment then changed my mind. :)
If you look at Wikipedia, you will find this equivalence:
R ⋈_φ S = σ_φ(R × S)
And particularly where the restriction is that of equality, it's an equijoin. What this means is, equijoin is equivalent to a restriction on a cartesian product of two tables on the equality of two fields.
So...
π_{dlocation}(
σ_{dnumber = loc_dnumber}(
σ_{dname = "finance"}(department)
×
ρ_{loc_dnumber / dnumber}(dept_locations)))
(We need to use a rename so that we don't get into the nonsensical dnumber = dnumber place.)

Related

Oracle SQL - How to add a filter on a case field that I created?

I'm relatively new to Oracle SQL and have run into an issue where I'm trying to filter a report to only return records logged by a specific list of user names.
They are currently stored in the system in fields user.first_name and user.surname and I've created the following CAST field in the coding to join the two together:
CAST(USER.FIRST_NAME||' '||USER.SURNAME as VARCHAR (25)) as CUSTOMER
What I want to do now though is restrict it so that my query will only return records where the customer is in a pre-determined list that I can hard core into the SQL.
eg I only want to see records for :
Joe Bloggs,
John Doe,
A Nother
How do I do this in Oracle SQL?
Thanks
One option (as you said you hardcoded it) is
select *
from your_table
where customer in ('Joe Bloggs', 'John Doe', 'A Nother');
A better one is to store those customers into a separate table and join it with your_table:
insert into separate_table (name) values ('Joe Bloggs'); -- do the same for the rest
select *
from your_table y join separate_table s on s.name = y.first_name ||' '||i.surname;
Even better, use their IDs (because, there could be two John Doe persons; which one will you take)?
insert into separate_table (id) values (1123); -- this is Joe Bloggs
select *
from your_table y join separate_table s on s.id = y.id;

Hierarchy query based on two tables,

In my oracle 11g database, I have two tables. The first, accitem, holds item information and the second, acchart holds chart information. Every row in table accitem is unique. There are no repetitions. In the other table, acchart item_type refers to a foreign key in the acchart table. It looks like so:
Accitem
ID Details
BS Balance sheet
PL Profit & loss
acchart
item_code item_description item_type
INC Income PL
EXP Expenses PL
Ass Assets BS
Eqt Equity BS
What I want to achieve is as follows:
Balance Sheet
+ Assets
+ Equity
Profit & Loss
+ Income
+ Expenses
How would you achieve that?
You can indeed use connect by:
select decode(level, 2, ' + ', '')||q.name from
(
select ID, Details name, null parent_id
from accitem
UNION ALL
select null ID, item_description name, item_type parent_id
from acchart
) q
start with parent_id is null
connect by prior id = parent_id
Its performance is questionable on larger datasets though.

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?).

relational algebra queries

I have these tables
Employee(ssn, name, sex, address, salary, bdate, dno, superssn)
fk:superssn is ssn in Employee
fk:dno is dnumber in Department
Department(dnumber, dname, mgrssn, mgrstartdate)
fk:mgrssn is ssn in Employee
Dept_locations(dnumber, dlocation)
fk:dnumber is dnumber in Department
Project(pnumber, pname, plocation, dnum)
fk:dnum is dnumber in Department
Dependent(essn, dependent_name, sex, bdate, relationship)
fk: essn is ssn in Employee
Works_on(essn,pno,hours)
fk: essn is ssn in Employee; pno is pnumber in Project
I would like to retrieve the the birthdays of each of the children of every female employee using only the following relational algebra operations {σ, π, ∪, ρ, −, ×}.
so far I have π bdate ( σ{sex = 'f'} Employee ) x (σ {relationship='child'} Dependent), but i don't think it is right.
What does x stand for ? Natural join ? Cartesian product ?
If it's cartesian product, that cartesian product will/might have two distinct attributes named BDATE. You need to deal with it.
If it's natural join, the BDATE attribute will be part of the join fields. You'll need to deal with it.
π bdate (σ{sex = 'f'} Employee) x (σ{relationship='child'} Dependent) has ambiguous bdates, and will join every female employee with every child, even unrelated ones.
In any relational algebra expression, you start by projecting the attributes you care about:
π(ssn, sex)Employee ...something... π(essn, bdate, relationship)Dependent
Now you can select the tuples to join:
(σ{sex='f'} π(ssn, sex)Employee) ...something...
(σ{relationship='child'} π(essn, bdate, relationship)Dependent)
And join them:
(σ{sex='f'} π(ssn, sex)Employee) ×
(σ{relationship='child'} π(essn, bdate, relationship)Dependent)
Select only the dependents of each employee:
σ{ssn=essn} ((σ{sex='f'} π(ssn, sex)Employee) ×
(σ{relationship='child'} π(essn, bdate, relationship)Dependent))
From that, select the dependent birthdate:
π(bdate)(σ{ssn=essn} ((σ{sex='f'} π(ssn, sex)Employee) ×
(σ{relationship='child'} π(essn, bdate, relationship)Dependent)))
If you are knowledgeable about relational algebra, you may suspect that I have not done this in the most efficient way possible. You would be correct, but this does show each step separately. Optimizing this query is left as an exercise for the reader.

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.

Resources