MySQL query for multiple tables being secondary tables multiple items? - mysql

I have a query where I currently get information from 2 tables like this:
SELECT g.name
FROM site_access b
JOIN groups g
ON b.group_id = g.id
WHERE b.site_id = 1
ORDER BY g.status ASC
Now I wanted to have another table with this query but this one table would return more then 1 row is that possible at all ?
All I could make was it pull 1 row from that table, the field I want is a string field and it is ok to join the result with a separator too as long as all the matchs can be pulled together in this query.
If you need more information about the tables or anything feel free to say I didnt think it would be needed as this is mostly an example of how to pull multiple rows from a join/select query.
UPDATE of what the above query would result:
Admin
Member
Banned
Now with my 3rd table each access have commands they are allowed to use so this 3rd table would list what commands each one has access to, example:
Admin - add, del, announce
Member - find
Banned - none
UPDATE2:
site_access
site_id
group_id
groups
id
name
status
groups_commands
group_id
command_id
commands
id
name

SELECT g.name, GROUP_CONCAT(c.command) AS commands
FROM site_access b
JOIN groups g
ON b.group_id = g.id
JOIN groups_commands gc
ON g.id = gc.group_id
JOIN commands c
ON gc.command_id = c.id
WHERE b.site_id = 1
GROUP BY g.name
ORDER BY g.status ASC

Related

Retrieving data from 3 Mysql tables

Suppose I have 3 different tables relationships as following
1st is tbl_users(id,gender,name)
2nd is tbl_feeds(id,user_id,feed_value)
3rd is tbl_favs(id,user_id,feed_id)
where id is primary key for every table.
Now suppose I want to get data where those feeds should come which is uploaded by Gender=Male users with one field in every row that should say either the user who is calling this query marked that particular feed as favourite or not.
So final data of result should be like following :
where lets say the person who is calling this query have user_id=2 then is_favourite column should contain 1 if that user marked favourite that particular feed otherwise is_favourite should contain 0.
user_id feed_id feed_value is_favourite gender
1 2 xyz 1 M
2 3 abc 0 M
3 4 mno 0 M
I hope you getting my question , I m able to get feeds as per gender but problem is I m facing problem to get is_favourite flag as per particular user for every feed entry.
I hope some one have these problem before and I can get help from those for sure.
I would be so thankful if some one can resolve my this issue.
Thanks
Something like this should work:
SELECT
u.id AS user_id.
fe.id AS feed_id,
fe.feed_value,
IFNULL(fa.is_favourite, 0),
u.gender
FROM
tbl_users u
JOIN
tbl_feeds fe ON (fe.user_id = u.id)
LEFT JOIN
tbl_favs fa ON (
fa.user_id = u.id
AND
fa.feed_id = fe.id
)
In order to link your tables, you need to find the most common link between them all. This link is user_id. You'll want to create a relationship between all tables with JOIN in order to make sure each and every user has data.
Now I don't know if you're planning on making sure all tables have data with the user_id. But I would use INNER JOIN as it will ONLY show records of that user_id without nulls. If the other tables could POSSIBLY (Not always guaranteed) you should use a LEFT JOIN based on the tables that is it possible with.
Here is an SQLFiddle as an example. However, I recommend you name your ID fields as appropriate to your table's name so that way, there is no confusion!
To get your isFavorite I would use a subquery in order to validate and verify if the user has it selected as a favorite.
SELECT
u.userid,
u.gender,
f.feedsid,
f.feedvalue,
(
SELECT
COUNT(*)
FROM
tbl_favs a
WHERE
a.userid = u.userid AND
a.feedsid = f.feedsid
) as isFavorite
FROM
tbl_users u
INNER JOIN
tbl_feeds f
ON
u.userid = f.userid
~~~~EDIT 1~~~~
In response to your comment, I have updated the SQLFiddle and the query. I don't believe you really need a join now based on the information given. If you were to do a join you would get unexpected results since you would be trying to make a common link between two tables that you do not want. Instead you'll want to just combine the tables together and do a subquery to determine from the favs if it is a favorite of the user's.
SQLFiddle:
SELECT
u.userid,
f.feedsid,
u.name,
u.gender,
f.feedvalue,
(
SELECT
COUNT(*)
FROM
tbl_favs a
WHERE
a.userid = u.userid AND
a.feedsid = f.feedsid
) as isFavorite
FROM
tbl_users u,
tbl_feeds f
ORDER BY
u.userid,
f.feedsid

using JOIN and subquery in mysql

I posted a question about 2 weeks ago about 'one to many' relation between SQL tables. Now I have a bit of a different scenario. Basically, there are two tables - coffee_users and coffee_product_registrations. The latter is connected to coffee_users table with 'uid' column. So basically coffee_users.uid = coffee_product_registrations.uid
A single user can have multiple products registered.
What I want to do is to display some product information (from coffee_product_registrations) along with some user information (from coffee_users), BUT retrieve only those rows that have more than 1 product registrations.
So to simplify, here are the steps I need to take:
Join two tables
Select users that have multiple products registered
Display all their products along with their names and stuff
My current SQL query looks like this:
SELECT c.uid, c.name, cpr.model
FROM coffee_users c
JOIN coffee_product_registrations cpr on c.uid = cpr.uid
GROUP BY c.uid
HAVING COUNT(cpr.uid) > 1
This joins the two tables on 'uid' column but displays only 1 row for each user. It selects just users that have multiple products registered.
Now I need to take these IDs and select ALL the products from coffee_product_registrations based on them.
I cannot figure out how to put this in one query.
Replace cpr.*, c.* with columns which you want to extract feom the query
Try this:
SELECT cpr.*, c.*
FROM coffee_product_registrations cpr
INNER JOIN coffee_users c ON c.uid = cpr.uid
INNER JOIN (SELECT cpr.uid
FROM coffee_product_registrations cpr
GROUP BY cpr.uid
HAVING COUNT(DISTINCT cpr.productId) > 1
) AS A ON c.uid = A.uid;

LEFT JOIN but with WHERE criteria, rows getting lost

I have a simple database with three tables. In the database I have a table for users of my system, a table for applications to a competition, and an intermediary table that allows me to track which users have selected which applications to view.
Table 1 = users (user_id, username, first, last, etc...)
Table 2 = applications (application_id, company_name, url, etc...)
Table 3 = picks (pick_id, user_id, application_id, picked)
I am trying to write an SQL query that will show all the applications that have been submitted and if any individual application has been selected by a user will show that it has been "picked" (1=picked, 0=not picked).
So for user_id = 1 I'd like to see:
Column Names (application_id, company_name, picked)
1, Foo, 1
2, Bar, 1
3, Alpha, Null
4, Beta, Null
I tried it with the following query:
SELECT applications.application_id, applications.company_name, picks.picked
FROM applications
LEFT JOIN picks ON applications.application_id = picks.application_id
ORDER BY applications.application_id ASC
Which is returning this:
1, Foo, 1
1, Foo, 1
2, Bar, null
3, Alpha, null
4, Beta, null
I have a second user (user_id = 2) that also picked application 1 ("Foo") which I know is returning the second row.
Then I tried to limit the scope by specifying user_id = 1 here:
SELECT applications.application_id, applications.company_name, picks.picked
FROM applications
LEFT JOIN picks ON applications.application_id = picks.application_id
WHERE user_id = 1
ORDER BY applications.application_id ASC
Now I'm only getting:
1, Foo, 1
Any suggestions on how I can get what I'm looking for? Again, ideally for a single user I'd like to see:
Column Names (application_id, company_name, picked)
1, Foo, 1
2, Bar, 1
3, Alpha, Null
4, Beta, Null
You have a so-called join table in your database schema. In your case it's called picks. This allows you to create a many-to-many relationship between your users and applications.
To use that join table correctly you need to join all three tables. These queries are easier to write if you use table aliases (applications AS a, etc.)
SELECT a.application_id, a.company_name, p.picked, u.user_id, u.username
FROM applications AS a
LEFT JOIN picks AS p ON a.application_id = p.application_id
LEFT JOIN users AS u ON p.user_id = u.user_id
ORDER BY a.application_id, u.user_id
This will give you a list of all applications with the users who have made them. If no users are related to an application, the LEFT JOIN operations will retain the application row and you'll see NULL values for columns from the picks and users table.
Now, if you add a WHERE p.something = something or u.something = something clause to this query in an attempt to narrow down the presentation, it has the effect of converting the LEFT JOIN clauses into INNER JOIN clauses. That is, you won't retain the applications rows that don't have matching rows in the other tables.
If you want to retain those unmatched rows in your result set, put the condition in the first ON clause instead of the WHERE clause, like so.
SELECT a.application_id, a.company_name, p.picked, u.user_id, u.username
FROM applications AS a
LEFT JOIN picks AS p ON a.application_id = p.application_id AND p.user_id = 1
LEFT JOIN users AS u ON p.user_id = u.user_id
ORDER BY a.application_id, u.user_id
Edit Many join tables like your picks table are set up with a composite primary key, in your example (application_id, user_id). That ensures just one row per possible relationship between the tables being joined. In your case you have the potential for multiple such rows.
To use only the most recent of those rows (the one with the highest pick_id) takes a little more work. You need a subquery (virtual table) to extract it, and to retrieve the appropriate value of picked so your query works. So now things get interesting.
SELECT MAX(pick_id) AS pick_id,
application_id, user_id
FROM picks
GROUP BY application_id, user_id
retrieves the unique relationship pair. That is good. But next we have to fetch the picked column detail value from those rows. That takes another join, using the MAX value of pick_id, like so
SELECT q.application_id, q.user_id, r.picked
FROM (
SELECT MAX(pick_id) AS pick_id,
application_id, user_id
FROM picks
GROUP BY application_id, user_id
) AS q
JOIN picks AS r ON q.pick_id = r.pick_id
So, we need to substitute this little virtual table (subquery) in place of the pick AS p table in the original query. That looks like this.
SELECT a.application_id, a.company_name, p.picked, u.user_id, u.username
FROM applications AS a
LEFT JOIN (
SELECT q.application_id, q.user_id, r.picked
FROM (
SELECT MAX(pick_id) AS pick_id,
application_id, user_id
FROM picks
GROUP BY application_id, user_id
) AS q
JOIN picks AS r ON q.pick_id = r.pick_id
) AS p ON a.application_id = p.application_id AND p.user_id = 1
LEFT JOIN users AS u ON p.user_id = u.user_id
ORDER BY a.application_id, u.user_id
Some developers prefer to create VIEW objects for subqueries like the one here, rather than creating a club sandwich of a query like this one. It's not called Structured Query Language on a foolish whim, eh? These subqueries sometimes can be elements of a structure.

Mysql: join across five tables

I have a mysql database with this setup (omitting fields not relevant to this question)
users
id #primary key
user_group_teachers
id #primary key
teacher_id #foreign key to users.id
user_group_id #foreign key to users_groups.id
user_groups
id #primary key
user_group_members
id #primary key
pupil_id #foreign key to pupils.id
user_group_id #foreign key to users_groups.id
pupils
id #primary key
I have a collection of user ids in an array, called "user_ids".
For each of those user ids, i want to collect the pupil ids associated with that user via the
user -> user_group_teachers -> user_groups -> user_group_members -> pupils
association. Ie, some kind of join across the tables.
So, i'd like to get some kind of result where the rows look like
[1, [6,7,8,9]]
where 1 is the teacher id, and [6,7,8,9] are the ids of pupils. I'd only like each pupil id to appear once in the second list.
Can anyone tell me how to do this in as small a number of queries as possible (or, more broadly, as efficiently as possible). I will probably usually have between 1000 and 10,000 ids in user_ids.
I'm doing this in a ruby script, so can store the results as variables (arrays or hashes) in between queries, if that makes things simpler.
Thanks! max
EDIT for Lyhan
Lyhan - thanks but your solution doesn't seem to work. For example in the first row of the results, using your method, i have
| user_id | group_concat(pupils.id separator ",")
| 1 | 2292
But, if i get the associated pupil ids in a slower, step by step way, then i get different results:
select group_concat(user_group_teachers.user_group_id separator ",")
from user_group_teachers
where user_group_teachers.teacher_id = 1
group by user_group_teachers.teacher_id;
I get
| group_concat(user_group_teachers.user_group_id separator ",")
| 12,1033,2117,2280,2281
Plugging these values (user_group ids) into another query:
select group_concat(user_group_members.pupil_id separator ",")
from user_group_members
where user_group_members.user_group_id in (12,1033,2117,2280,2281)
group by user_group_members.user_group_id;
I get
| group_concat(user_group_members.pupil_id separator ",")
| 47106,47107
Thanks for the group_concat method btw, that's handy :)
I made a couple comments above that are important to the solution for this, but I think you could start with these two queries to see if it gets you far enough along to get what you need.
To get ordered lists for a teacher for pupils across all groups, you could do this:
select distinct t.teacher_id, m.pupil_id
from user_groups g
inner join user_group_teachers t
on t.user_group_id = g.id
inner join user_group_members m
on t.user_group_id = g.id
order by t.teacher_id, m.pupil_id
To get ordered lists for a teacher for pupils with the relationship to group in tact, you could do this:
select g.id, t.teacher_id, m.pupil_id
from user_groups g
inner join user_group_teachers t
on t.user_group_id = g.id
inner join user_group_members m
on t.user_group_id = g.id
order by g.id, t.teacher_id, m.pupil_id
You would have to walk these result sets and transform them into the nested arrays, but it is the data you wanted.
Update: Update: If the data set is too large or you do not want to walk a single result set, then you could do this to emulate the results of the first query above and build your sub-arrays based on query result sets:
/* Use this query to drive the batch */
select distinct t.teacher_id
from user_groups_teachers t
order by t.teacher_id
/* Inside a loop based on first query result, pull out the array of pupils for a teacher */
select distinct m.pupil_id
from user_groups_members m
inner join user_groups g
on g.id = m.user_group_id
inner join user_groups_teachers t
on t.user_group_id = g.id
where t.teacher_id = /* parameter */
order by m.pupil_id
This is what i came up with:
select pupil_group_teachers.teacher_id, group_concat(pupil_group_members.pupil_id separator ',')
from pupil_group_teachers join pupil_groups on pupil_group_teachers.pupil_group_id = pupil_groups.id
join pupil_group_members on pupil_group_members.pupil_group_id = pupil_groups.id
group by pupil_group_teachers.teacher_id;
it seems to work, and is really fast. Lyhan (who has since deleted his answer) and David Fleeman both helped me figure it out. Cheers guys.

MySQL joins and COUNT(*) from another table

I have two tables: groups and group_members.
The groups table contains all the information for each group, such as its ID, title, description, etc.
In the group_members table, it lists all the members who are apart of each group like this:
group_id | user_id
1 | 100
2 | 23
2 | 100
9 | 601
Basically, I want to list THREE groups on a page, and I only want to list groups which have MORE than four members. Inside the <?php while ?> loop, I then want to four members who are apart of that group. I'm having no trouble listing the groups, and listing the members in another internal loop, I just cannot refine the groups so that ONLY those with more than 4 members show.
Does anybody know how to do this? I'm sure it's with MySQL joins.
MySQL use HAVING statement for this tasks.
Your query would look like this:
SELECT g.group_id, COUNT(m.member_id) AS members
FROM groups AS g
LEFT JOIN group_members AS m USING(group_id)
GROUP BY g.group_id
HAVING members > 4
example when references have different names
SELECT g.id, COUNT(m.member_id) AS members
FROM groups AS g
LEFT JOIN group_members AS m ON g.id = m.group_id
GROUP BY g.id
HAVING members > 4
Also, make sure that you set indexes inside your database schema for keys you are using in JOINS as it can affect your site performance.
SELECT DISTINCT groups.id,
(SELECT COUNT(*) FROM group_members
WHERE member_id = groups.id) AS memberCount
FROM groups
Your groups_main table has a key column named id. I believe you can only use the USING syntax for the join if the groups_fans table has a key column with the same name, which it probably does not. So instead, try this:
LEFT JOIN groups_fans AS m ON m.group_id = g.id
Or replace group_id with whatever the appropriate column name is in the groups_fans table.
Maybe I am off the mark here and not understanding the OP but why are you joining tables?
If you have a table with members and this table has a column named "group_id", you can just run a query on the members table to get a count of the members grouped by the group_id.
SELECT group_id, COUNT(*) as membercount
FROM members
GROUP BY group_id
HAVING membercount > 4
This should have the least overhead simply because you are avoiding a join but should still give you what you wanted.
If you want the group details and description etc, then add a join from the members table back to the groups table to retrieve the name would give you the quickest result.