Make one-to-many relationship look like one-to-one - sql

I'm using postgres 9.3.5.
Given the following data:
select * from department;
id | name
----+-----------
1 | sales
2 | marketing
3 | HR
and
select * from people;
id | department_id | first_name | last_name
----+---------------+------------+-----------
1 | 1 | Tom | Jones
2 | 1 | Bill | Cosby
3 | 2 | Jessica | Biel
4 | 1 | Rachel | Hunter
5 | 2 | John | Barnes
I'd like to return a result set like this:
id | name | first_name-1 | last_name-1 | first_name-2 | last_name-2 | first_name-3 | last_name-3
----+-----------+--------------+-------------+--------------+-------------+--------------+------------
1 | sales | Tom | Jones | Bill | Cosby | Rachel | Hunter
2 | marketing | Jessica | Biel | John | Barnes
3 | HR |
Is this possible?
The answer provided here by Max Shawabkeh using the GROUP_CONCAT is close - but its not returning as extra fields in the dataset, its concatenating them into a single field.

You need cross-tabulation (sometimes called pivot).
Could look like this in your case:
SELECT * FROM crosstab(
$$SELECT d.id, d.name,'p' AS dummy_cat
,concat_ws(' ', p.first_name, p.last_name) AS person
FROM department d
LEFT JOIN people p ON p.department_id = d.id
ORDER BY d.department_id, p.id$$
)
AS ct (id int, department text, person_1 text, person_2 text, person_3 text);
Returns:
id department person_1 person_2 person_3
--------------------------------------------------------
1 sales Tom Jones Bill Cosby Rachel Hunter
2 marketing Jessica Biel John Barnes <NULL>
3 HR <NULL> <NULL> <NULL>
Very similar to this related case (explanation for special difficulties there):
Postgres - Transpose Rows to Columns
But this case is simpler: since you do not seem to care about the order in which persons are listed, you can use the basic one-parameter form of crosstab().
Also, according to your comment, you want all departments, even if no people are assigned. Adjusted the LEFT JOIN accordingly.
Basic details in this related answer:
PostgreSQL Crosstab Query

Related

Finding duplicates between two columns same table

I want to find duplicate values between two columns without a id.
Example:
table employees
-----------------------------------
employee_one | employee_two |
-----------------------------------
JOHN SMITH | JACK STEVENS |
MASON LEWIS | JOHN WALKER |
ANDREA YOUNG | MARTINA ROBINSON|
JACK STEVENS | JOHN SMITH |
JOHN WALKER | MASON LEWIS |
MARTINA ROBINSON| ANDREA YOUNG |
and the results I want is:
-----------------------------------
employee_one | employee_two |
-----------------------------------
JOHN SMITH | JACK STEVENS |
MASON LEWIS | JOHN WALKER |
ANDREA YOUNG | MARTINA ROBINSON|
or
-----------------------------------
employee_one | employee_two |
-----------------------------------
JACK STEVENS | JOHN SMITH |
JOHN WALKER | MASON LEWIS |
MARTINA ROBINSON| ANDREA YOUNG |
My problem is that my query always find all the results and I get the same table. I tried:
SELECT DISTINCT t1.*
FROM employees
AS t1 LEFT JOIN employees AS t2 ON (t1.employee_one = t2.employee_two AND t1.employee_two = t2.employee_one)
OR (t1.employee_one = t2.employee_one AND t1.employee_two = t2.employee_two)
But I get the same results
Every pair of names will show up twice, so use a where clause to limit the output to just those where employee_one < employee_two:
select t1.*
from employees t1
where employee_one < employee_two
and exists (
select *
from employees t2
where t2.employee_two = t1.employee_one
and t2.employee_one = t1.employee_two)
Caveat: this assumes that there are no rows where employee_one = employee_two.

Finding an ID not in another column

I'm working on a little SQL exercise, and am scratching my head an this problem.
I am trying to find all the Employees to whom no other employee reports to.
This is what the employees table looks like:
EmployeeId LastName FirstName Title ReportsTo
1 Adams Andrew General Manager null
2 Edwards Nancy Sales Manager 1
3 Peacock Jane Sales Support Agent 2
4 Park Margaret Sales Support Agent 2
5 Johnson Steve Sales Support Agent 2
6 Mitchell Michael IT Manager 1
7 King Robert IT Staff 6
8 Callahan Laura IT Staff 6
I thought a straightforward one of these queries would do it:
SELECT *
FROM employees
Where EmployeeId not in (select ReportsTo from employees)
SELECT *
FROM employees
Where EmployeeId not in (ReportsTo)
But those return the following results, which isn't what I'm looking for:
EmployeeId LastName FirstName Title ReportsTo
2 Edwards Nancy Sales Manager 1
3 Peacock Jane Sales Support Agent 2
4 Park Margaret Sales Support Agent 2
5 Johnson Steve Sales Support Agent 2
6 Mitchell Michael IT Manager 1
7 King Robert IT Staff 6
8 Callahan Laura IT Staff 6
Why is NOT IN returning items that are definitely in that column? How would I go about returning items not in ReportsTo if I am using NOT IN incorrectly?
Use not exists with a correlated subquery (as commented by jarlh):
select *
from employees e
where not exists (
select 1
from employees e1
where e1.ReportsTo = e.EmployeeId
)
The problem with your 1st query is that you use NOT IN with a list that contains a NULL value.
So a comparison of an EmployeeId like say 5:
5 NOT IN (null, 1, 2, 6)
will return NULL, because any comparison to NULL returns NULL and that EmployeeId will not be included in the results.
Change to:
SELECT *
FROM employees
Where EmployeeId not in (
select ReportsTo
from employees
where ReportsTo is not null
);
See the demo.
Results:
| EmployeeId | LastName | FirstName | Title | ReportsTo |
| ---------- | -------- | --------- | ------------------- | --------- |
| 3 | Peacock | Jane | Sales Support Agent | 2 |
| 4 | Park | Margaret | Sales Support Agent | 2 |
| 5 | Johnson | Steve | Sales Support Agent | 2 |
| 7 | King | Robert | IT Staff | 6 |
| 8 | Callahan | Laura | IT Staff | 6 |
You can simply use the below query--
select * from employees emp where employeeID not in (select ReportsTo from employee)

How to use SQL to add a column of data to a result table, that is based on a logical reference within the same table?

I am trying to add a column to some results not editing the database itself, for the sake of a dashboard program that will be more user friendly, in order to help organize results according to employee supervisors. The supervisors will not know their Employee ID off the top of their heads, and for ease of use, just having their names be the item that is sortable going forward would be useful.
So I want to write a query that will transform TABLE 1 into TABLE 2.
TABLE 1
EmployeeID | Name | SupervisorID |
1 | Dave | 3 |
2 | Jeff | 3 |
3 | Cindy | 4 |
4 | Carol | NULL |
TABLE 2
EmployeeID | Name | SupervisorID | **SupervisorName
1 | Dave | 3 | Cindy
2 | Jeff | 3 | Cindy
3 | Cindy | 4 | Carol
4 | Carol | NULL | NULL
...
select t1.EmployeeID ,t1.Name, t1.SupervisorID, t2.name As SupervisorName
from table1 t1
left join table1 t2 on t1.SupervisorId = t2.EmployeeID

Group and sum on multiple ids

My brain has locked up on the following problem.
This for an Bi Publisher report
Users can select one or several employee names from a multi select dropdown
e.g John, Peter, Ann ...
In table Emp we have:
emp_id | emp_name
---
1 | John
2 | Peter
3 | Ann
Table worklimit
emp_id | limit | from_date | to_date
---
1 | 35 | 04-jul-2016 | 08-jul-2016
1 | 15 | 11-jul-2016 | 15-jul-2016
2 | 40 | 04-jul-2016 | 08-jul-2016
2 | 20 | 01-aug-2016 | 05-aug-2016
3 | 27 | 04-jul-2016 | 08-jul-2016
So the result I want is a total sum(limit) for the selected employee
e.g John + Ann 77
Or John + Ann + Peter = 137
This is just the first step. I need to sum up som values from a third table too, and I need to select a date range, and it must be grouped by week or month to make a barchart .
Can some of you clever brains out there point me in the right directions?
Try this:
SELECT SUM(A.limit) FROM
(
SELECT SUM(limit) limit,emp_name
FROM worklimit
JOIN Emp ON Emp.emp_id = worklimit.emp_id
GROUP BY emp_name
) A
WHERE emp_name IN ('John','Peter','Ann')

Merging different column values in a single row in Oracle 9i [duplicate]

Consider the following table,
Name | Subject_1 | Marks_1 | Subject_2 | Marks_2 |
Tom | Maths | 90 | | |
Tom | | | Science | 50 |
Jon | | | Science | 70 |
Jon | Maths | 60 | | |
How do I get the following result
Name | Subject_1 | Marks_1 | Subject_2 | Marks_2 |
Tom | Maths | 90 | Science | 50 |
Jon | Maths | 60 | Science | 70 |
Tried forms of GROUP BY but did not get correct result, Maths will always come under Subject_1 and Science under SUbject_2.
Use:
MAX
GROUP BY
SQL> SELECT NAME,
2 MAX(subject_1) subject_1,
3 MAX(marks_1) marks_1,
4 MAX(subject_2) subject_2,
5 MAX(marks_2) marks_2
6 FROM t
7 GROUP BY name;
NAME SUBJECT_1 MARKS_1 SUBJECT_2 MARKS_2
---- --------- ---------- --------- ----------
Jon Maths 60 Science 70
Tom Maths 90 Science 50
SQL>
On a side note, you need to think about your table design. You could only have 3 columns, name, subject, marks.
If you want to have separate columns in the same table, then you should have them as a single row for each student. And when you have a new subject for the student, then update the row for that student, instead of adding a new row.
Try this :
select maths.name,maths.subject_1,maths.marks_1,sci.subject_1,sci.marks_2
from mytable maths,(select * from mytable where subject_2 is not null) sci
where maths.name=sci.name
and maths.subject_1 is not null;