Can't figure out complex query [closed] - mysql

Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 3 years ago.
Improve this question
Could anybody point me in the direction of how to write a complex mysql query? I am familiar with joins, views, and intermediate querying, but cannot quite conceptualise how to go about writing a query to pull out the data I am looking for.
I have three tables, user, user_category and category. user_category is just a bridge table.
user
user_id name
-----------------------
1 Bob
2 Tim
3 Dave
4 Simon
5 Ben
user_category
user_id category_id
-----------------------
1 1
1 5
1 3
2 4
2 1
3 2
3 4
4 4
4 1
5 3
5 5
category
category_id category
-----------------------
1 drawing
2 climbing
3 hunting
4 fishing
5 sleeping
So I can easily pull out a list of users in each category, no problem.
What I want to do, for each of these categories, is show a list of other categories that the list of users in this category is also in.
So if I take fishing as an example, my query would pull out Tim, Dave and Simon as being in that category. I want to list all the other categories that Tim Dave and Simon are in, with a count of how many users are in each of those categories. Like this:
Drawing (2) - this has Tim and Simon in it
Climbing (1) - this has Dave in it
I realise I need to get a list of users in a given category. Simple.
I also need a list of all the categories that each of these users is in (excluding current category).
Then for each category, I need a count of each of the users in each.
I think I could write this with separate nested queries, but I would like to write this in a single query using all the necessary joins if possible, but if anyone can point me towards some reading material or video content that would help me work out how to plan these queries, that would be even better.

You need multiple joins of the tables:
select cn.category, count(*) counter
from category c
inner join user_category uc on uc.category_id = c.category_id
inner join user u on u.user_id = uc.user_id
inner join user_category ucn
on ucn.user_id = u.user_id and ucn.category_id <> uc.category_id
inner join category cn on cn.category_id = ucn.category_id
where c.category = 'Fishing'
group by cn.category
See the demo.
Results:
| category | counter |
| -------- | ------- |
| climbing | 1 |
| drawing | 2 |

With this query, you get your data, if you want a specific hobby, add a where clause for c.category = 'fishing' for example
SELECT
MIN(c.category), COUNT(*), GROUP_CONCAT(`name`)
FROM
user_category uc
INNER JOIN
user u ON uc.user_id = u.user_id
INNER JOIN
category c ON uc.category_id = c.category_id
GROUP BY
uc.category_id
This returns the following result:
MIN(c.category) Count(*) GROUP_CONCAT(`name`)
drawing 3 Bob,Tim,Simon
climbing 1 Dave
hunting 2 Ben,Bob
fishing 3 Tim,Dave,Simon
sleeping 2 Bob,Ben
DBfiddle example
https://dbfiddle.uk/?rdbms=mysql_5.7&fiddle=ad26cf2ab673d349f82d8875022af2aa

Related

Comma separated MYSQL CONCAT from 2 tables

How to you group concat 2 simple tables so the the output is:
John | One, Two
Luke | One, Two
I have seem some other examples, but I don't appear to make sense of it. Im a complete NOOB
users table
|name|names|
|John|001|
|John|002|
|Luke|001|
|Luke|002|
servers table
|id|name|
|001|One|
|002|Two|
MYSQL statement
SELECT *
FROM users
INNER JOIN servers
ON users.names = servers.names
Produces:
John | One
John | Two
Luke | One
Luke | Two
Use GROUP_CONCAT as follows:
SELECT u.name, GROUP_CONCAT(s.name ORDER BY s.id) names
FROM users u
LEFT JOIN servers s ON s.id = u.names
GROUP BY u.name;
Demo

JOIN three tables not quite producing results needed

I have tried for a number of hours to get this. I am still quite new to mysql but have managed to achieve queries that I was impressed with after using the resources and examples I found. I am a bit stuck here. Apologies if I do not ask this very well.
Three tables that are used for managing categories and category membership within a project.
table a = project membership
id user_id project_id
== ======= ==========
1 1 10
2 1 12
3 3 45
4 5 12
table b = categories
id name project_id
== ==== ==========
1 cat1 10
2 cat4 12
3 cat8 45
tabke c = category members
id user_id_added category_id capability
== ============= =========== ==========
1 1 2 1
2 3 3 2
3 5 3 1
4 5 2 0
Required result
members of project 2
user_id category capability_in_category
======= ======== ======================
1 2 1
5 2 0
SELECT a.user_id
, c.capability
, b.id as category
FROM a
LEFT OUTER JOIN b
ON a.project_id = c.project_id
LEFT OUTER JOIN c
ON b.id = c.category_id
WHERE a.project_id = $project_id
AND c.category_id = $category_id;
It feels like I don't need to join the three tables, but I do not see a way of joining the project table with the category membership table without using the category table (b). The query I am running nearly works, but user capability is not returning correct. I am using left outer joins as a member may not always be part of a category, but they still need to be shown as a member in the project. I have been trying various joins and subqueries, without success. I basically need a list of the members in the project and if they are part of a category, to show the capability they have of the specific category. I feel there are a few ways of doing this potentially, but there is a gray area I am struggling to bridge.
The question is vague so I might help you to solve the wrong problem but if you want to have all members of a specific project listed (regardless of their capability) and to list the capabilities in a specified category listed as well, then:
SELECT project_memberships.user_id
, category_members.category_id AS category
, category_members.capability AS capability
FROM project_members
LEFT OUTER JOIN categories
ON project_members.project_id = categories.project_id
LEFT OUTER JOIN category_members
ON categories.id = category_members.category_id
AND category_members.user_id_added = project_membership.user_id
WHERE project_members.project_id = $project_id
AND (categories.id = $category_id OR categories.id IS NULL);
should get you that.
I altered tree things compared to your original query:
I used the table names as they are more speaking than "a, b, c"
I added the additional constraint category_members.user_id_added = project_membership.user_id to the second join so as to not join category_members of a different user to a project_members record.
I loosened the WHERE condition so that members not having the desired capability are also displayed. category and capability will be NULL for those records.
As to your question regarding having to join the three tables the answer is yes, you need to do that.

Display the number of entries in one table, for a key shared by two tables in SQL while also including entries with no place in the other

I'm very new to MySQL, so still learning. Basically, what I currently have is two tables:
Table 1, people, has a name, date of birth, and phone number.
Table 2, meetings, has a name, meeting date, and meeting time.
The entries in "name" in both tables are the same (although the names are mentioned multiple times), although Table 1 has an extra name, Paul, who has no meetings scheduled (so there are no entries of that person in Table 2).
I want to print out a simple, 2 column result showing:
name | number of meetings
david | 3
john | 4
jerry | 5
paul | 0
I am able to print out:
david | 3
john | 4
jerry | 5
using
SELECT name, COUNT(*) as Number_Of_Meetings FROM meetings GROUP BY name;
although this only really uses the name data in meetings so isn't really going to be helpful when trying to print out paul as well because he isn't included.
Using the people table I can also do the much more complicated
SELECT p.name, COUNT(*) as Number_of_Meetings FROM meetings m JOIN people p ON p.name = m.name GROUP BY p.name;
This instead shows the name entries in people but only the ones with an entry in meetings, but I cannot for the life of me figure out how to also show
paul | 0
I've tried so many options but honestly cannot figure it out.
Can somebody help please?
Since you want all records from the people table event when there aren't any matching records in meetings you'll want to use a left join:
SELECT p.name, COUNT(m.name) as Number_of_Meetings
FROM people p
LEFT JOIN meetings m ON p.name = m.name
GROUP BY p.name;

Mysql select between two table without limiting if record appear on the joined table

I have been trying to figure out how to select data related to one id between to tables without limit it to the joined table. I tried using UNION, Inner join, JOIN, but it limit me to show records that are only in both tables. By example:
Table 1 (users)
id | name | register
1 | John | 2014-03-01
2 | Kate | 2014-03-02
etc..
Table 2 (birthdays by example)
id | user | birthday
1 | 1 | 1989-09-09
Note that kate dont have a record on the birthdays table, if i do:
SELECT U.id, name, register, B.birthday FROM users as U INNER JOIN birthday as B ON B.user = U.id
it will only shows JOHN data, i would like to select all my users and if the record do not exist on the joined table, still be able to select all my users, sort of:
id | name | register | birthday
1 | John | 2014-03-01 | 1989-09-09
2 | kate | 2014-03-02 | null or ''
3
4
etc.
Sorry if its a stupid question but i dont find the light on this one. I would appreciate the help.
Regards
You need a LEFT OUTER JOIN instead of the plain JOIN (also known as INNER JOIN), like this:
SELECT U.id, name, register, B.birthday
FROM users as U
LEFT JOIN birthday as B
ON B.user = U.id
A LEFT JOIN between users and birthday tables will contain all records of the "left" table (users), even if the join-condition does not find any matching record in the "right" table (birthday).
This excellent article on The Code Project will help you a lot: Visual Representation of SQL Joins.
Summary of all JOIN types:
Note: Mysql does not support FULL OUTER JOIN but it can be emulated. Useful articles:
https://stackoverflow.com/a/4796911
http://www.sql-tutorial.ru/en/book_full_join_and_mysql.html
http://www.xaprb.com/blog/2006/05/26/how-to-write-full-outer-join-in-mysql/
Use left outer join instead of inner join..
SELECT U.id, name, register, B.birthday
FROM users as U left join birthday as B ON B.user = U.id

COUNT(*) FROM different tables with LEFT JOIN

I have 1 table of users, and 10 tables (articles, news, ...) where I save user's publications. I want to show how many publications has each user, in one query:
| ID_USER | COUNT(id_article) | COUNT(id_news) | etc...
-------------------------------------------------
| 1 | 0 | 3 |
| 2 | 2 | 9 |
| 3 | 14 | 5 |
| 4 | 0 | 0 |
If I use this query to show the number of articles...
SELECT id_user,COUNT(articles.id_article) FROM users
LEFT JOIN articles ON articles.id_user_article=users.id_user
GROUP BY users.id_user
... it shows the information correctly. But if I start to add the second table...
SELECT id_user,COUNT(articles.id_article),COUNT(news.id_news) FROM users
LEFT JOIN articles ON articles.id_user_article=users.id_user
LEFT JOIN news ON news.id_user_news=users.id_user
GROUP BY users.id_user
... it doesn't show the correct information.. and if I join all the rest tables, if shows really strange result (thousands of articles for first user, and NULL for the rest).
Which is the correct way of show this information using only one query? Thank you!
You can use a subselect instead of a left join for each table. The final result will be the same but maybe in that way is clearer.
SELECT u.id_user,
(SELECT COUNT(a.id_article)
FROM articles a
WHERE a.id_user_article = u.id_user) AS articles,
(SELECT COUNT(n.news)
FROM news n
WHERE n.id_user_news = u.id_user) AS news
FROM users u
Also if you only uses one column of each table, the subselect is a better option than multiple left joins.
Your problem is that you are joining along different dimensions, which creates cartesian products for each user. The solution by #rafa is actually a fine solution in MySQL. The use of count(distinct) works okay, but only when the counts are not very large. Another approach is to pre-aggregate the results along each dimension:
SELECT u.id_user, a.articles, n.news
FROM users u left outer join
(select id_user_article, count(*) as articles
from articles
group by id_user_article
) a
on u.id_user = a.id_user_article left outer join
(select id_user_news, count(*) as news
from news
group by id_user_news
) n
on u.id_user = n.id_user_news;
EDIT:
If you are using the count(distinct) approach, then you are generating a cross product. If every user had 3 articles and 4 news items, then the users would be multiplied by 12. Probably feasible.
If every user had 300 articles and 400 news items, then every user would be multiplied by 120,000. Probably not feasible.