SQL Database Design | Roles relationship between managers and group - mysql

I need some help to design correctly a SQL database. Here is what I want :
**Group A :**
- Alice (Manager)
- Bob
- Carol
**Group B :**
- Dave (Manager)
- Eve
- Mallory
**Group C :**
- Oscar (Manager)
- Trudy
- Isaac
**Group Z**
- Admin (Manager)
- Alice
- Dave
- Oscar
A few restrictions :
1) Each member can see the planning of the people in their group. For
example when Bob logs in, he can consults Alice and Carole planning
added to his own.
2) Manager can update the timetable for the people in their group but not their own! So
Alice can manage Bob and Carol, Dave can manage Eve and Mallory, Oscar
manages Trudy and Isaac and finally Admin manages Alice, Dave and
Oscar planning.
3) It has been agreed that Alice would help Dave to manage his group,
so Alice can manage Eve and Mallory (but not Dave), but Eve and
Mallory can't see Alice's planning as she still belongs to group A.
4) Admin is the only manager that can update his own planning. Also he
can consults everyone planning but only people in their group can see
his planning (So Alice, Dave and Oscar basically)
5) User can be in multiple group (look at Alice, Dave and Oscar that
are also in group Z). They will have the choice to consult the
planning of a specified group when they will log in so it is not an issue.
I would have done something like :
TABLE User
id
nom
prenom
login
password
userGroup
TABLE group
id
groupName
groupManager
But this will obviously not work unless I could set an Array as a value for groupManager (I'm not good at SQL, but I am pretty sure it's impossible, isn't it?)
Could someone guide me to something that would allow me to reach what I expect ?

Since your design requires a many-to-many relationship between users and groups (a group contains multiple users; a user can be in multiple groups), I would use something like this:
User
(
ID
, Name
, Login
, Password
, etc.
)
Group
(
ID
, Name
, etc.
)
Group_Role
(
ID
, Role_Description
, etc.
)
User_Group_Role
(
ID
, User_ID -- foreign key to User.ID
, Group_ID -- foreign key to Group.ID
, Group_Role_ID -- foreign key to Group_Role.ID
)
So your Users table will contain
1 | Alice
2 | Bob
3 | Carol
4 | Dave
5 | Eve
6 | Mallory
99 | Admin
, your Groups table will contain
1 | Group A
2 | Group B
99 | Group Z
, your Group_Roles table will contain things like
1 | Consult
2 | Manage -- cannot manage self
3 | Assist
4 | SuperManage -- can manage self
, and finally, the User_Group_Role table:
1 | 1 | 1 | 2 -- Alice manages Group A
2 | 2 | 1 | 1 -- Bob consults in Group A
3 | 3 | 1 | 1 -- Carol consults in Group A
4 | 4 | 2 | 2 -- Dave manages Group B
5 | 1 | 2 | 3 -- Carol assists with Group B
6 | 5 | 2 | 1 -- Eve consults in Group A
7 | 6 | 2 | 1 -- Mallory consults in Group A
etc.
Does that make sense?

Related

Count groups in table but if any record in 2 group is same then consider it as one group only

I am woking on production database in my organisation. I have come across one scenario. Here is the sample data which I generated. Actual data is similar only, Just changed the field names.
My table structure is similar to this,
tableAi - unsigned bigint(20) - Auto increment index
loginDetails - varchar(200)
loginType - tinyint(4) (2- gmail, 3 facebook)
studentid - unsigned bigint(20)
tableAi | loginDetails | loginType | studentId
1 | abc#gmail.com | 2 | 333
2 | abc#facebook.com | 3 | 333
3 | xyz#facebook.com | 3 | 444
4 | xxx#gmail.com | 2 | 444
5 | test#gmail.com | 2 | 555
6 | abc#facebook.com | 3 | 555
7 | ac#facebook.com | 3 | 666
8 | ac#gmail.com | 2 | 777
9 | abc#facebook.com | 3 | 777
I want to count total number of students (that is very simple). But here my requirement is if loginDetail is same for 2 students then consider them as one student.
So, from above example StudentId 333, 555 and 777 have same facebook email id. So, when I count number of students, I have to consider these 2 student Ids as 1 only even though gmail account is different. So, even if one login details is same, for 2 persons, I want to treat those 2 persons as 1 person only. In production data, there is such data also that I have to consider 4-5 personIds as one person only based on their login details.
So, for above sample table, I need to generate query which returns total number of students as 3. (not 5).
Distinct student Ids will be (333,555,777) , 444 and 666
some query like tis will give you the output:
SELECT
count(*), -- only for test
GROUP_COUNCAT(t.studentId) AS stundents,
t.loginDetails,
MAX(t.loginType) as loginType,
t.studentId
FROM tableAi t
GROUP BY t.loginDetails
HAVING count(*) = 2;

How To Design A Database for a "Check In" Social Service

I want to build a "check in" service like FourSquare or Untappd.
How do I design a suitable database schema for storing check-ins?
For example, suppose I'm developing "CheeseSquare" to help people keep track of the delicious cheeses they've tried.
The table for the items into which one can check in is fairly simple and would look like
+----+---------+---------+-------------+--------+
| ID | Name | Country | Style | Colour |
+----+---------+---------+-------------+--------+
| 1 | Brie | France | Soft | White |
| 2 | Cheddar | UK | Traditional | Yellow |
+----+---------+---------+-------------+--------+
I would also have a table for the users, say
+-----+------+---------------+----------------+
| ID | Name | Twitter Token | Facebook Token |
+-----+------+---------------+----------------+
| 345 | Anne | qwerty | poiuyt |
| 678 | Bob | asdfg | mnbvc |
+-----+------+---------------+----------------+
What's the best way of recording that a user has checked in to a particular cheese?
For example, I want to record how many French cheeses Anne has checked-in. Which cheeses Bob has checked into etc. If Cersei has eaten Camembert more than 5 times etc.
Am I best putting this information in the user's table? E.g.
+-----+------+------+--------+------+------+---------+---------+
| ID | Name | Blue | Yellow | Soft | Brie | Cheddar | Stilton |
+-----+------+------+--------+------+------+---------+---------+
| 345 | Anne | 1 | 0 | 2 | 1 | 0 | 5 |
| 678 | Bob | 3 | 1 | 1 | 1 | 1 | 2 |
+-----+------+------+--------+------+------+---------+---------+
That looks rather ungainly and hard to maintain. So should I have separate tables for recordings check in?
No, don't put it into the users table. That information is better stored in a join table which represents a many-to-many relationship between users and cheeses.
The join table (we'll call cheeses_users) must have at least two columns (user_ID, cheese_ID), but a third (a timestamp) would be useful too. If you default the timestamp column to CURRENT_TIMESTAMP, you need only insert the user_ID, cheese_ID into the table to log a checkin.
cheeses (ID) ⇒ (cheese_ID) cheeses_users (user_ID) ⇐ users (ID)
Created as:
CREATE TABLE cheeses_users
cheese_ID INT NOT NULL,
user_ID INT NOT NULL,
-- timestamp defaults to current time
checkin_time DATETIME DEFAULT CURRENT_TIMESTAMP,
-- (add any other column *specific to* this checkin (user+cheese+time))
--The primary key is the combination of all 3
-- It becomes impossible for the same user to log the same cheese
-- at the same second in time...
PRIMARY KEY (cheese_ID, user_ID, checkin_time),
-- FOREIGN KEYs to your other tables
FOREIGN KEY (cheese_ID) REFERENCES cheeses (ID),
FOREIGN KEY (user_ID) REFERENCES users (ID),
) ENGINE=InnoDB; -- InnoDB is necessary for the FK's to be honored and useful
To log a checkin for Bob & Cheddar, insert with:
INSERT INTO cheeses_users (cheese_ID, user_ID) VALUES (2, 678);
To query them, you join through this table. For example, to see the number of each cheese type for each user, you might use:
SELECT
u.Name AS username,
c.Name AS cheesename,
COUNT(*) AS num_checkins
FROM
users u
JOIN cheeses_users cu ON u.ID = cu.user_ID
JOIN cheeses c ON cu.cheese_ID = c.ID
GROUP BY
u.Name,
c.Name
To get the 5 most recent checkins for a given user, something like:
SELECT
c.Name AS cheesename,
cu.checkin_time
FROM
cheeses_users cu
JOIN cheeses c ON cu.cheese_ID = c.ID
WHERE
-- Limit to Anne's checkins...
cu.user_ID = 345
ORDER BY checkin_time DESC
LIMIT 5
Let's define more clearly, so you can tell me if I'm wrong:
Cheese instances exist and aren't divisible ("Cheddar/UK/Traditional/Yellow" is a valid checkinable cheese, but "Cheddar" isn't, nor is "Yellow" or "Cheddar/France/...)
Users check into a single cheese instance at a given time
Users can re-check into the same cheese instance at a later date.
If this is the case, then to store fully normalized data, and to be able to retrieve that data's history, you need a third relational table linking the two existing tables.
+-----+------------+---------------------+
| uid | cheese_id | timestamp |
+----+-------------+---------------------+
| 345 | 1 | 2014-05-04 19:04:38 |
| 345 | 2 | 2014-05-08 19:04:38 |
| 678 | 1 | 2014-05-09 19:04:38 |
+-----+------------+---------------------+
etc. You can add extra columns to correspond to the cheese data, but strictly speaking you don't need to.
By putting all this in a third table, you potentially improve both performance and flexibility. You can always reconstruct the additions to the users table you mooted, using aggregate queries.
If you really decide you don't need the timestamps, then you'd replace them with basically the equivalent of a COUNT(*) field:
+-----+------------+--------------+
| uid | cheese_id | num_checkins |
+----+-------------+--------------+
| 345 | 1 | 15 |
| 345 | 2 | 3 |
| 678 | 1 | 8 |
+-----+------------+--------------+
That would dramatically reduce the size of your joining table, although obviously there's less of a "paper trail", should you need to reconstruct your data (and possibly say to a user "oh, yeah, we forgot to record your checkin on such-a-date.")
The entities 'User' and 'Cheese' have a many-to-many relationship. A user can have multiple cheeses he checked into, and a cheese can have multiple people that checked into it.
The only right way to design this in a relational database is to store it into a separate table. There are many reasons why storing it into the user table for instance, is a very bad idea. Read up on normalizing databases for more info on this.
Your table should look something like this:
CheckIns(CheeseId, UserId, (etc...))
Other useful columns might include date or rating, or whatever you want to store about a particular relationship between a user and a cheese.

Optimising table design or optimising the query

I am trying to decide which one is better: to design a table that wastes a lot of space and has a simple query OR to write a very tight table but then the process of finding what I am looking for would be very processing intense.
The actual problem is this:
Imagine you have a very simple table. 1st column for the ID number the 2nd is a list of names and the 3rd is a list of names too. The 2nd column is a list of people who owe to the people in the 3rd column.
The search should do the following:
I search for a name in the 3rd column and see who owes this person in the 2nd column. A name or multiple names come up, then I want to see who owes them, again a bunch of names come up, and so on to level 5.
Maybe this is a well known scheme for which there is a well known simple answer in table design or MySQL circles. Could anybody suggest a MySQL query or perhaps an appropriate table design where I can use a simple query?
Example
ID owes owned to
1 Peter John
2 John George
3 Abdul George
4 George Anna
So I could design a wasteful table like this
ID 1 2 3 4 5
1 Anna George Abdul
2 Anna George John Peter
3 George Abdul
4 George John Peter
5 John Peter
But this would be very wasteful and bad bad design but it would be very easy to access the data along with the hierarchy and the owing chain.
Something like this seems suitable:
people
+----+--------+
| id | name |
+----+--------+
| 1 | Marty |
| 2 | Steven |
| 3 | John |
+----+--------+
With the table building the relationships between people owed and owing:
loans
+-----------+-------------+
| lender_id | borrower_id |
+-----------+-------------+
| 1 | 2 |
| 1 | 3 |
| 2 | 1 |
+-----------+-------------+
You could get all the people owing a given lender with something as simple as:
SELECT people.id, people.name
FROM loans
INNER JOIN people ON people.id = loans.borrower_id
WHERE loans.lender_id = X
Where X is the id of the lender. Given the lender_id of 1 (Marty) for example would yield:
+----+--------+
| id | name |
+----+--------+
| 2 | Steven |
| 3 | John |
+----+--------+
You can repeat this process for each of the resulting people until there are no results (no one being owed).

How to write a proper If...Else Statement with JOIN in MySQL?

I'm quite a beginner in MySQL I just know the totally basic statements, however now I'ts time for me to get into some more difficult, but worth stuff.
I actually have 3 tables in MySQL, here is the representation:
users:
user_id | name | country
---------------------------
1 | Joseph | US
2 | Kennedy | US
3 | Dale | UK
admins:
admin_id | name | country
----------------------------
1 | David | UK
2 | Ryan | US
3 | Paul | UK
notes:
id | n_id | note | comment | country | type | manager
----------------------------------------------------------------
1 | 3 | This is the 1st note | First | US | admin | 2
2 | 2 | This is the 2nd note | Second | US | user | 1
3 | 2 | This is the 3rd note | Third | UK | user | 2
Now I would like to execute something like this SQL (I'm going to type not real commands here, because I'm not really familiar with all of the SQL expressions):
IF notes.type = admin
THEN
SELECT
notes.note,
notes.comment,
notes.country,
admins.name,
admins.country
FROM notes, admins
WHERE notes.n_id = admin.admin_id
ELSEIF notes.type = 'user'
SELECT
notes.note,
notes.comment,
notes.country,
users.name,
users.country
FROM notes, users
WHERE notes.n_id = users.user_id
I hope you understand what would I like to achieve here. I could do this easily with more SQL statements, but I would like to try some query which doesn't use that much resources.
Edit 1:
I would like to Get all of the Notes and get which usergroup has submitted it than apply the user's name to it. I mean, if the admin submitted the note, than SQL should choose the ID from the Admin table (as per the type value) but if a User submitted the note, it should get the name from the Users table.
The result should look something similar to this:
result:
------
id | note | comment | country | name
--------------------------------------------------------
1 | This is the 1st note | First | US | Paul
2 | This is the 2nd note | Second | US | Kennedy
3 | This is the 3rd note | Third | UK | Kennedy
Edit 2:
I have actually forgot to mention, that all of these should be listed to a manager. So a 'manager ID' should be added to the Notes and list all of the notes where the manager is for example: 2.
Here is a method that you can do in one query:
SELECT n.note, n.comment, n.country,
coalesce(a.name, u.name) as name, coalesce(a.country, u.country) as country
FROM notes n left join
admins a
on n.n_id = a.admin_id and n.type = 'admin' left join
users u
on n.n_id = u.user_id and n.type = 'user';
This uses left join to bring the records together from both tables. It then chooses the matching record for the select.
To select a particular manager, remove the semicolon and add:
where n.manager = 2;
If you expect admins and users in one result you have got several options. The simplest way is to make a union select like this:
SELECT
notes.note,
notes.comment,
notes.country,
admins.name,
admins.country
FROM
notes join admins on notes.n_id = admin.admin_id
WHERE
notes.manager = 2
UNION ALL
SELECT
notes.note,
notes.comment,
notes.country,
users.name,
users.country
FROM
notes join users on notes.n_id = users.user_id
WHERE
notes.manager = 2

Join three Tables to create an Access Granted List

I have three tables I would like to join to pull off a MySQL Query that will check to see if a user has access to download photo. Each photo has its own access rights which can be one of the following.
Level one - any user can download the photo.
Level two - only users who have access can download the photo.
Table 1: FILES
FILE_ID is AUTO_INCREMENT
USER_ID is the identifier of the user that owns the file.
FILE_NAME is just the name of the photo.
FILE_ACCESS is the access rights to the 2 levels noted above.
FILE_ID | USER_ID | FILE_NAME | FILE_ACCESS
1 | 3 | 1279141923.jpg | 1
2 | 3 | 1279141925.jpg | 1
3 | 3 | 1279141927.jpg | 2
4 | 4 | 1279141929.jpg | 1
5 | 4 | 1279141931.jpg | 2
6 | 3 | 1279141933.jpg | 2
Table 2: USERS
USER_ID is AUTO_INCREMENT
USER_NAME is just the name of the file owner.
USER_ID | USER_NAME
1 | jack
2 | jill
3 | john
4 | mike
Table 3: ACCESS
ACCESS_ID is AUTO_INCREMENT
USER_ID is the identifier of the user that owns the file.
ALLOW_ID is the identifier of the user that has access all the file uploaded by user.
ACCESS_ID | USER_ID | ALLOW_ID
1 | 3 | 1
2 | 3 | 4
User Jack has access to download photos 1279141923.jpg, 1279141925.jpg and 1279141927.jpg that John has uploaded while Jill only has access to 1279141923.jpg and 1279141925.jpg
Jack gets access to all three as files 1 and 2 have access level one while he also gets access to download file 3 seeing that John has given him full access to all files uploaded by John.
SELECT a.file_name, b.user_name FROM files AS a
JOIN users AS b ON a.user_id = b.user_id
WHERE a.file_access = '1'
This MySQL Query gives me the following when Jill is signed in.
1279141923.jpg, john (owner name)
1279141925.jpg, john
1279141929.jpg, mike
I am now looking to introduce the ACCESS table into this Query, so that if Jill is signed in she is displayed with the same results as above will if Jack was signed he would get the following results.
1279141923.jpg, john (owner name)
1279141925.jpg, john
1279141927.jpg, john
1279141929.jpg, mike
1279141933.jpg, john
I hope I explain this right, as I sure need help with the last portion. I am not quite sure how to add this to the current Query. I can do it using multiple queries - but I prefer if possible would like to leave it as one.
SELECT DISTINCT(F.id), F.filename
FROM files F, ACCESS A
WHERE F.flie_access=1
OR (F.user_id=2)
OR (F.user_id = A.user_id AND A.allow_id=2);
So, you can access the file if it is:
level 1 (ie public)
or you are the owner
or you have a matching access entry in the 'ACCESS' table.
You seem to have granted access to ALL a users files to another user, you might want to consider a "USER_ACCESS" and a "FILE_ACCESS" table to grant other people access to either all your files or just a specfific file.
Tim, here is the modified query...
SELECT F.file_name, U.user_name
FROM files AS F
INNER JOIN users AS U USING (user_id)
LEFT OUTER JOIN access AS A USING (user_id)
WHERE (A.allow_id = 1 AND F.file_access IN (1, 2))
OR (A.allow_id IS NULL AND F.file_access = 1)
ORDER BY F.file_id
With multi-table JOINs, you have to be careful because the memory footprint of such join operations are significantly large, particularly if the tables are large -- which I am assuming they are (or will be as soon as your app picks up lot of user traffic). Sometimes, separating the query is not such a bad thing.
Just my 2 cents... HTH
Ashwin