mySQL column to hold array - mysql

I'm a beginner concerning coding and especially SQL and PHP.
I deal with app. 120 users.
The users can acquire app. 300 different collectible items.
When a user acquires a specific item, I would like the ID number of that particular item to be stored in the row of the user who acquired it, so that there is some information about what items the user already has (and to avoid duplicate items in his possession).
Is there a good way to store such information?
Is it even possible to set a column type to array and store it there?
Please note: I'm not lazy and I've been digging around and searching for the answer for 2 hours. I couldn't find a solution. I know of the rule that one should insert only one piece of information into one cell.

MySQL does not support storing arrays. However, you can use a second table to emulate an array by storing the relation between the users and items. Say you have the table users:
CREATE TABLE users (
user_id SERIAL PRIMARY KEY,
...
);
And you have a table defining items:
CREATE TABLE items (
item_id SERIAL PRIMARY KEY,
...
);
You can relate what items a user has using a table similar to user_items:
CREATE TABLE user_items (
id SERIAL PRIMARY KEY,
user_id BIGINT UNSIGNED NOT NULL,
item_id BIGINT UNSIGNED NOT NULL,
...,
FOREIGN KEY (user_id)
REFERENCES users (user_id),
FOREIGN KEY (item_id)
REFERENCES items (item_id)
);
Then, to determine what items user 123 has acquired, you could use JOINs similar to:
SELECT items.*
FROM users
INNER JOIN user_items
ON user_items.user_id = users.user_id
INNER JOIN items
ON items.item_id = user_items.item_id
WHERE users.user_id = 123; -- Or some other condition.

I assume you have 2 tables for example, users and items. To control which user already has a specific item, i would create an associative table, including the UserID from users and ItemID from items. This way you can now check in your user_items table if the user already has this item.
Here is a small example:
users (UserID is PK):
+--------+----------+
| UserID | UserName |
+--------+----------+
| 1 | Fred |
| 2 | Joe |
+--------+----------+
items (ItemID is PK):
+---------+----------+
| ItemID | ItemName |
+---------+----------+
| 5 | Book |
| 6 | Computer |
+---------+----------+
user_items (ItemID referencing items.ItemID, UserID referencing users.UserID):
+---------+--------+
| ItemID | UserID |
+---------+--------+
| 5 | 1 |
| 6 | 2 |
+---------+--------+

Related

Reduce number of joins in mysql

I have 12 fixed tables (group, local, element, sub_element, service, ...), each table with different numbers of rows.
The columns 'id_' in all table is a primary key (int). The others columns are of datatype varchar(20). The maximum number of rows in these tables are 300.
Each table was created in this way:
CREATE TABLE group
(
id_G int NOT NULL,
name_group varchar(20) NOT NULL,
PRIMARY KEY (id_G)
);
|........GROUP......| |.......LOCAL.......| |.......SERVICE.......|
| id_G | name_group | | id_L | name_local | | id_S | name_service |
+------+------------+ +------+------------+ +------+--------------+
| 1 | group1 | | 1 | local1 | | 1 | service1 |
| 2 | group2 | | 2 | local2 | | 2 | service2 |
And I have one table that combine all these tables depending on user selects.
The 'id_' come from fixed tables selected by the user are recorded into this table.
This table was crate in this way:
CREATE TABLE group
(
id_E int NOT NULL,
event_name varchar(20) NOT NULL,
id_G int NOT NULL,
id_L int NOT NULL,
...
PRIMARY KEY (id_G)
);
The tables (event) look like this:
|....................EVENT.....................|
| id_E | event_name | id_G | id_L | ... |id_S |
+------+-------------+------+------+-----+-----+
| 1 | mater1 | 1 | 1 | ... | 3 |
| 2 | master2 | 2 | 2 | ... | 6 |
This table get greater each day, an now it has about thousunds of rows.
Column id_E is the primary key (int), event_name is varchar(20).
This table has, in addition of id_E and event_name columns, 12 other columns the came from the fixed tables.
Every time than I need to retrieve information on the event table, to turn more readable, I need to do about 12 joins.
My query look like this where i need to retrieve all columns from table event:
SELECT event_name, name_group, name_local ..., name_service
FROM event
INNER JOIN group on event.id_G = group.id_G
INNER JOIN local on event.id_L = local.id_L
...
INNER JOIN service on event.id_S = service.id_S
WHERE event.id_S = 7 (for example)
This slows down my system performance. Is there a way to reduce the number of joins? I've heard about using Natural Keys, but I think this is not a good idea to form my case thinking in future maintenance.
My queries are taking about 7 seconds and I need to reduce this time.
I changed the WHERE clause and this caused not affect. So, I am sure that the problem is that the query has so many joins.
Could someone give some help? thanks a lot...
MySQL has a great keyword of "STRAIGHT_JOIN" and might be what you are looking for. First, each of your lookup tables (id/description) I have to assume already have an index on the ID column since that is primary key.
Your event table is the one you are querying as the primary basis of the details and joining to the lookups per their respective IDs. As long as your WHERE clause applicable to the EVENT table is optimized, such as the ID you are looking for, it SHOULD be virtually instantaneous.
If it is not, then it might be that MySQL is trying to think for you and take one of the secondary lookup tables and make it a primary basis of the query for whatever reason, such as much lower record count. In this case, add the keyword and try it..
SELECT STRAIGHT_JOIN ... rest of your query
This tells MySQL to do the query in the order you gave it, thus the Event table first and it's where clause on the ID. It should find that one thing, then grab all the corresponding lookup descriptions from the other tables.
Create indexes, concretely use compound indexes, for instance, start creating a compound index for event and groups:
on table events create one for (event id, group id).
then, on the group table create another one for the next relation (group id, local id).
on local do the same with service, and so on...

How can I delimit the possible values of a foreign key?

I have three tables in a MySQL DB.
This is the main table with organisation related stuff. Every Organisation has an unique identifier which is also the foreign key in some tables.
org
+------------+-------------+
| org_id | name |
+------------+-------------+
| 1 | a |
| 2 | b |
| 3 | c |
+------------+-------------+
This is the groups table. Organisations can have many groups.
groups
FOREIGN KEY (ORG_ID) REFERENCES ORG (ID);
+------------+-------------+----------+
| ID | org_id | name |
+------------+-------------+ ---------+
| 1 | 1 | Group1 |
| 2 | 2 | Group2 |
| 3 | 2 | Group3 |
+------------+-------------+----------+
And this is the feed table in which I would like to perform an update.
A feed can have only one associated group.
feed
FOREIGN KEY (GROUP_ID) REFERENCES GROUPS (ID);
+------------+-------------+--------------+
| ID | org_id | group_id |
+------------+-------------+ -------------+
| 1 | 1 | 1 |
| 2 | 2 | 2 |
| 3 | 1 | NULL |
| 4 | 2 | 3 |
+------------+-------------+--------------+
So now there is one problem, that i can't solve. When I INSERT or UPDATE a row, I set the groups_id, but this can also be a groups_id which not belongs to the organisation.
This happens, because all ID's in GROUPS are valid FK values. That's a thing I want to avoid. It should only be possible to insert or update a row with a groups_id which has also the same org_id as in feeds.org_id.
As you can see, the data is now fine. But when I try to make this INSERT INTO feed VALUES (4, 2, 1) it were nice to see an error. Yeah, right, I'm missing an lovely error....
It is difficult for me to make an connection between them. There seems one information or method that I'm missing. I've been looking for a lot, but I don't know the words to describe my problem.
So I ask you, could you give me a tip?
EDIT:
All feeds and all groups are related to an organisation, which has an identifier. An organisation can create feeds/messages. When this feeds are not associated with a group, this feed ist public. For special feeds they can create a group. This group is related to this special organisation.
This works and everything is good:
UPDATE feed
SET title = "Title", message = "Message", groups_id = "1"
WHERE id = "1" AND org_id = "1"
But this works also:
UPDATE feed
SET title = "Title", message = "Message", groups_id = "2"
WHERE id = "1" AND org_id = "1"
The problem is, that it is possible to associate a group to a feed (which is associated to org 1), while the group is not associated with the org (group 2 is associated with org 2).
So my thought was, is there a way to solve this through FOREIGN KEY or similar (checks, joins, subqueries). Or should I think about my db design?
I think a composed foreign key solves your problem:
create table agroup (
id int primary key,
orgid int,
UNIQUE (id,orgid)
);
create table feed (
id int primary key,
groupid int,
orgid int,
FOREIGN KEY (groupid, orgid) REFERENCES agroup(id, orgid)
);
insert into agroup values (10, 1), (20, 1), (30, 2), (40, NULL);
insert into feed values (100,10,1), (101, 20, 1);
insert into feed values (102, 40, NULL); # works
insert into feed values (103, NULL, 1); # works as well
# insert into feed values (110,10,2); # yields error "Cannot add or update a child row: a foreign key constraint fails"
Note the UNIQUE(id,orgid), which seems to be necessary. Though I do not understand why agroup(id primary key) is not sufficient to make also agroup(id,orgid) unique, I got a compiler error without this explicit unique(id,orgid)-constraint. Documentation says that the referenced attributes must be indexed. Anyway, your problem should be solved.
EDIT: Extended example, which now demonstrates also the case of NULL-values in referencing attributes.
At least in MySQL, a composite foreign key constraint permits NULL values in the referencing (child) rows, regardless of whether the parent table contain rows with corresponding NULL-values or not. If one inserts a row with NULL-values for foreign-key attributes, the foreign key constraint is simply ignored. Confer mysql foreign key semantics, which says:
"... MySQL essentially implements the semantics defined by MATCH SIMPLE, which permit a foreign key to be all or partially NULL. In that case, the (child table) row containing such a foreign key is permitted to be inserted, and does not match any row in the referenced (parent) table. It is possible to implement other semantics using triggers."

How does stackoverflow find users to give them their notifications?

I'm creating a website like SO. Now I want to know, when I write a comment under Jack's answer/question, what happens? SO sends a notification to Jack, right? So how SO finds Jack?
In other word, should I store author-user-id in the Votes/Comments tables? Here is my current Votes-table structure:
// Votes
+----+---------+------------+---------+-------+------------+
| id | post_id | table_code | user_id | value | timestamp |
+----+---------+------------+---------+-------+------------+
// ^ this column stores the user-id who has sent vote
// ^ because there is multiple Posts table (focus on the Edit)
Now I want to send a notification for post-owner. But I don't know how can I find him? Should I add a new column on Votes table named owner and store the author-id ?
Edit: I have to mention that I have four Posts tables (I know this structure is crazy, but in reality the structure of those Posts tables are really different and I can't to create just one table instead). Something like this:
// Posts1 (table_code: 1)
+----+-------+-----------+
| id | title | content |
+----+-------+-----------+
// Posts2 (table_code: 2)
+----+-------+-----------+-----------+
| id | title | content | author_id |
+----+-------+-----------+-----------+
// Posts3 (table_code: 3)
+----+-------+-----------+-----------+
| id | title | content | author_id |
+----+-------+-----------+-----------+
// Posts4 (table_code: 4)
+----+-------+-----------+
| id | title | content |
+----+-------+-----------+
But the way, Just some of those Post tables have author_id column (Because I have two Posts tables which are not made by the users). So, as you see, I can't create a foreign key on those Posts tables.
What I need: I want a TRIGGER AFTER INSERT on Votes table which send a notification to the author if there is a author_id column. (or a query which returns author_id if there is a author_id). Or anyway a good solution for my problem ...
Votes.post_id should be a foreign key into the Posts table. From there you can get Posts.author_id, and send the notification to that user.
With your multiple Posts# tables, you can't use a real foreign key. But you can write a UNION query that joins with the appropriate table depending on the table_code value.
SELECT p.author_id
FROM Votes AS v
JOIN Posts2 AS p ON p.id = v.post_id
WHERE v.table_code = 2
UNION
SELECT p.author_id
FROM Votes AS v
JOIN Posts3 AS p ON p.id = v.post_id
WHERE v.table_code = 3
Try to avoid storing data that you can get by following foreign keys, so that the information is only stored one place. If you run into performance problems because of excessive joining, you may need to violate this normalization principle, but only as a last resort.

Which method is better way to store information in table?

I am going to store user Likes into database. But I am not sure which one of these 2 methods is better:
in my situation, users can like Posts, Comments and Groups. something like Facebook.
Assume there are 10 million likes for : Posts, Comments and Groups
Method A:
Create a Like table, and add a LikeType field in it:
+--------+----------+--------+
| likeID | LikeType | userID |
+--------+----------+--------+
| 1 | 1 | 1 | // User 1 liked a post
+--------+----------+--------+
| 2 | 2 | 1 | // User 1 liked a comment
+--------+----------+--------+
| 3 | 3 | 1 | // User 1 liked a group
which LikeType includes : 1,2,3
1 = Posts, 2= Comments, 3= Groups
Method B:
Create three separated tables for each one of Posts, Comments and Groups.
in Method A,
Because there are too many likes and it needs an extra condition ( Where status = 1, or 2, or 3 ) to get a Post, Comment or Group likes, which method is better?
UPDATED POST:
users
uid // PK
---------------------------------------
itemTypes
typeID // PK
typeText // comments, groups, posts
---------------------------------------
--------------------------------------- +
posts |
id // PK |
typeID // 1 |
... |
--------------------------------------- +
comments |
id // PK |
typeID // 2 |
... |
--------------------------------------- + Items
groups |
id // PK |
typeID // 3 |
... |
--------------------------------------- +
photos |
id // PK |
typeID // 4 |
... |
--------------------------------------- +
---------------------------------------
likes
uid // FK to user id
itemid // FK to posts, groups, photos, comments id
itemType // FK to itemsTypes.typeID
// select post #50 likes
SELECT count(*) FROM likes WHERE itemid = 50 and itemType = 1
// select comment #50 of user #2
SELECT * FROM likes WHERE itemid = 50 and uid = 2 and itemType = 2
is this a good schema ?
I don't like either of your methods. I would go more normalized. I would have a table for item types, such as comments, groups, posts, etc. Then I would have a table for items. It would have an ItemId as the PK and a FK reference to item types. There would also be a users table. Finally, the likes table would be a many to many relationship between items and users.
As Jan Doggen said, what you're doing with the information is an important consideration. In particular, if you want to be able to ask the question "what things does a given user like", then you will benefit from having all the data in one table -- otherwise, you'd have to have three separate queries to answer that question.
For the case of the question "which people like a given thing", the performance difference between the single-table model and the multiple-table model should be relatively small if your tables are properly indexed (with an index on likeID/likeType, in this case). The multiple-table model will make your application logic more complex, and will be harder to extend in the future when you want to add other things a user might be able to like.

MySQL schema design issues - Normalizing

I'm creating tables for my site using the following design(s)
Design 1
Design 2
Since not every user who register will try the challenge, Design 1 is suited. On insert into third table, table 2 score is updated accordingly. But the user_id field becomes redundant.
Either 0 or NULL values are set for every user in design 2 which still isn't normalized.
What would be the optimal design and how important is normalization or key in an organization?
Edit:
For future people - I had some problems understanding what OP was asking for so read through the comments if you get a little lost. Ultimately, they were looking to store aggregate data and didn't know where to put it or how to make it happen. The solution is basically to use an insert trigger, which is explained near the end of this post.
I chose to just add another column on to the user table to store the accumulated sum of user_problem.score. However, making a new table (with the columns user_id and total_sum) isn't a bad option at all even though it seems to be an excessive use of normalization. Sometimes it is good to keep data that is constantly updated separate from data that is rarely changed. That way if something goes wrong, you know your static data will be safe.
Something else I never touched on are the data concurrency and integrity issues associated with storing aggregate data in general... so beware of that.
I would suggest something like this:
User Table
User_ID - Email - Name - Password - FB_ID
-- holds all the user information
Problem Table
Problem_ID - Problem_Title - Problem_Descr
-- holds all the info on the individual challenges/problems/whatever
User_Problem Table
User_Problem_ID - User_ID - Problem_ID - Score - Completion_Date
-- Joins the User and Problem tables and has information specific
-- to a user+challenge pair
And this assumes that a user can take many challenges/problems. And one problem/challenge can be taken by several users.
To see all the problems by a certain user, you would do something like:
select user.user_id,
user.name,
problem_title,
problem_descr,
user_problem.score,
user_problem.completed_date
from user
join user_problem on user.user_id = user_problem.user_id
join problem on user_problem.problem_id = problem.problem_id
where user.user_id = 123 or user.email = 'stuff#gmail.com'
The lengths for the varchar fields are fairly generic...
create table User(
User_ID int unsigned auto_increment primary key,
Email varchar(100),
Name varchar(100),
Password varchar(100),
FB_ID int
);
create table Problem (
Problem_ID int unsigned auto_increment primary key,
Problem_Title varchar(100),
Problem_Descr varchar(500)
);
create table User_Problem (
User_Problem_ID int unsigned auto_increment primary key,
User_ID int unsigned,
Problem_ID int unsigned,
Score int,
Completion_Date datetime,
foreign key (User_ID) references User (User_ID),
foreign key (Problem_ID) references Problem (Problem_ID)
);
After our conversation from down below in the comments... you would add a column to user:
User Table
User_ID - Email - Name - Password - FB_ID - Total_Score
I gave the column a default value of 0 because you seemed to want/need that if the person didn't have any associated problem/challenges. Depending on other things, it may benefit you to make this an unsigned int if you have a rule which states there will never be a negative score.
alter table user add column Total_Score int default 0;
then... you would use an insert trigger on the user_problem table that affects the user table.
CREATE TRIGGER tgr_update_total_score
AFTER INSERT ON User_Problem
FOR EACH ROW
UPDATE User
SET Total_score = Total_score + New.Score
WHERE User_ID = NEW.User_ID;
So... after a row is added to User_Problem, you would add the new score to user.total_score...
mysql> select * from user;
+---------+-------+------+----------+-------+-------------+
| User_ID | Email | Name | Password | FB_ID | Total_Score |
+---------+-------+------+----------+-------+-------------+
| 1 | NULL | kim | NULL | NULL | 0 |
| 2 | NULL | kyle | NULL | NULL | 0 |
+---------+-------+------+----------+-------+-------------+
2 rows in set (0.00 sec)
mysql> insert into user_problem values (null,1,1,10,now());
Query OK, 1 row affected (0.16 sec)
mysql> select * from user;
+---------+-------+------+----------+-------+-------------+
| User_ID | Email | Name | Password | FB_ID | Total_Score |
+---------+-------+------+----------+-------+-------------+
| 1 | NULL | kim | NULL | NULL | 10 |
| 2 | NULL | kyle | NULL | NULL | 0 |
+---------+-------+------+----------+-------+-------------+
2 rows in set (0.00 sec)
mysql> select * from user_problem;
+-----------------+---------+------------+-------+---------------------+
| User_Problem_ID | User_ID | Problem_ID | Score | Completion_Date |
+-----------------+---------+------------+-------+---------------------+
| 1 | 1 | 1 | 10 | 2013-11-03 11:31:53 |
+-----------------+---------+------------+-------+---------------------+
1 row in set (0.00 sec)