relational algebra queries - 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 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.

Related

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

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 specific operations

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

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.

Oracle/SQL - Join one table multiple times to the same table

I'm trying to wrap my brain around a query and hoping you can help. I have two tables: a customer_table and a product_table that look like this
name sku_num1 sku_num2 sku_num3 sku_num4
----------------------------------------------------
Bob A B C D
Frank E A
Tom G
Shelly G E
Justin E G A
sku_num widget_name
-------------------
A widget_a
B widget_b
C widget_c
D widget_d
So what I want to do is return a list of all the customers who have at least 1 product whose SKU appears in the product table. So with the above information I would receive back the records for
Bob
Frank
Justin
Any ideas how to do this?
There is no need to join one table twice:
SELECT *
FROM customers
WHERE sku_num1 IN (SELECT sku_num
FROM product_table)
OR sku_num2 IN (SELECT sku_num
FROM product_table)
....
The reason why you need multiple conditions that do a full table scan on the product_table is your wrong database design. The products a customer bought should not go into multiple columns in the customer table. Instead you should have a third table that is a N:M relation between customer and products.
Does something like this not work?
select name
from customer c, products p
where ( c.sku_num1 = p.sku)
or ( c.sku_num2 = p.sku)
or ( c.sku_num3 = p.sku)
or ( c.sku_num4 = p.sku)
Christopher,
Does this mean that you are just looking for any customers where sku_num1 or sku_num2 or sku_num3 or sku_num4 is not null?
If that's the case, you could do it two different ways.
select * from customers
where sku_num1 is not null
or sku_num2 is not null
or sku_num3 is not null
or sku_num4 is not null
If you are trying to see if they have ordered something from a specific product list, you could modify this to:
select * from customers
where sku_num1 in (select sku_num from skus)
or sku_num2 in (select sku_num from skus)
or sku_num3 in (select sku_num from skus)
or sku_num4 in (select sku_num from skus)
Incidentally, this is why people normally don't structure tables like this. There should be another join table, for lack of anything better to call it, customer_skus which would tie together customers and skus and just have a customerID and a sku_num.
This would make the query easier to write, read, and maintain. The query would look something like:
select distinct name from customers, customer_skus, skus
where customers.id = customer_skus.id
and customer_skus.sku_num = skus.sku_num
You can join a table with itself by using an alias:
select * from mytable a, mytable b where condition

Resources