PHP and MySQL many-to-many question - php

Still wrapping my head around SQL and PHP, but hope someone can help with this:
I have the following tables:
1.
user table
- id
- name
- email
2.
user_group table
- user_id
- group_id
3.
group table
- id
- group_name
There is a many-to-many relationship between the user table and the group table. Now what I am trying to do build a browse users page which lists all the users in the system along with the groups that they belong to, so the page would look something like this:
Name: John Doe
Groups: football, tennis, swimming
Name: Jane Doe
Groups: hockey, basketball
Name: Jim Doe
Groups: hockey, football, rugby
etc. etc.
To accomplish this, I have the following SQL:
SELECT `user`.name, `group`.name
FROM `user`, `user_group`, `group`
WHERE `user`.id = `user_group`.user_id
AND `group`.id = `user_group`.group_id
GROUP BY `user`.id, `group`.id
which returns results as follows:
1. John Doe | football
2. John Doe | tennis
3. John Doe | swimming
4. Jane Doe | hockey
5. Jane Doe | basketball
etc. etc.
As you can see, the results returned need to be manipulated in order to produce the comma separated groups shown earlier, as .
Is there a simple way to get the page to display the groups so that they are in a comma separated list for each user in MySQL? Or do I have to write PHP code to loop through the results looking for duplicate IDs and generating the comma-separated lists of groups on the page? Or am I doing something completely wrong in my approach?
Many thanks.

There are a few options (in order of my personal preference).
Don't group by user id, and iterate trough your result and create an multi dimensional array using the user id as a key.
Use GROUP_CONCAT, which isn't pretty.
Use separate queries for selecting all groups + users, and iterate to create an multi dimensional array.

It makes no sense to have group_id in your user table if it is many-to-many and you already have a connecting table.
From PHP I guess you use MySQL, so you can use GROUP_CONCAT in situations like this.
Anyway, you are querying a hierarchical structure, which can not gracefully flatted to a single table, so you will always have to do some PHP coding to get the hierchical structure back.

try with GROUP_CONCAT with INNER JOIN
SELECT user.name, GROUP_CONCAT(group.name) FROM user
INNER JOIN user_group ON user.id = user_group.user_id
INNER JOIN group ON group.id = user_group.group_id
GROUP BY user.name

You're in luck. MySQL has a very handy aggregation operator group_concat which allows you to collapse the grouped results into a single row. In your case it would go something like this:
SELECT
`user`.name,
GROUP_CONCAT(`group`.group_name)
FROM `user`
INNER JOIN `user_group` ON (`user_group`.user_id = `user`.user_id)
INNER JOIN `group` ON (`group`.group_id = `user_group`.group_id)
GROUP BY `user`.name

This will match your table structure :)
SELECT `user`.name, GROUP_CONCAT( `group`.group_name )
FROM `user`
INNER JOIN `user_group` ON ( `user_group`.user_id = `user`.id )
INNER JOIN `group` ON ( `group`.id = `user_group`.group_id )
GROUP BY `user`.name
HTH :)

Related

cross referencing table columns

I am trying to select the names from one table column and then ignore if any rows have the same member as the user id in a different column.
for example i would like to select all the "groupNames" from groups then check the "members" column for any members that match the user.
members groupName
mike test
andy test
eric runners
erica test
If the user was "mike", I would like the list to ignore any row that had the "groupName" test as "mike" also has that as a group name and the list should only display runners.
Is this possible? I have tried to research it but not even sure what I need to search for?
Does this do what you want?
select t.*
from groups t
where t.groupName <> (select t2.groupName from groups t2 where t2.members = 'Mike');

MySQL Design Clarification

I'm new to MySQL but have a pretty solid background in a wide variety of programming languages.
Right now I'm designing my first database from the ground up. I understand the basic functionality of MySQL tables and what a relational database is but I'm having trouble wrapping my head around a few things so I'm posting this for help (searching hasn't worked, the terms I've been using are too general and I don't know how to narrow it down). Here's where I'm stuck:
I want to pull Facebook data, specifically interests ("likes") and user location.
If this were some OO language I would just create a data structure for Users with all of the info in it (Facebook ID, interests as an array, location) but I'm not sure how to recreate this in MySQL.
Right now I'm thinking of creating a users table with
Facebook ID (primary key?)
Name
Location
Join date
Then creating an interests table with
Interest name (index, sorted alphabetically?)
Maybe a count of users with this interest
Foreign key that relates back to each user
I think this is where my lack of understanding comes in. How do I replicate the concept of a list or array in MySQL in a way that allows me to map each interest to each user who has "liked" that interest?
Any other suggestions, thoughts, or directions to good tutorial sites are greatly appreciated. I'm a tactile learner so getting my hands dirty with a tutorial would be great for me, I just haven't found one that covers this yet.
You could use a third table that would link the interests table to the user table. There would only be a record if the user liked that interest:
Table User_Interest:
Interest_ID
User_ID
To get a list of a user with all of their interests:
SELECT u.Name, i.Interest_Name
FROM Users AS u
INNER JOIN User_Interest AS ui ON ui.User_ID = u.ID
INNER JOIN interests AS i ON i.ID = ui.Interest_ID
WHERE u.Name = 'Tom Jones'
To get a list of a particular interest and all users that liked:
SELECT u.Name, i.Interest_Name
FROM Users AS u
INNER JOIN User_Interest AS ui ON ui.User_ID = u.ID
INNER JOIN interests AS i ON i.ID = ui.Interest_ID
WHERE i.Interest_Name = 'Hiking'
This type of setup is called a "many to many" relation. To do this, you will need 3 tables. 2 tables which contain your data and the last table a mapping table.
Here is an example of a many to many relation ship where the two objects are Students and Courses
Example
Your user table looks like this:
(ID_User, User_Name, ...)
Your interest table looks like this:
(ID_Interest, Interest_Name, ...)
Now your mapping table will look like this:
(ID_User, ID_Interest)
Sample Data
Now lets put some data in the tables
User_Table
------------------------
ID_user | Username | ...
------------------------
1 John
2 Mark
3 Foo
4 Bar
Interest_Table
----------------------------------
ID_Interest | Interest_Name | ...
----------------------------------
001 Pop
002 Rock
003 Alternative
004 Rap
User_To_Interest (Mapping Table)
---------------------------
ID_user | ID_Interest
---------------------------
1 001
1 003
3 002
2 004
4 001
Examining this
Ok so here if you analyze what has been set up, we are mapping users to interests. The mapping table sort of adds wires from the User object to an interest object.

Grouping by Column with Dependence on another Column

Here is a simplified look at the problem I am trying to cleanly solve via a MySQL query. This is not the actual table I am dealing with.
If I have the following table:
Name Buyer ID
John Fred 4
John Smith 3
Fred Sally 2
John Kelly 1
I would like a query to return the following:
Name Buyer ID
John Fred 4
Fred Sally 2
Such that we group by 'name' and show the latest row / buyer / ID.
I tried to implement this by performing a nested select statement, wherein I first performed "ORDER BY ID DESC" then, on the outermost SELECT, "GROUP BY NAME". And, while this is a roundabout way of solving the problem, it seemed that, by virtue of the ordering, the correct selection would be returned to me. Unfortunately, "GROUP BY" does not 'guarantee' that the 'Buyer' column will contain the expected entry.
Any helpful suggests for implementing this as a query? At the moment, I have a highly-inefficient PHP 'version' of the query running on a large table dump - definitely not the best choice.
Try this one, the idea behind the subquery is that it gets the latest ID for each Name using MAX (aggregate function). Then join it against the table itself on the two columns of the subquery.
SELECT a.*
FROM tableName a
INNER JOIN
(
SELECT name, MAX(ID) maxID
FROM tableName
GROUP BY name
) b ON a.Name = b.Name AND
a.ID = b.MaxID
SQLFiddle Demo
Another alternative is to load the data sorted in a subquery, then group on the results. I can't cite this, but I've read in a few places there's no (discernable) performance hit on this.
So something like:
SELECT *
FROM (
SELECT *
FROM `yourtable`
ORDER BY `id` DESC
) as `tmp`
GROUP BY `name`

Trying to display 2 tables data

I have 2 tables
Table name Dist.
NAME|Phone|ID
----------------
Oakley|555-555|1
Maui|666-666|2
lux|777-7777|3
Table name Patientacct.
Name|prescription|id|orderfrom
-------------------------------
bob|20-20|1|oakley
billy|15-20|2|Oakley, Maui
kim|20-20|3|Lux
I'm looking for a display like
Oakley
--------------------
Bob
Billy
Maui
------------------
Billy
Lux
--------------
kim
Trials so far
SELECT *
FROM `dist`
JOIN `patientacct` ON patientacct.dist LIKE CONCAT('%', `dist`.name ,'%')
GROUP BY `dist`.name
This showed only 1 dist
If I drop the group by example:
SELECT *
FROM `dist`
JOIN `patientacct` ON patientacct.dist LIKE CONCAT('%', `dist`.name ,'%')
I get the record twice so what I need is somewhere between the two. I'm extremely new to joins so be easy when explaining.
you must change your tables structure and you need to add a relation table with this values:
orders:
dist_id, patientacct_id PRIMARY_KEY(dist_id, patientacct_id)
First of all, please make the table structure relational. Have a primary auto_increment value in each table and use the primary key from the Dlist foreign key to Patientacct. This way, a simple join would fetch the record you have wanted.
Regarding the display, what do you mean you don't want record twice. Since you are using join, you will get two records for Oakley with the patient Bob and Billy. That's what you wanted and it would be like that by a simple join too in a relational table.

SQL Left Join - same results multiple times

I have the following tables:
users
id | name
info
id | info | user_id
(user_id from table info is foreign key => connects to id from table users)
It is possible that the user will have many entries in the info table (same user_id in many rows).
Now I want to fetch data and display it like that:
username:
... info ....
Example:
admin:
aaa
bbb
ccc
someuser:
ddd
eee
fff
So I use LEFT JOIN like that:
SELECT users.name, info.info
FROM users
LEFT JOIN info
ON users.id = info.user_id
But what I get is the following:
admin:
aaa
admin:
bbb
admin:
ccc
How can i display the username only once? I've tried using DISTINCT keyword after SELECT but it didn't help. Meanwhile i solve this problem inside the php code itself, but is there any way to fix this only inside the sql?
SELECT users.name, GROUP_CONCAT(info.info SEPARATOR 'whateveryouwant') as info
FROM users
INNER JOIN info ON (users.id = info.user_id)
GROUP BY users.name
By default group_concat uses a , as separator, but you can change that if needed.
Link:
http://dev.mysql.com/doc/refman/5.5/en/group-by-functions.html#function_group-concat
$lastname="";
$res=mysql_query($yourQuery);
while($row=mysql_fetch_assoc($res)){
if($row['name']!=$lastname){
$lastname=$row['name'];
print $lastname.':'; // first time print it
}
print $row['info'];
}

Categories