Joining multiple counts on the same table, from different columns? - sql

I feel like this should be easy but it's late and I'm struggling.
Say (in an oracle 12 db) I have a table which represents which staff filled what roles in a bar, during different events, like this:
+----------+----------+-------+------------+----------+
| event_id | bar | doors | cloak_room | keg_room |
+----------+----------+-------+------------+----------+
| 2 | bob | bill | john | mary |
+----------+----------+-------+------------+----------+
| 3 | bob | bill | mary | kev |
+----------+----------+-------+------------+----------+
| 4 | bob | john | louise | mary |
+----------+----------+-------+------------+----------+
| 5 | kyle | kev | sarah | louise |
+----------+----------+-------+------------+----------+
| 6 | jennifer | bob | jay | john |
+----------+----------+-------+------------+----------+
| 7 | john | bill | mary | steve |
+----------+----------+-------+------------+----------+
and I want to get a count of, overall, how many events each staff member worked, like this:
+-------+--------+
| count | person |
+-------+--------+
| 4 | bob |
+-------+--------+
| 4 | john |
+-------+--------+
| 3 | bill |
+-------+--------+
| 3 | mary |
+-------+--------+
| 2 | kev |
+-------+--------+
| 2 | louise |
+-------+--------+
| 1 | jay |
+-------+--------+
| 1 | steve |
+-------+--------+
We see here that bob has a count of 4 - because he is associated with 4 distinct event_id: 3 as a barman, and 1 as a doorman.
(assuming no two staff members have the same name, and no one can work two jobs at once)
How do I do this?
for one 'role' it's clear:
select count(event_id), bar group by bar
but is there an elegant way to do this for all columns - without full joins and string concat?
thanks!

You should change the structure of your data, so you have one row per event/person/role. Then you could just use aggregation.
You can do that in a query as well:
select who, count(*)
from (select event_id, 'bar' as job, bar as who from t union all
select event_id, 'doors' as job, doors as who from t union all
select event_id, 'cloak_room' as job, cloak_room as who from t union all
select event_id, 'keg_room' as job, keg_room as who from t
) jw
group by who;
If someone could have multiple jobs in one event, then use count(distinct event_id).
EDIT:
I see you are using Oracle 12c. Then use a lateral join/cross apply:
select who, count(*)
from t cross apply
(select t.event_id, 'bar' as job, t.bar as who from dual union all
select t.event_id, 'doors' as job, t.doors as who from dual from dual union all
select event_id, 'cloak_room' as job, cloak_room as who from dual union all
select t.event_id, 'keg_room' as job, t.keg_room as who from dual
) jw
group by who;

You may count by string columns in the nested inner query and then sum them up outside with your desired order :
SELECT sum(count) count, person
FROM
(
SELECT count(event_id) count, bar person FROM mytable GROUP BY bar UNION ALL
--> P.S. Only aliasing as "person" is enough in this upper "select" for all
--> four "select" statements inside the parentheses.
SELECT count(event_id) , doors FROM mytable GROUP BY doors UNION ALL
SELECT count(event_id) , cloak_room FROM mytable GROUP BY cloak_room UNION ALL
SELECT count(event_id) , keg_room FROM mytable GROUP BY keg_room
)
GROUP BY person
ORDER BY 1 desc, 2;
COUNT PERSON
4 bob
4 john
3 bill
3 mary
2 kev
2 louise
1 jay
1 jennifer
1 kyle
1 mary2
1 sarah
1 steve
SQL Fiddle Demo

Related

SQL Count of same country, same name, same age for people with same last name (e.g smith)

I need your advice.
we have the below table
Name of table: PEOPLE (i know bad name for example :-) )
ID | firstname | age | country
1 | George | 20 | US
1 | George | 20 | GB
2 | Jim | 20 | FR
2 | Jim | 21 | FR
i need to see the below result, so for same ID counts of values
ID | firstnamecnt | agecnt | countrycnt
1 | 2 | 2 | 1
2 | 2 | 1 | 2
I hope I explained well :-)
Thanks and regards,
Alex
I assume you meant firstname. Here is one way
select id,
count(*) - count(distinct firstname) + 1 as firstnamecnt,
count(*) - count(distinct age) + 1 as agecnt,
count(*) - count(distinct country) + 1 as countrycnt
from people
group by id;
DEMO

Summarise Data based on an Account within a Query using SQL

Come up against an issue where i want to summarize results in a query.
Example being as follows:
NAME | FRUIT | PRICE
JOHN | APPLE | 2
JOHN | APPLE | 2
JOHN | APPLE | 2
JOHN | APPLE | 2
DAVE | GRAPE | 3
DAVE | GRAPE | 3
DAVE | GRAPE | 3
This is my table at the moment, what i need though is to have a summary of Johns business, like below:
NAME | FRUIT | PRICE
JOHN | APPLE | 2
JOHN | APPLE | 2
JOHN | APPLE | 2
JOHN | APPLE | 2
JOHN | TOTAL | 8
DAVE | GRAPE | 3
DAVE | GRAPE | 3
DAVE | GRAPE | 3
I have tried to group the information but it does not reflect what i want, plus if John were to have different fruit it would need to sum that up before it sums up the next part.
Any advice would be great
Why do you want a summary only of John? You can use union all for this:
select name, fruit, price
from ((select name, fruit, price, 1 as ord, fruit as f
from t
) union all
(select name, 'Total', sum(price), 2 as ord, fruit as f
from t
where name = 'John'
group by name, fruit
)
) x
order by name, f, ord;

select multiple columns count one column and group by one column in one table

I have a table named [delivery], with columns [ID],[Employee],[Post],[c_name],[Deli_Date],[note]
I need to Select all columns and count Employee and then group by Employee.
data in the table like this:
---------------------------------------------------
| Employee| Post |c_name |Deli_Date |Note |
|---------|-----------|---------------------|-----|
| DAVID |MANAGER | a |11/11/2018 |note |
| DAVID |MANAGER | b |01/01/2015 |note |
| SAM |ACOUNTS | c |10/10/2016 |note |
| DAVID |IT | a |10/02/2015 |note |
| DAVID |DOCTOR | c |20/02/2017 |note |
| JAMES |DELIVERYMAN| b |05/05/2015 |note |
| OLIVER |DRIVER | b |02/02/2014 |note |
| SAM |ACOUNTS | c |02/02/2012 |note |
this code:
select Employee, count(Employee) as TIMES from delivery
group by Employee
the result is: (but I need to show the other columns too).
| Employee| TIMES |
|---------|-------|
| DAVID | 4 |
| JAMES | 1 |
| OLIVER | 1 |
| SAM | 2 |
I need my code show Like This:
| Employee| TIMES | Post |c_name |Deli_Date |Note |
|---------|-------|-----------|---------------------|-----|
| DAVID | 4 |MANAGER | a |11/11/2018 |note |
| JAMES | 1 |DELIVERYMAN| b |05/05/2015 |note |
| OLIVER | 1 |DRIVER | b |02/02/2014 |note |
| SAM | 2 |ACOUNTS | c |10/10/2016 |note |
what is the best Query could give me that result?
see columns [c_name],[Deli_Date] they shows the last inserted data.
or just give me the result without [c_name],[Deli_Date] it's ok.
If you want the last date along with the count, you can use window functions:
select d.Employee, d.cnt, d.Post, d.c_name, d.Deli_Date, d.Note
from (select d.*,
count(*) over (partition by employee) as cnt,
row_number() over (partition by employee order by deli_date desc) as seqnum
from delivery d
) d
where seqnum = 1;
You need to include all the non-aggregated columns in the select and group by clauses
select Employee,
count(Employee) as TIMES,
max(Deli_date) as LAST_DELI_DATE,
post,
note
from delivery
group by Employee, post, note
Max(Deli_date) will give you the latest date.
To get the latest c_name, note, post, etc. you will need a subquery with a rank function sorted by the deli_date. I will add the example later.
select Employee, count(Employee) as TIMES,Post,c_name,Deli_Date,Note from delivery
group by Employee

Union two tables with a table _name column

I want to union two tables(Student1, Student2).
1 - Student1
| student_code | name |
--------------------------
| 1 | katia |
| 2 | roger |
| 3 | ken |
2 - Student2
| student_code | name |
--------------------------
| 3 | katia |
| 4 | roger |
| 5 | ken |
then I want get result like this.
result
|table_name| student_code | name |
-------------------------------------
|Student1 | 1 | katia |
|Student1 | 2 | roger |
|Student1 | 3 | ken |
|Student2 | 3 | katia |
|Student2 | 4 | roger |
|Student2 | 5 | ken |
I want to use only ANSI sql.
You could use
SELECT 'Student1' AS table_name, student_code, name FROM Student1
UNION ALL
SELECT 'Student2' AS table_name, student_code, name FROM Student2
select 'Student1' AS table_name,student_code,name from student1
union
select 'Student2' AS table_name,student_code,name from student2
I assume you know the difference betweenUNION and UNION ALL, union brings unique records, it is same as UNION PERFORMED ON SETS while union all will bring duplicate rows as well.
In your case, it will bring duplicates even with union because of the first column which differentiates the rows.
Use UNION ALL statement :
SELECT 'Student1' as table_name, student_code, name FROM Student1
UNION ALL
SELECT 'Student2' as table_name, student_code, name FROM Student2

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

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