MySql - Avoid SET() using junction and foreign keys - php

So here is the problem :
I have a table "Members" with members and their attributes (name, birthday, mail, etc.)
These members may belong to groups (let's say there are 3 groups), from none to all of them. And these groups are referenced in a table ("Groups") so I can add/delete/modify them as I want.
SET() doesn't seem to be a solution, it isn't compatible with foreign keys / reference table.
So at first, I was thinking of doing a TINYINT() column, which I use like SET() : 111 (7) for all groups, 000 (0) for none, 001 (1) for the 1st group , 010 (2) for the 2nd, etc. But since the names are quite complex, it's confusing, and not much more compatible with foreign keys.
I read that I should do a 3rd table "Members-Groups" with memberID and groupID to join both of my two tables, but I don't clearly understand how it work.
What I understand is that I will have a table with IDs of members and groups like this :
+----------+---------+
| memberID | groupID |
+----------+---------+
| 1 | 1 |
| 1 | 2 |
| 2 | 1 |
| 2 | 3 |
| 3 | 2 |
+----------+---------+
and combined with junction I can retrieve what I want. Is it right ? Otherwise can someone explain me how i should do ?
I precise that I'd like to have as final result (after sql request + php script) a member, his attributes and the groups he belongs to in a single row (as with SET()), even members that doesn't belong to any group.

Assuming
drop table if exists mg;
drop table if exists m;
create table m (id int primary key, name varchar(3));
insert into m values
(1,'abc'),
(2,'def'),
(3,'ghi');
drop table if exists g;
create table g(id int primary key ,name varchar(3));
insert into g values
(1,'aaa'),
(2,'bbb'),
(3,'ccc');
create table mg
(memid int,grid int,
index fmid(memid,grid) ,
foreign key (memid) references m(id) on delete cascade,
foreign key (grid) references g(id) on delete cascade
);
insert into mg values
(1,1),(1,2),(1,3),
(2,1),(2,3);
You could join the 3 tables and produce the results using group_concat or conditional aggregation.
MariaDB [sandbox]> select m.id,m.name, group_concat(g.name) groups
-> from m
-> join mg on mg.memid = m.id
-> join g on mg.grid = g.id
-> group by m.id,m.name;
+----+------+-------------+
| id | name | groups |
+----+------+-------------+
| 1 | abc | aaa,bbb,ccc |
| 2 | def | aaa,ccc |
+----+------+-------------+
2 rows in set (0.00 sec)
MariaDB [sandbox]>
MariaDB [sandbox]> select m.id,m.name,
-> max(case when g.id = 1 then g.name else '' end) as group1,
-> max(case when g.id = 2 then g.name else '' end) as group2,
-> max(case when g.id = 3 then g.name else '' end) as group3
-> from m
-> join mg on mg.memid = m.id
-> join g on mg.grid = g.id
-> group by m.id,m.name;
+----+------+--------+--------+--------+
| id | name | group1 | group2 | group3 |
+----+------+--------+--------+--------+
| 1 | abc | aaa | bbb | ccc |
| 2 | def | aaa | | ccc |
+----+------+--------+--------+--------+
2 rows in set (0.00 sec)
If you want members who don't belong to any group change the joins to left joins.
ariaDB [sandbox]> select m.id,m.name, group_concat(g.name) groups
-> from m
-> left join mg on mg.memid = m.id
-> left join g on mg.grid = g.id
-> group by m.id,m.name;
+----+------+-------------+
| id | name | groups |
+----+------+-------------+
| 1 | abc | aaa,bbb,ccc |
| 2 | def | aaa,ccc |
| 3 | ghi | NULL |
+----+------+-------------+
3 rows in set (0.00 sec)
MariaDB [sandbox]>
MariaDB [sandbox]> select m.id,m.name,
-> max(case when g.id = 1 then g.name else '' end) as group1,
-> max(case when g.id = 2 then g.name else '' end) as group2,
-> max(case when g.id = 3 then g.name else '' end) as group3
-> from m
-> left join mg on mg.memid = m.id
-> left join g on mg.grid = g.id
-> group by m.id,m.name;
+----+------+--------+--------+--------+
| id | name | group1 | group2 | group3 |
+----+------+--------+--------+--------+
| 1 | abc | aaa | bbb | ccc |
| 2 | def | aaa | | ccc |
| 3 | ghi | | | |
+----+------+--------+--------+--------+
3 rows in set (0.00 sec)

I feel half-confused by the question, but I'll take a stab at it.
If you have a Members table, then it makes sense to have member_id be the unique primary key. If you want to store which groups each member is in, simply add a new column to the Members table for each Group.
As for the values to be given to columns Group1, Group2, Group3 you could set them as ENUM('0','1') or ENUM('No','Yes') or whatever and make the default value the negative-meaning (first) value.
With this db structure, you won't have to bother chopping up a string during querying -- you just write SELECT or WHERE statements that specify the appropriate Group column value.
If this doesn't directly answer, please clarify your question.

Related

PDO Query : Count related and repeated values then inner join

I work with PHP and PDO.
So I have 2 tables like,
Table 1
| id | name | age |
| 1 | John | 25 |
| 2 | Tom | 32 |
| 3 | James| 45 |
Table 2
| id | Comment | Link |
| 1 | some text | 3 |
| 2 | some text | 3 |
| 3 | some text | 1 |
So, Link column numbers represent id's in table1. For example Link = 3s in table 2 represent James in table 1. I need a query which brings all table1's data and also a number of repeated value for related Link column which comes from table2.
For example, the query should give me (let's choose James),
| id | name | age | Value |
| 3 | James | 45 | 2 |
value=2, because there are two 3s in link column which related to James
I tried somethings but got lots of errors.
I think you just need the GROUP BY
SELECT a.id,
a.name,
a.age,
count(*) as value
FROM table1 a
JOIN table2 b ON a.id = b.link
GROUP BY a.id, a.name, a.age
If you really want just one row then add WHERE
SELECT a.id,
a.name,
a.age,
count(*) as value
FROM table1 a
JOIN table2 b ON a.id = b.link
WHERE a.name = 'James'
GROUP BY a.id, a.name, a.age
or use subquery
SELECT a.id,
a.name,
a.age,
(SELECT count(*) FROM table2 b WHERE a.id = b.link) as value
FROM table1 a
WHERE a.name = 'James'

copying rows in a table into the same table with a different id

Hi I'm trying to copy rows in a table but I need it with a different id, that can be in that table aswell. Both id and second_id are primary key. and foreign keys.
+----+-----------+
| id | second_id |
+----+-----------+
| 1 | 1 |
| 1 | 2 |
| 1 | 3 |
| 2 | 2 |
+----+-----------+
So I need to copy all of second_id of id 1 to the id 2, but and eventually, if there's going to be id 3, copy that aswell.
the result should be
+----+-----------+
| id | second_id |
+----+-----------+
| 1 | 1 |
| 1 | 2 |
| 1 | 3 |
| 2 | 2 |
| 2 | 1 |
| 2 | 3 |
+----+-----------+
Also id is a foreign key, so if I have id 3 copy it aswell just like id 2
Any solutions ?
Use a self-join of the table to get all the combinations of id and second_id. Then use a LEFT JOIN to filter out the combinations that already exist, so you can insert the rest into the table.
INSERT INTO yourTable (id, second_id)
SELECT DISTINCT t1.id, t2.second_id
FROM yourTable AS t1
CROSS JOIN yourTable AS t2
LEFT JOIN yourTable AS t3 ON t1.id = t3.id AND t2.second_id = t3.second_id
WHERE t3.id IS NULL
DEMO
Instead of the LEFT JOIN you could simply use INSERT IGNORE. Since (id, second_id) is the primary key, the duplicates will simply be ignored when inserting.
INSERT IGNORE INTO yourTable (id, second_id)
SELECT DISTINCT t1.id, t2.second_id
FROM yourTable AS t1
CROSS JOIN yourTable AS t2
DEMO

Comparing values of columns from 3 tables - MySQL

following question:
I'm working with 3 tables = actors, movies, roles. I'm trying to find all the movies a given actor, say 'Robin Williams' has been in by comparing the specific actor id since there be may more than one actor with the same name. The actors table has the following relevant columns: first_name, last_name, id - the movies table has columns: id (the movie's id) - and the roles table has: actor_id and movie_id.
Do I JOIN the tables or use UNION? How do I compare columns from different tables when the columns have different names?
Thank you!
Just for reference:
Table actors:
mysql> SELECT *
-> FROM actors;
+--------+--------------------+------------------------+--------+------------+
| id | first_name | last_name | gender | film_count |
+--------+--------------------+------------------------+--------+------------+
| 933 | Lewis | Abernathy | M | 1 |
| 2547 | Andrew | Adamson | M | 1 |
| 2700 | William | Addy | M | 1 |
Table movies:
mysql> SELECT *
-> FROM movies;
+--------+------------------------------+------+------+
| id | name | year | rank |
+--------+------------------------------+------+------+
| 10920 | Aliens | 1986 | 8.2 |
| 17173 | Animal House | 1978 | 7.5 |
| 18979 | Apollo 13 | 1995 | 7.5 |
Table roles:
mysql> SELECT *
-> FROM roles;
+----------+----------+-------------------------------+
| actor_id | movie_id | role |
+----------+----------+-------------------------------+
| 16844 | 10920 | Lydecker |
| 36641 | 10920 | Russ Jorden |
| 42278 | 10920 | Cpl. Dwayne Hicks |
At first I tried setting each check equal to a PHP variable and comparing them but that seemed wrong, then I tried:
mysql> SELECT roles.actor_id, roles.movie_id, movies.id, actors.id
-> FROM roles
-> JOIN movies, actors
-> ON roles.actor_id = actors.id && roles.movie_id =movies.id;
which again does not work.
Finally figured it out..
>SELECT m.name, m.year
-> FROM movies m
->JOIN roles r ON m.id = r.movie_id
->JOIN actors a ON a.id = r.actor_id
->WHERE a.first_name = "whatever actor's first name"
->AND a.last_name = "whatever actor's last name"
This will then give you two columns with the corresponding name and year! hazzah!
First Read this answer - it made it click for me finally after years of unions when should join and vice versa.
In this case you should definitely JOIN as you want the result to act as a single row.
(think of it like this - I want to see Movie, Actor -> together as one result)
PS
You don't need your film count field any more as once you have the joins worked out you can just use MySQL COUNT -> it will make it easier to maintain.
You need a join. Try this:
SELECT A.first_name,A.last_name,A.gender,M.name,M.year,M.rank,R.role
FROM roles R INNER JOIN
Movies M ON R.movie_id = M.movie_id INNER JOIN
Actors A ON R.actory_id = A.id

Query to select from multiple tables

I know that there are several posts about this, but I can't get this to work. I don't have any experience of MySQL "join" or "left", only simple querys.
I've got 3 tables: categories, companies and catcomp
Categories
id | name | ... | ... |
1 | Foo
2 | Bar
Companies
id | name | ... | ...
1 | Company1
2 | Company2
Catcomp (To store multiple categories for one company)
company_id | category_id
1 | 1
1 | 2
2 | 2
I've only got this:
$result = mysql_query("SELECT * FROM categories");
while($row = mysql_fetch_array($result))
{
echo '<input type="checkbox" id="'.$row['id'].'" name="cat[]" value="'.$row['id'].'">'.$row['name'].'<br>
}
This prints out all categories with check boxes.. I want the boxes to be checked for the current company.
Any ideas?
There's probably more elegant solutions but anyway...
DROP TABLE IF EXISTS categories;
CREATE TABLE categories
(id INT NOT NULL AUTO_INCREMENT PRIMARY KEY
,name VARCHAR(12) NOT NULL UNIQUE
);
INSERT INTO categories VALUES
(1 ,'Foo'),(2,'Bar'),(3,'Boo');
DROP TABLE IF EXISTS companies;
CREATE TABLE companies
(id INT NOT NULL AUTO_INCREMENT PRIMARY KEY
,name VARCHAR(12) NOT NULL UNIQUE
);
INSERT INTO companies VALUES
(1,'Company1'),(2,'Company2'),(3,'Company3');;
DROP TABLE IF EXISTS company_category;
CREATE TABLE company_category
(company_id INT NOT NULL,category_id INT NOT NULL,PRIMARY KEY(company_id,category_id));
INSERT INTO company_category VALUES (1 ,1),(1 ,2),(2 ,2);
SELECT o.id company_id
, o.name company_name
, a.id category_id
, a.name cateory_name
, CASE WHEN oa.company_id IS NOT NULL THEN ' checked' ELSE '' END checked
FROM companies o
JOIN categories a
LEFT
JOIN company_category oa
ON oa.company_id = o.id
AND oa.category_id = a.id;
+------------+--------------+-------------+--------------+----------+
| company_id | company_name | category_id | cateory_name | checked |
+------------+--------------+-------------+--------------+----------+
| 1 | Company1 | 1 | Foo | checked |
| 2 | Company2 | 1 | Foo | |
| 3 | Company3 | 1 | Foo | |
| 1 | Company1 | 2 | Bar | checked |
| 2 | Company2 | 2 | Bar | checked |
| 3 | Company3 | 2 | Bar | |
| 1 | Company1 | 3 | Boo | |
| 2 | Company2 | 3 | Boo | |
| 3 | Company3 | 3 | Boo | |
+------------+--------------+-------------+--------------+----------+
http://www.sqlfiddle.com/#!2/11e6e/1
Select c.category_id,c.name as categoriesname,b.name as companyname from catcomp a ,companies b, categories c
where a.company_id = b.id and a.category_id = c.id
use this query
Usin example from Strawberry
SELECT categories.name as catename, companies.name as compname from company_category
INNER JOIN companies on (companies.id=company_category.company_id)
INNER JOIN categories on (categories.id=company_category.category_id)
where company_category.category_id = categories.id and categories.id=1
I want the boxes to be checked for the current company.
You need in company_category table any status or column with check status, and the query stay:
SELECT categories.name as catename, companies.name as compname from company_category
INNER JOIN companies on (companies.id=company_category.company_id)
INNER JOIN categories on (categories.id=company_category.category_id)
where company_category.category_id = categories.id and company_category.status=1
where 'status' is company has been checked or not...
Edit the below query with your table details . I Tried it and its working .
SELECT definition.definition,relation.rtype,word.word FROM definition INNER JOIN relation
ON definition.id = relation.id INNER JOIN word ON definition.id = word.id

GROUP_CONCAT With Nested Set Model

I have an application that uses a nested set model class to organise my data, however I'm trying to write a query that will group_concat my results. I know I need to put some sub select statements somewhere but I can't figure it out!
Here's my structure at the moment:
table: person
-----------+------------+-----------
|Person_ID | Name | Age |
-----------+------------+-----------
| 1 | Mark Vance | 19 |
| 2 | Michael Tsu| 22 |
| 3 | Mark Jones | 29 |
| 4 | Sara Young | 25 |
-----------+------------+-----------
table: person_to_group
----+------------+-----------
|ID | Person_ID | Group_ID |
----+------------+-----------
| 1 | 3 | 1 |
| 2 | 3 | 2 |
| 3 | 1 | 2 |
| 4 | 4 | 3 |
----+------------+-----------
table: groups
----------+--------------+--------------+-------------
|Group_ID | Group_Name | Group_Left | Group_Right |
----------+--------------+--------------+-------------
| 1 | Root | 1 | 6 |
| 2 | Node | 2 | 5 |
| 3 | Sub Node | 3 | 4 |
----------+--------------+--------------+-------------
I need to render something like this with my results:
//Grab the group_IDs for this person and put them in the class tag...
<li class="2 3">Sara Young is in the Sub Node Group</li>
Notice that although Sara is in the Sub Node group, she is still being given the id for Node aswell because she is a child of Node.
The following is the query that I am working with as a starting point.
SELECT *, GROUP_CONCAT( CAST( gg.Group_ID AS CHAR ) SEPARATOR ' ' ) Group_IDs
FROM groups gg
LEFT JOIN person_to_group AS t1 ON gg.Group_ID = t1.Group_ID
LEFT JOIN person AS t2 ON t2.Person_ID = t1.Person_ID
GROUP BY t2.per_ID
ORDER BY t2.Name ASC
Any help would be much appreciated!
Here's how I'd write the query:
SELECT p.Name,
GROUP_CONCAT( g.Group_Name ) AS Group_List,
GROUP_CONCAT( CAST( gg.Group_ID AS CHAR ) SEPARATOR ' ' ) AS Group_ID_List
FROM person AS p
INNER JOIN person_to_group AS pg ON p.Person_ID = pg.Person_ID
INNER JOIN groups AS g ON pg.Group_ID = g.Group_ID
INNER JOIN groups AS gg ON g.Group_Left BETWEEN gg.Group_Left AND gg.Group_Right
GROUP BY p.Name
ORDER BY p.Name ASC
Note that if you group by person name, you also need to GROUP_CONCAT the list of group names. According to your schema, a person could belong to multiple groups, because of the many-to-many relationship.
I also recommend against using SELECT * in general. Just specify the columns you need.
This was little bit interesting as I do programming in both MsSQL and MySql. In SQL I have used function called STUFF. In MySQL you can use a function called INSERT. I tried out the below query in MsSQL. Don't have a MySQL handy to try out my query. If I have time I will post the MySQL version of the query.
DECLARE #person TABLE (Person_ID INT, Name VARCHAR(50), Age INT)
INSERT INTO #person VALUES
(1,'Mark Vance',19),
(2,'Michael Tsu',22),
(3,'Mark Jones',29),
(4,'Sara Young',25)
DECLARE #groups TABLE (Group_ID INT, Group_Name VARCHAR(50), Group_Left INT, Group_Right INT)
INSERT INTO #groups VALUES
(1,'Root',1,6),
(2,'Node',2,5),
(3,'Sub Node',3,4)
DECLARE #person_to_group TABLE (ID INT, Person_ID INT, Group_ID INT)
INSERT INTO #person_to_group VALUES
(1,3,1),
(2,3,2),
(3,1,1),
(4,4,1),
(4,1,1)
SELECT *,STUFF((SELECT ',' + CAST(g.Group_ID AS VARCHAR) FROM #groups g
JOIN #person_to_group pg ON g.Group_ID = pg.Group_ID AND pg.Person_ID = a.Person_ID FOR XML PATH('')) , 1, 1, '' ) FROM #person a
Function: INSERT(str,pos,len,newstr)
Documentation: http://dev.mysql.com/doc/refman/5.0/en/string-functions.html#function_insert

Categories