PostgreSQL: Realizing columns depending on other tables - sql

I'm currently learning the basics of SQL and now I'm facing the following problem. The implementation of it will be in PostgreSQL.
Let's say you have the two following tables in your database:
department(dpID:PK, ..., avg_salary)
employee(eID:PK, ..., dpID:FK REF department, salary)
(PK = Primary Key, FK = Foreign Key)
So every employee has a salary and a department, every department should store the average salary of its employees (not just in a view).
Of course there should be some kind of update rule in the database, so when a new employee gets inserted, the avg_salary of the employees department gets updated / recalculated.
So when there are e.g. currently no employees in a department X, the avg_salary should be 0. After inserting an employee in department X with a salary of 1000, the avg_salary should be 1000.
Calculating the avg_salary wouldn't be a problem with a sql query.
SELECT AVG(e.salary) FROM empoyee e WHERE e.dpID = ...
But I'm stuck at finding a way to use this result to solve my problem.
So I would be very glad for ideas to realize the function of automatically (re-)calculating attribute values with the result of an aggregation function used on a different table.
Thank you and have a nice day! :)

I agree with nbk, you shouldn't use a function to recalculate.
What I would do is create a view. Something like this
create view deptview as select d.name, coalesce(avg(salary),0) from employee e right join department d on e.dpId=d.id group by d.name;
It will recalculate the average every time you use the deptview view
To see your data just
select * from deptview;

Related

SQL query from Lynda.com

I have a question about two queries. Will these two queries give the same result? I am trying to find the average salary by department:
Select s1.department, avg(s1.salary)
From
(Select department, salary
From staff
Where salary > 100000) s1
Group by s1.department
vs
select department, avg(salary) as avg_salary
from staff
where salary > 100000
group by department
Yes, it gives the same amounts back.
the bottom query gets data from a sub select which gets its data from the table, whereas the top query gets it straight from the table itself.
There are no additional filters in there. So the result will be the same.
you can test it out however, don't take my word for it.

SQL - Why Does This Happen?

These are the tables that I'm working with.
With that in mind, I want to showcase the Employees that are both a supervisor and a manager.
But when I used this
select e1.fname,e1.lname
from employee e1,employee e2,department
where e1.ssn=e2.super_ssn and e1.ssn = Mgr_ssn
This was the output
I know I can solve the problem with 'distinct', but I'm more interested to know why the output turned out like it did.
How about exists?
select e.*
from employee e
where exists (select 1 from employee e2 where e2.mgr_ssn = e.ssn) and
exists (select 1 from employee e2 where e2.super_ssn = e.ssn) ;
Your query returns duplicates for two reasons. First, presumably managers and supervisors have multiple employees below them. You end up with rows for each such employee. Second, you have a cartesian product with department, which further multiplies the rows. The department table is not used in the query.
Using select distinct is not a good solution in this case. The database just ends up having to do a lot more work than necessary -- first to create the duplicate rows and then to remove them.
add department matching clause in where like
select e1.fname,e1.lname
from employee e1,employee e2,department d
where e1.ssn=e2.super_ssn and e1.ssn = Mgr_ssn and
d.Dnumber=e1.Dno

ORACLE SQL dealing with different tables

I will explain the problem I am stuck on. I have a table named empl02 which contains Lastname, salary, and position for all the employees. I am asked to display last,name,salary, position for all employees making more money than the highest paid member of a certain 'position', we will call this position server. I cannot just do something simple like...
SQL> select Lastname,salary,position FROM empl02
2 WHERE
3 SAL > 125000;
Rather, it must be dynamic. I feel the logic is pretty simple I'm just not sure how to translate it into SQL. I am thinking something along the lines of
"SELECT Lastname,salary,position from empl02 where salary > MAX(SALARY) of position(server)" what is a way to translate this task to SQL?
You need to retrieve the "reference" salary as a sub-query:
select lastname, salary, position
from empl02
where salary > (select max(salary)
from empl02
where position = 'manager');

Oracle SQL change value of field to other value from list as long as new value different from current?

I have a test table which contains 33 employee records in my Oracle 10g XE. Examples are as the following (I'm showing 5 only):
I'm not a SQL person so I only know those basic INSERT, UPDATE etc. What I want to achive is to change the department of each employee to another department from another employee as long as it's not the same as their current department. Basically what I'm thinking is:
Get all of the department from each employee (DEPT A, DEPT B, DEPT B, DEPT C, DEPT A), maybe store it in a variable?
For each of the employee, put a different department into its record from the list of department collected before. The new department must not be the same as what that record has before.
The result of the above example would be like the following.
For the record, there are more than just DEPT A - C. My 33 employee records mostly have unique department. Only 2 - 3 department have more than 1 employee under it. Therefore, I can assure that there probably won't be any cases where an employee record cannot be assign under a different department from what it currently has.
This is a harder problem than you may realize. First, it is a permutation problem because you want the final counts for the departments to be the same. Second, there are times where it is not solvable (say, if more than half the employees are in the same department).
If you want to shuffle the departments for employees, here is a method:
with cte as (
select e.*,
row_number() over (order by emp_id) as orig_seqnum,
row_number() over (order by dbms_random.value) as new_seqnum
from employees e
)
select e1.*, e2.dept as new_dept
from cte e1 join
cte e2
on e1.orig_seqnum = e2.new_seqnum;
This does not guarantee that the new department is different from the old one. There are actually ways to make that happen. However, given that most of your departments have only one employee, this might solve your problem.

Find all the emps and their salaries who earn minimum salary in their department, display result in salary ascending order

employee database
SELECT name, MIN(salary)
FROM employee
GROUP BY deptid;
Why I can't select "name" here?
And what is the suggested query for this question?
Why I can't select "name" here?
You can only use columns or expressions the the SELECT if they are present in the GROUP BY area, or part of a function that calculates an aggregation.
Think of grouping like buckets. When you say group by dept_id if there are 3 departments (even if they are mentioned 20 times; 20 employees in departments A,A,A,A,A,A,A,A,A,A,A,A,B,B,B,B,B,C,C,C) you get 3 buckets and there's a label on the outside of each bucket, one for each different value of dept_id (a bucket for A, a bucket for B and a bucket for C). Into those buckets, all the employees rows are thrown according to which department they're in: 12 rows in A, 5 rows in B, 3 rows in C. Then you say MIN(salary), the db searches each bucket looking for the minimum salary.
Why can't you say name? There simply isn't a bucket for it. Your buckets are labelled A, B and C for the departments. No bucket has a name written on it. While there are names inside the bucket, you can only ask for them in terms of a MIN, MAX, AVG, SUM, COUNT etc - it's the rule. "To get something out of a bucket you have to use some kind of function that calculates some statistic". Actually, in some DBs you can ask for a CSV string of all the names, but we'll ignore that because it's accessory to the main point: unless you use an aggregate function you can only ask for something written on the outside of a bucket
What information should the DB give you if you ask for name?
You could ask for MIN(name), but you'd only get one name. You could GROUP BY Name.. but then you'd have a lot more buckets, and your employees would be redistributed across the buckets. You would't be asking for the MIN salary per department bucket any more, you'd be asking for the min salary per name.. Not what you want
And what is the suggested query for this question?
Break it down:
"minimum salary in the department"
We're going to need a list of all departments and the minimum salary in the department
SELECT dept_id, MIN(salary) as min_sal_for_dept
FROM employee
GROUP BY dept_id
"Find all the emps .. who earn minimum salary in their department"
Now we know that the depatment ID and min salary is for that department, we can join it back to the employee data on the department id and the salary
SELECT *
FROM
(
SELECT dept_id, MIN(salary) as sal
FROM employee
GROUP BY dept_id
) dep_min_sals
INNER JOIN
employee e
ON
e.dept_id = dep_min_sals.dept_id AND e.salary = dep_min_sals.sal
"display result in salary ascending order"
ORDER BY sal
The key thing to realize is that you have to group up (lose) detail in order to get the minimum salary per department, so if you want the detail back you have to join the grouped up data back to the detail. You cannot both lose the detail to calculate the minimum AND also keep the detail to have the employee name
A database-independent solution is to filter with a subquery:
select e.*
from emp e
where e.salary = (select min(e1.salary) from emp e1 where e1.deptid = e.deptid)
order by e.salary