group by expression not working properly? - sql

My code works fine for the below query:
CREATE TABLE employee ( employee_id INTEGER, manager_id INTEGER, first_name VARCHAR2(10) NOT NULL, last_name VARCHAR2(10) NOT NULL, title VARCHAR2(20), salary NUMBER(6, 0) );
CREATE TABLE manager( manager_id INTEGER NOT NULL, manager_dept VARCHAR2(20) NOT NULL, first_name VARCHAR2(30) NOT NULL, last_name VARCHAR2(30) NOT NULL ) Here are my tables
SELECT
DISTINCT E.EMPLOYEE_ID, M.MANAGER_ID, E.FIRST_NAME,
E.LAST_NAME, CONNECT_BY_ISLEAF, CONNECT_BY_ISCYCLE, level
FROM EMPLOYEE E
LEFT OUTER JOIN MANAGER M
ON E.MANAGER_ID=M.MANAGER_ID
WHERE m.manager_id=10
start with e.employee_id >0
connect by NOCYCLE m.manager_id=prior e.employee_id
GROUP BY
E.EMPLOYEE_ID, M.MANAGER_ID, E.FIRST_NAME,
E.LAST_NAME, CONNECT_BY_ISLEAF, CONNECT_BY_ISCYCLE,LEVEL
but i want my result set to grouped by only on employee_id and not all fields
When i use this query below then an error generates "NOT A GROUP BY EXPRESSION"
SELECT
DISTINCT E.EMPLOYEE_ID, M.MANAGER_ID, E.FIRST_NAME,
E.LAST_NAME, CONNECT_BY_ISLEAF, CONNECT_BY_ISCYCLE, level
FROM EMPLOYEE E
LEFT OUTER JOIN MANAGER M
ON E.MANAGER_ID=M.MANAGER_ID
WHERE m.manager_id=10
start with e.employee_id >0
connect by NOCYCLE m.manager_id=prior e.employee_id
GROUP BY
E.EMPLOYEE_ID
Whats wrong with this query??

I assume your employee ID is unique, so by grouping by it, you're grouping by everything else in the query as well. But Oracle doesn't know that. If you were grouping by employee ID only, and you had two employees having the same id, one with last name "Smith" and one with last name "Miller", which one would you like to see? It's either Smith or Miller, as you have only one row of data, so the database doesn't know what to do.
That's why you always have to group by every column that's not aggregated (using SUM, MAX, MIN, or one of the other functions).
You could omit the columns from your result set and the group by, then put the whole query into a subquery, with the outer query selecting the remaining columns from a join of the subquery and the employee table. But trust me, you don't really want to do that, as that version would be much uglier to read and understand than your current query.

Related

somebody explain to me how this query works step by step?

I don't yet understand this SQL statement:
select FIRST_NAME
from EMPLOYEES e
where DEP_ID != (select DEP_ID
from EMPLOYEES
where e.MANAGER_ID = EMPLOYEE_ID);
I would write your query as:
select e.FIRST_NAME
from EMPLOYEES e
where e.DEP_ID <> (select e2.DEP_ID
from EMPLOYEES e2
where e.MANAGER_ID = e2.EMPLOYEE_ID
);
This does not functionally change the query but it qualifies all column references and uses <> which is the traditional SQL operator for not equals.
What this query is doing is returning all employees whose department is not the same as their managers department.
How does it do this? The subquery is a correlated subquery. For each row in employees the subquery returns the department id of the manager.
The where clause then checks whether or not it matches the employee's manager.
This subquery will get the Manager's departments.
select DEP_ID from EMPLOYEES where e.MANAGER_ID = EMPLOYEE_ID
So the main query will just get the employees that not managers.
select FIRST_NAME from EMPLOYEES e where DEP_ID != (Managers dept_ID)
It's finding the employees who are not under a particular manager.
Let's see the inner part first:
select DEP_ID from EMPLOYEES where e.MANAGER_ID = EMPLOYEE_ID
This will fetch the department under particular manager
Now the outer part:
select FIRST_NAME from EMPLOYEES e where DEP_ID != <Departments under particular manager>
Now the result will the list of employees's first name not under that manager

How do I use a value from the superquery inside a subquery?

I need to create a query that shows the last name of an employee, the employee id, the last name of the manager of that employee and the id of that manager.
The last name, id, and the manager id of that employee is easy to do because it is already in one row, which means that the following is sufficient:
SELECT last_name, employee_id, manager_id FROM employees WHERE manager_id IS NOT NULL;
But to get the last_name of the manager, you have to search the same table by the manager id you got from the employee. The solution I found is:
SELECT last_name,
employee_id,
(SELECT last_name FROM employees WHERE employee_id = manager_id),
manager_id
FROM employees
WHERE manager_id IS NOT NULL;
However, it seems that 'manager_id' doesn't work in the subquery (although I expected that) and the output is NULL (for the manager id, all the other columns do have values).
So my question is, how can I use the manager_id in the subquery?
Side note: The manager_id can be different for each employee, so using a constant value doesn't work.
What you need is a correlated subquery. I strongly, strongly recommend that you use table aliases and qualified column names in all your queries. However, these are particularly important with correlated subqueries.
You should write this query as:
SELECT e.last_name, e.employee_id,
(SELECT m.last_name
FROM employees m
WHERE m.employee_id = e.manager_id
),
e.manager_id
FROM employees e
WHERE e.manager_id IS NOT NULL;
The alias e is an abbreviation for the table reference to employees in the outer query. The alias m is an abbreviation for the table reference in the subquery.
Notice that all column references use the table alias. This makes the query unambiguous, can prevent unexpected errors, and makes the query much easier for you and others to understand.
You could use a self inner join ( a join with the same table)
SELECT
a.last_name
, a.employee_id
, b.last_name
, a.manager_id
FROM employees a
INNER JOIN employees b ON b.employee_id = a.manager_id;
The inner join work only if a.manager_id is not null so you can avoid this where condition
When you want to refer to a table in the outer query, you need to either use the full table name like table.field or, as in your case, if the outer query table is same as the subquery table, you need to assign an alias to the outer query table and use it in the subquery like this:
SELECT
last_name, employee_id,
(SELECT last_name FROM employees WHERE employee_id = emp_outer.manager_id),
manager_id
FROM employees emp_outer
WHERE manager_id IS NOT NULL;

Employees without subordinates

I'm trying to print all the employees that don't have subordinates.
I have been thinking about a tree data structure. Practically, most of the employees have subordonates (those are called managers). The only ones without subordonates are the leafs (they don't have any children).
However, I don't understand how can I select the leafs from this tree.
--following prints employees without manager.
SELECT e.employee_id, e.last_name, e.first_name
FROM employees e
WHERE e.employee_id = (SELECT employee_id FROM employees WHERE manager_id IS NULL AND employee_id = e.employee_id);
In short, you want to select all employees, who don't act as managers for other employees. That means, you want to select such employees, whose employee_id is not used as manager_id for any other employee.
Try this:
SELECT *
FROM employees e
WHERE NOT EXISTS (SELECT 1
FROM employees e2
WHERE e2.manager_id = e.employee_id)
You can do this via an outer join:
SELECT e.employee_id, e.last_name, e.first_name
FROM
employees e
LEFT JOIN employees sub
ON e.employee_id = sub.manager_id
WHERE sub.manager_id IS NULL
The filter condition selects only those rows of the left table that have no matching rows in the right table.
This is preferable to filtering via a correlated subquery, as the latter may require performing the subquery separately for every single employee row. (If the query planner avoids that, it will be by transforming it into an equivalent of the outer join.)
SELECT e.employee_ID, e.last_name, e.First_name, CONNECT_BY_ISLEAF "IsLeaf",
LEVEL, SYS_CONNECT_BY_PATH(e.employee_ID, '/') "Path"
FROM employees e
CONNECT BY PRIOR E.employeeID = E.Manager_ID;
where isLeaf =1
Basically stolen from help docs:
http://docs.oracle.com/cd/B12037_01/server.101/b10759/pseudocolumns001.htm#i1007332
or another stack question: get ALL last level children (leafs) from a node (hierarhical queries Oracle 11G)
SELECT *
FROM employees e
WHERE e.employee_id NOT IN ( SELECT nvl(manager_id, 0)
FROM employees );

Aggregate function couting times hired

I'm using the HR table schema where i have an exercise stating:
The job_history can contain more than one entries for an employee who was hired more than once. Create a query to retrieve a list of employees that were hired more than once. Include the columns EMPLOYEE_ID, LAST_NAME, FIRST_NAME and the aggregate "Times Hired".
What have I done so far is:
select e.employee_id, e.last_name, e.first_name,
count (start_date) as Times_Hired
from job_history jH, employees e
WHERE e.employee_id=jH.employee_id
group by e.employee_id, e.last_name, e.first_name;
Now, my questions are:
should the whole thing be a subquery?
which columns connect the tables job_history and employees?
Because when I run this it displays a few employees only.
You can (and should) rewrite the query with explicit ANSI JOIN syntax to make it clear:
SELECT employee_id, e.last_name, e.first_name,
count (*) AS Times_Hired
FROM employees e
JOIN job_history j USING (employee_id)
GROUP BY employee_id, e.last_name, e.first_name
HAVING count (*) > 1;
I also use a LEFT [OUTER] JOIN to include employees in the result that do not have any rows on job_history (yet).
This is not relevant, since you are only interested in employees ..
than were hired more than once
I implemented this condition with HAVING count (*) > 1.
As to your 2nd question: obviously, employee_id is the column that ..
connects the tables job_history and employees?
Since the column name we join on (employee_id) is identical in both tables I simplified to an equi-join with USING.
And I use count(*) instead of count (start_date), since it has not been declared whether start_date can be NULL, in which case it wouldn't add to the count.
As to your first question: no, you don't need a subquery here.
Alternative JOIN syntax with ON
SELECT e.employee_id, e.last_name, e.first_name,
count (*) AS Times_Hired
FROM employees e
JOIN job_history j ON j.employee_id = e.employee_id
GROUP BY e.employee_id, e.last_name, e.first_name
HAVING count (*) > 1;
Tested both in SQLfiddle.

find the link in tables sql exercise

so I'm stuck on this question where it says:
write a query to retrieve a list of ALL departments in alphabetical order containing the columns DEPARTMENT_ID , DEPARTMENT_NAME , LAST_NAME , FIRST_NAME where last_name and first_name are the name of the Manager of the Department wherever there is one.
I'm have the HR database using the departments, employees table what i have written so far is:
select department_id, department_name, e.last_name, e.first_name
from departments d, employees e
where e.department_id=d.department_id
and d.department_id=e.department_id
and d.manager_id=e.manager_id
having department_name = '%Manager%';
yet i can't figure it out, any tips will help thanks!
A having clause relates to a group by. Where you have no group by, there is no meaning for a having clause.
You just need your join to get the record in the employees table for the manager.
select department_id, department_name, e.last_name, e.first_name
from departments d, employees e
where d.manager_id=e.id
Better is to use the newer join syntax,
select dept.department_id, dept.department_name, emp.last_name, emp.first_name
from departments dept
inner join employees emp on dept.manager_id = emp.id
You don't need conditions for e.department_id=d.department_id and d.department_id=e.department_id, (for one thing these are redundant, so you would only need one if they were needed) because the employees.department_id field refers to the department the employee is in, and what you want is the manager employee record for the department. The manager employee record for the department is represented as the foreign key to the primary key of the employee table.
this might help you...
`select e.first_name,e.last_name,d.department_id ,d.department_name from employees e,departmets d where d.manager_id=e.employee_id order by d.department_name;`