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.
Related
I have a very large dataset of donations to educational projects. I have done some processing and for this question there are three tables of interest: Project, Funding and Category.
Project contains the project ID, some other negligible info (e.g. date started), and the category ID the project belongs to. Projects can belong to one or two categories, and so there are two columns for each project. If a project only belongs to one category, category 2 is NULL. In total there are 8 categories, with category ID's going from 1 to 8.
Funding contains the project ID, some other negligible info (e.g. total cost), and the current status of the project. This is either 'fully funded' or 'expired', as all projects are done.
Category only contains 2 columns, one with the 8 category ID's and the other with the category names (1 - Sports, 2 - Science, etc).
*Project*
project_id category_id1 category_id2
... ... ...
... ... ...
*Funding*
project_id status
... ...
... ...
*Category*
Category_ID project_category
... ...
... ...
I'm now trying to find out for each category the percentage of those fully funded, which would be (fully funded) / (fully funded + expired). However, I can't seem to find a way to make SQL count instances for each category regardless of whether they are in category column 1 or category column 2 of 'Project' table. This is the code I have so far with its output:
SELECT project_category, status, count(project_category)
FROM Project
INNER JOIN Category ON Project.Category_ID1 = Category.Category_ID
INNER JOIN Funding ON Project.project_id = Funding.project_id
GROUP BY project_category, status
project_category status count(project_category)
Applied Learning Expired 4003
Applied Learning Fully Funded 11441
Essentials Expired 16
Essentials Fully Funded 219
Health & Sports Expired 1235
Health & Sports Fully Funded 4518
... .... ...
... .... ...
This output only counts the categories from project.category_id1. I could just make another table for project.category_id2 and add them up manually, but I would rather have it one table. Is there a way to do this?
Thanks for trying to help!!
You can unpivot and then aggregate:
SELECT c.project_category, f.status, count(*)
FROM (SELECT p.project_id1 as project_id, p.Category_ID FROM Project p
UNION ALL
SELECT p.project_id2 as project_id, p.Category_ID FROM Project p
) p JOIN
Category c
ON p.Category_ID = c.Category_ID JOIN
Funding f
ON p.project_id = f.project_id
GROUP BY c.project_category, f.status;
Note that this also introduces table aliases and qualified all column references.
Here is a db<>fiddle.
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
I i have a table named Taxonomy it preaty much holds everything that has to do with the structure of the organization. as a school example table looks like
id | name | type | parent
1 | 2014 | year | null
2 | igcse| dep | 1
3 | kg1 | grade| 2
4 | c1 | class| 3
4 types, upper most is the school year (2014-2015) for example. under it school department, under it grade (grade1,2,3 etc) under it multiple classes.
when i need to get this i run a self join query like this :
SELECT y.name AS year,
d.name AS dep,
g.name AS grade,
c.name AS class,
e.name AS exam
FROM `taxonomys` e
left JOIN `taxonomys` c on c.id = e.parent
left JOIN `taxonomys` g on g.id = c.parent
left JOIN `taxonomys` d on d.id = g.parent
left JOIN `taxonomys` y on y.id = d.parent
where c.type in ('grade','department','class','year')
its working fine except with the so many nulls i get !
example result of query
as you can see, the classes shows correctly with year under year field,
yet on first row, year is under Class field, (shifted 3 cells).
when ever there is a null value is shifted. how can i fix that ?
thanks alot
EDIT
What is this table ?
A variation of Adjacency List Model, I added an extra column named type so that I can identify level of any row without having to retrieve whole path.
Table hold the structure of the school. example
In every new year, they create A Year example "2014-2015", then inside this year creates the school different departments "american diploma, Highschool, playshool, etc.." under that comes the school grades and under every grade come the classes..
Example
id name type parent
1 2014-2015 year null
2 highschool dep. 1
3 grade 10 grade 2
4 grade 11 grade 2
5 class a class 3
6 class b class 3
These inputs means
2014-2015
|__Highschool
|__ grade 10
|__ class a
|__ class b
|__ grade 11
after than another table link students to Class node, and another table link posts to class's and so on.
so this table basically holds the school structures. since years, departments, and grades are only there for organization of the school users "there is no data except names needed for them" i decided to have the all in one table.
Why did you build it in this a very very very bad/anti-pattern design ?
its actually working very nice for me !
(we hv 4 years with all the departments and students and classes, over 100k posts linked to user/class and over 10k users and its working smooth so far !)
I don't know what your output is supposed to be, and I don't know how your query is suppposed to get it, and I don't know how you are encoding what information in your table, but I suspect that the query below gives the result you want, and that it's somewhat redundant (namely the last 4 lines), and that your design is really, really, really an anti-pattern.
/*
rows y.n,d.n,g.n,c.n,e.n
where
taxonomies(y.i,y.n,'year',y.p)
AND taxonomies(d.i,d.n,'dep',y.i) AND taxonomies(g.i,g.n,'grade',d.p)
AND taxonomies(c.i,c.n,'class',g.i) AND taxonomies(e.i,e.n,'exam',c.i)
*/
SELECT y.name AS year,
d.name AS dep,
g.name AS grade,
c.name AS class,
e.name AS exam
FROM `taxonomys` y
JOIN `taxonomys` d on y.id = d.parent
JOIN `taxonomys` g on d.id = g.parent
JOIN `taxonomys` c on g.id = c.parent
JOIN `taxonomys` e on c.id = e.parent
WHERE y.type = 'year'
AND d.type = 'dep'
AND g.type = 'grade'
AND c.type = 'class'
AND e.type = 'exam'
They are shifting to the left and you are doing a left JOIN. That's a clue. I suggest to change it to only join. When you perform a left JOIN you get all the rows from the left table in conjunction with the matching rows from the right table. If there are no columns matching in the right table, it returns NULL values.
The columns shifting places is new to my eyes (not a SQL expert here though) but it seems to me that it could be related with the fact that you are retrieving all the data from the same table taxonomys.
I've done a good bit of research on how to do this and was not able to find anything. I don't think what I'm trying to do is too difficult, but I'm not sure where to go from here and wanted to post this question.
I have two tables, one (DEVICE) is a table of devices and one (USER_DEVICE) is mapping a user to a device (user is assigned a device via this mapping table):
DEVICE USER_DEVICE
id user_id device_id
------ ------- ----------
1 1 1
2 1 2
3 1 5
4 2 3
5
6
I'm trying to create an LEFT OUTER JOIN that will return all the devices from DEVICES currently NOT ASSIGNED to a specific user_id. For example, running this query for user_id 1 should return
DEVICE
id
------
3
4
6
And running this query for user_id 2 should return
DEVICE
id
------
1
2
4
5
6
I have created the following LEFT OUTER JOIN which successfully returns the following
SELECT id FROM DEVICE d LEFT OUTER JOIN USER_DEVICE ud ON d.id = ud.device_id WHERE ud.device_id IS null;
DEVICE
id
------
4
6
I'm not sure where I need to include the user_id=1 statement in the above sql. What I need is something to the effect of:
SELECT id FROM DEVICE d LEFT OUTER JOIN USER_DEVICE ud ON d.id = ud.device_id WHERE ud.user_id=1 AND ud.device_id IS null;
But this returns no rows. Any help on how to do this LEFT OUTER JOIN with a conditional statement looking for a specific user_id would be greatly appreciated! Thanks :)
You want to include the condition on the device in the on clause:
SELECT id
FROM DEVICE d LEFT OUTER JOIN
USER_DEVICE ud
ON d.id = ud.device_id and
ud.user_id = 1
WHERE ud.device_id IS null;
I've got an incredibly convoluted SQL query (three INNER JOINS) that results in an easy to read result set as follows (simplified). I have inherited the db, so it's impossible to change the structure of any of the existing tables, and therefore I have to perform the convoluted query to get to this point:
product_category | product_code
------------------------------------
Hardware 102
Hardware 104
Hardware 102
Software 205
Software 104
If I then simply do a GROUP BY product_category, product_code, I get most of the final result set I'm interested in:
product_category | product_code
------------------------------------
Hardware 102
Hardware 104
Software 205
Software 104
However, what's missing is number in stock:
product_category | product_code | num_in_stock
--------------------------------------------------------
Hardware 102 2
Hardware 104 1
Software 205 1
Software 104 1
Since I want to be able to COUNT() directly from the processing done by the GROUP BY statement, I'm a little lost.
Here is the SQL query thus far:
SELECT categories.product_category, codes.product_code FROM stock
INNER JOIN products ON stock.product_id = products.id
INNER JOIN codes ON products.code_id = codes.id
INNER JOIN categories ON codes.category_id = categories.id
GROUP BY categories.product_category, codes.product_code
The tables are as follows:
CATEGORIES - e.g., "Hardware", "Software"
CODES - e.g., 100, 204 (belongs to a category)
PRODUCTS - combinations of categories + codes, with a useless version #
STOCK - entries of products, if more than one is in stock, there are multiple entries
So the reason this is so messy is because of the useless version # field in PRODUCTS. What this means is that for a particular combo (e.g., "Hardware 102") it can be entered in PRODUCTS multiple times, each with different version # values, which will then cause STOCK to refer to different ids from PRODUCTS, even though, to me, it's the same product. Ugh!
Any ideas?
Edit:
So let's say there's a product "Misc 999" that has two different versions. This means that there will an entry in CATEGORIES of "Misc", in CODES of "999" (with a category_id of that belonging to "Misc"), and two entries in PRODUCTS (both with the same code_id but with different version info - which I'm ignoring).
Then, if we have 10 of these in stock (3 of one version and 7 of the other, but I'm ignoring version info) there will be 10 entries in the STOCK table, each of which will refer to the PRODUCTS table through an id (two different ids, in this case).
Just add count(*) to your select clause:
SELECT categories.product_category, codes.product_code, count(*) qty_in_stock
FROM stock
INNER JOIN products ON stock.product_id = products.id
INNER JOIN codes ON products.code_id = codes.id
INNER JOIN categories ON codes.category_id = categories.id
GROUP BY categories.product_category, codes.product_code
SQLFiddle here.
It's not entirely clear what you want, but perhaps this works:
SELECT categories.product_category
, codes.product_code
, SUM(num_in_stock) as num_in_stock
FROM (
SELECT product_id
, count(*) as num_in_stock
FROM stock
group by product_id
) a
INNER JOIN products
ON a.product_id = products.id
INNER JOIN codes
ON products.code_id = codes.id
INNER JOIN categories
ON codes.category_id = categories.id
GROUP BY categories.product_category
, codes.product_code