SQL group by and listing - sql

I have the following information in DB :
id | name | company
---+----------+------
1 | Joe | company_1
2 | Sally | company_2
3 | Marc | company_3
4 | Bob | company_1
Is possible to do something like that in SQL :
SELECT company, _something_ FROM my_db GROUP BY company
And get :
company | members
---------- | ---------
company_1 | Joe, Bob
company_2 | Sally
company_3 | Marc
Thanks in advance for your help !

MySQL:
SELECT company, GROUP_CONCAT(name SEPARATOR ',') AS members FROM my_table GROUP BY company

Related

FIRST & LAST values in Oracle SQL

I am having trouble querying some data. The table I am trying to pull the data from is a LOG table, where I would like to see changes in the values next to each other (example below)
Table:
+-----------+----+-------------+----------+------------+
| UNIQUE_ID | ID | NAME | CITY | DATE |
+-----------+----+-------------+----------+------------+
| xa220 | 1 | John Smith | Berlin | 2020.05.01 |
| xa195 | 1 | John Smith | Berlin | 2020.03.01 |
| xa111 | 1 | John Smith | München | 2020.01.01 |
| xa106 | 2 | James Brown | Atlanta | 2018.04.04 |
| xa100 | 2 | James Brown | Boston | 2017.12.10 |
| xa76 | 3 | Emily Wolf | Shanghai | 2016.11.03 |
| xa20 | 3 | Emily Wolf | Shanghai | 2016.07.03 |
| xa15 | 3 | Emily Wolf | Tokyo | 2014.02.22 |
| xa12 | 3 | Emily Wolf | null | 2014.02.22 |
+-----------+----+-------------+----------+------------+
Desired outcome:
+----+-------------+----------+---------------+
| ID | NAME | CITY | PREVIOUS_CITY |
+----+-------------+----------+---------------+
| 1 | John Smith | Berlin | München |
| 2 | James Brown | Atlanta | Boston |
| 3 | Emily Wolf | Shanghai | Tokyo |
| 3 | Emily Wolf | Tokyo | null |
+----+-------------+----------+---------------+
I have been trying to use FIRST and LAST values, however, cannot get the desired outcome.
select distinct id,
name,
city,
first_value(city) over (partition by id order by city) as previous_city
from test
Any help is appreciated!
Thank you!
Use the LAG function to get the city for previous date and display only the rows where current city and the result of lag are different:
WITH cte AS (
SELECT t.*, LAG(CITY, 1, CITY) OVER (PARTITION BY ID ORDER BY "DATE") LAG_CITY
FROM yourTable t
)
SELECT ID, NAME, CITY, LAG_CITY AS PREVIOUS_CITY
FROM cte
WHERE
CITY <> LAG_CITY OR
CITY IS NULL AND LAG_CITY IS NOT NULL OR
CITY IS NOT NULL AND LAG_CITY IS NULL
ORDER BY
ID, "DATE" DESC;
Demo
Some comments on how LAG is being used and its values checked are warranted. We use the three parameter version of LAG here. The second parameter means the number of records to look back, which in this case is 1 (the default). The third parameter means the default value to use should a given record per ID partition be the first. In this case, we use the default as the same CITY value. This means that the first record would never appear in the result set.
For the WHERE clause above, a matching record is one for which the city and lag city are different, or for where one of the two be NULL and the other not NULL. This is the logic needed to treat a NULL city and some not NULL city value as being different.

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

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

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

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

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