MySQL Storing Unlimited Data within Row - mysql

What is the best way to store data in this scenario:
ACCOUNT table stores id, username, password and a large set of data comprising of multiple rows. Example: multiple sessions stored with date and other information.
What I'm doing now is creating a new table called sessions_id# for each user, but there has to be a more efficient way.

You should be able to create a single sessions table that has a foreign key reference back to the account table. The session table would have columns for an identity, then the user's ID, then the session-related data (date, context, etc.)

You are correct in your assumption that there is a better way. What you need is the "relational" in relational databases.
You need only 1 table for the sessions. This table contains columns for the session data, probably a unique ID (use auto-increment) and most important of all, one field where you store the ID of the user the session belongs to.
This is called a one-to-many relationship, because one user can have many sessions, but each session is coupled with only one user.
If you use MyISAM as the DB engine, you will need to keep track of the IDs manually, but if you choose InnoDB as the engine, you can use foreign keys.
You can read more about foreign keys in the MySQL documentation:
http://dev.mysql.com/doc/refman/5.1/en/innodb-foreign-key-constraints.html

Instead of creating a new table for each user's sessions, create a table SESSION that holds sessions for all users at once. This satisfies a database design principle called "normalisation" that you should read up on. It is more scalable than creating a new table every time a new user signs up.
CREATE TABLE SESSION (
SessionID INT NOT NULL,
AccountID INT NOT NULL,
StartDate DATETIME,
PRIMARY KEY (SessionID),
CONSTRAINT Constr_SESSION_AccountID_fk
FOREIGN KEY SESSION_AccountID_fk REFERENCES ACCOUNT (AccountID)
ON DELETE RESTRICT ON UPDATE RESTRICT
)

Related

Relationships midst two tables?

I have table visitors and table users. Each visitor can be created by only one user and contains field visitors.userId.
So, any another user from table users can edit or delete one or more visitors.
I have created a third table events_log for logging actions like edit/delete:
Visitors_log
id | userId | visitorId | action
This table stores information about user who did actions over table visitors.
Which relationship should be between table visitors_log and rest two: users and visitors?
Now I have this Db scheme:
My opinion is: visitors_log can have one or more rows. So relations is one to many. In the same time visitors_log can contain one or more users. So, where I am wrong? Now it is one to one relation.
Is there a possibility that the user_id of a visitor could ever change ?
If the answer is yes, then you would better take the safe approach and have two foreign keys in the log, referencing the concerned visitor and the user that commited the action.
In MySQL :
CREATE TABLE visitors_log (
idVisitorLog INT AUTO_INCREMENT,
idVisitor INT NOT NULL,
idUser INT NOT NULL,
action VARCHAR(100) NOT NULL,
date_action DATETIME NOT NULL,
PRIMARY KEY (idVisitorLog),
FOREIGN KEY (visitor_log_idVisitor) REFERENCES visitors(idVisitor),
FOREIGN KEY (visitor_log_idUser) REFERENCES users(idUser)
);
PS : you probably want a date column too in the log table, I added one.
On the other hand, if the user a visitor is assigned to can never change over time, then your design can be simplified. In the log table, you could just store a foreign key to the visitors table. As the visitor stores a (fixed) user id, you do not need to duplicate that information in the log.

How to ensure that a foreign key may only appear a limited number of times in a table

I'm trying to create some simple tables. I have one table with a list of members and another that links to the list of members via the foreign key memberNo. Can I constrain the second table to only allow an individual foreign key to appear three times, for example?
CREATE TABLE members(memberNo varchar(6) NOT NULL,
fname varchar(10),
lname varchar(10),
PRIMARY KEY (memberNo));
CREATE TABLE activities(actNo varchar(6) NOT NULL,
type varchar(30),
memberNo varchar(6) NOT NULL,
PRIMARY KEY(actNo),
FOREIGN KEY(memberNo) REFERENCES Members(memberNo),
%CHECK(count(memberNo) < 6));
This is probably very simple, but I can't find a solution, so I'm probably asking the question incorrectly. I know that I don't want memberNo to be UNIQUE in the activities table, but to be allowed to occur only a limited number of times.
There is no really easy way to do this in MySQL. Here are two methods using triggers.
One method is for an insert trigger to count the number of rows in activities for a given member, and not allow updates that exceed the threshold.
A second method is to modify members to have a column with the number of activities -- typically requiring triggers on insert, delete, and possibly update in activities. This value can then be used in the insert trigger on activities to check the value.
Two other methods are possible. If all your data modifications are wrapped in stored procedures, then you can implement these in the stored procedures rather than in the database. When designing systems, I often take this approach, but you have to be very careful that all modifications are controlled.
Finally, if you only want to allow two or three activities, you can have a separate column in members referring to each one. I'm not thrilled with this approach but it does meet the need for small numbers of activities per member.

Comma separated list on MySQL database

I am implementing a friends list for users in my database, where the list will store the friends accountID.
I already have a similar structure in my database for achievements where I have a separate table that has a pair of accountID to achievementID, but my concern with this approach is that it is inefficient because if there are 1 million users with 100 achievements each there are 100million entries in this table. Then trying to get every achievement for a user with a certain accountID would be a linear scan of the table (I think).
I am considering having a comma separated string of accountIDs for my friends list table, I realize how annoying it will be to deal with the data as a string, but at least it would be guaranteed to be log(n) search time for a user with accountID as the primary key and the second column being the list string.
Am I wrong about the search time for these two different structures?
MySQL can make effective use of appropriate indexes, for queries designed to use those indexes, avoiding a "scan" operation on the table.
If you are ALWAYS dealing with the complete set of achievements for a user, retrieving the entire set, and storing the entire set, then a comma separated list in a single column can be a workable approach.
HOWEVER... that design breaks down when you want to deal with individual achievements. For example, if you want to retrieve a list of users that have a particular achievement. Now, you're doing expensive full scans of all achievements for all users, doing "string searches", dependent on properly formatted strings, and MySQL is unable to use an index scan to efficiently retrieve that set.
So, the rule of thumb, if you NEVER need to individually access an achievement, and NEVER need to remove an achievement from user in the database, and NEVER need to add an individual achievement for a user, and you will ONLY EVER pull the achievements as an entire set, and only store them as an entire set, in and out of the database, the comma separated list is workable.
I hesitate to recommend that approach, because it never turns out that way. Inevitably, you'll want a query to get a list of users that have a particular achievement.
With the comma separated list column, you're into some ugly SQL:
SELECT a.user_id
FROM user_achievement_list a
WHERE CONCAT(',',a.list,',') LIKE '%,123,%'
ugly in the sense that MySQL can't use an index range scan to satisfy the predicate; MySQL has to look at EVERY SINGLE list of achievements, and then do a string scan on each and every one of them, from the beginning to the end, to find out if a row matches or not.
And it's downright excruciating if you want to use the individual values in that list to do a join operation, to "lookup" a row in another table. That SQL just gets horrendously ugly.
And declarative enforcement of data integrity is impossible; you can't define any foreign key constraints that restrict the values that are added to the list, or remove all occurrences of a particular achievement_id from every list it occurs in.
Basically, you're "giving up" the advantages of a relational data store; so don't expect the database to be able to do any work with that type of column. As far as the database is concerned, it's just a blob of data, might as well be .jpg image stored in that column, MySQL isn't going to help with retrieving or maintaining the contents of that list.
On the other hand, if you go with a design that stores the individual rows, each achievement for each user as a separate row, and you have an appropriate index available, the database can be MUCH more efficient at returning the list, and the SQL is more straightforward:
SELECT a.user_id
FROM user_achievements a
WHERE a.achievement_id = 123
A covering index would be appropriate for that query:
... ON user_achievements (achievement_id, user_id)
An index with user_id as the leading column would be suitable for other queries:
... ON user_achievements (user_id, achievement_id)
FOLLOWUP
Use EXPLAIN SELECT ... to see the access plan that MySQL generates.
For your example, retrieving all achievements for a given user, MySQL can do a range scan on the index to quickly locate the set of rows for the one user. MySQL doesn't need to look at every page in the index, the index is structured as a tree (at least, in the case of B-Tree indexes) so it can basically eliminate a whole boatload of pages it "knows" that the rows you are looking for can't be. And with the achievement_id also in the index, MySQL can return the resultset right from the index, without a need to visit the pages in the underlying table. (For the InnoDB engine, the PRIMARY KEY is the cluster key for the table, so the table itself is effectively an index.)
With a two column InnoDB table (user_id, achievement_id), with those two columns as the composite PRIMARY KEY, you would only need to add one secondary index, on (achievement_id, user_id).
FOLLOWUP
Q: By secondary index, do you mean a 3rd column that contains the key for the composite (userID, achievementID) table. My create table query looks like this
CREATE TABLE `UserFriends`
(`AccountID` BIGINT(20) UNSIGNED NOT NULL
,`FriendAccountID` BIGINT(20) UNSIGNED NOT NULL
,`Key` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT
, PRIMARY KEY (`Key`)
, UNIQUE KEY `AccountID` (`AccountID`, `FriendAccountID`)
);
A: No, I don't mean the addition of a third column. If the only two columns in the table are the foreign keys to another table (looks like they refer to the same table, and the columns are both NOT NULL and there is a UNIQUE constraint on the combination of the columns... and there are no other attributes on the table, I would consider not using a surrogate as the primary key at all. I would make the UNIQUE KEY the PRIMARY KEY.
Personally, I would be using InnoDB, with the innodb_file_per_table option enabled. And my table definition would look something like this:
CREATE TABLE user_friend
( account_id BIGINT(20) UNSIGNED NOT NULL COMMENT 'PK, FK ref account.id'
, friend_account_id BIGINT(20) UNSIGNED NOT NULL COMMENT 'PK, FK ref account.id'
, PRIMARY KEY (account_id, friend_account_id)
, UNIQUE KEY user_friend_UX1 (friend_account_id, account_id)
, CONSTRAINT FK_user_friend_user FOREIGN KEY (account_id)
REFERENCES account (id) ON UPDATE CASCADE ON DELETE CASCADE
, CONSTRAINT FK_user_friend_friend FOREIGN KEY (friend_account_id)
REFERENCES account (id) ON UPDATE CASCADE ON DELETE CASCADE
) Engine=InnoDB;

how to create sql table with foreign keys

I am wanting to create a sqlDB on my server, I am very new to db development and have only done the odd bit of coding here an there several years ago. I am trying to build a db that has four tables that reference each other..
This is what each of the tables are created for to give you a better idea as to what I am trying to achieve.
Group
this table will store hubs which users create for other users to follow.
User
stores users details which are used when creating groups and joining groups
//Not sure if this next one is a good idea but hopefully someone can let me know what they thing about it
userGroups
this table stores all or the groups and user connections. When a user joins a group their id is stored with the group id.
Countries
countries are used to help restrict the number of results that can be returned
heres the code I have created that I would like to run on my DB.. I am not sure if I have made the foreign keys correctly, any advice on that would be appreciated also.
CREATE DATABASE tempDB
USE tempDB
CREATE TABLE Group (groupID UInt32 PRIMARY KEY, userID UInt32 references User(SID), country VARCHAR(20) references Country(SID));
CREATE TABLE User (userID UInt32 PRIMARY KEY, name VARCHAR(20), country VARCHAR(20) references Country(SID));
CREATE TABLE userGroups (userID references User(SID), groupID UInt32 references Group(SID));
CREATE TABLE countries (countryName VARCHAR(20) PRIMARY KEY);
so three questions:
Is the table UserGroups worth while?
Have I created the foreign Keys correctly?
Will this code create a DB with the 4 tables in them...
If your users can be in many groups and many users can be in a group drop the column and foreign key reference to the userid field in the group table. The usergroups table handles this many-to-many mapping.
Your current setup would only make sense if say each group had also a single 'owner' user...
Also, what DB engine are you using? Innodb or myisam... If myisam foreign keys are jest like any other index without any ability to enforce constraints, therefore not really useful to try and declare.
Before creating any table which has a foreign key that table should be created.
Country(SID)- there is no table Country and field SID
Countries table should have a id field as primary key.
To use foreign keys you should specify the engine =INNODB
To check if tables have been created with Forign key
mysql> Show tables // Shows all created table
mysql> desc tableName // to see the structure of the created table

MySQL index design with table partitioning

I have 2 MySQL tables with the following schemas for a web site that's kinda like a magazine.
Article (articleId int auto increment ,
title varchar(100),
titleHash guid -- a hash of the title
articleText varchar(4000)
userId int)
User (userId int autoincrement
userName varchar(30)
email etc...)
The most important query is;
select title,articleText,userName,email
from Article inner join user
on article.userId = user.UserId
where titleHash = <some hash>
I am thinking of using the articleId and titleHash columns together as a clustered primary y for the Article table. And userId and userName as a primary key for the user table.
As the searches will be based on titlehash and userName columns.
Also titlehash and userName are unqiue by design and will not change normally.
The articleId and userid columns are not business keys and are not visible to the application, so they'll only be used for joins.
I'm going to use mysql table partitioning on the titlehash column so the selects will be faster as the db will be able to use partition elimination based on that column.
I'm using innoDB as the storage engine;
So here are my questions;
Do I need to create another index on
the titlehash column as the primary
key (articleId,titlehash) is not
good for the searches on the
titlehash column as it is the second
column on the primary key ?
What are the problems with this
design ?
I need the selects to be very fast and expects the tables to have millions of rows and please note that the int Id columns are not visible to the business layer and can never be used to find a record
I'm from a sql server background and going to use mysql as using the partitioning on sql server will cost me a fortune as it is only available in the Enterprise edition.
So DB gurus, please help me; Many thanks.
As written, your "most important query" doesn't actually appear to involve the User table at all. If there isn't just something missing, the best way to speed this up will be to get the User table out of the picture and create an index on titleHash. Boom, done.
If there's another condition on that query, we'll need to know what it is to give any more specific advice.
Given your changes, all that should be necessary as far as keys should be:
On Article:
PRIMARY KEY (articleId) (no additional columns, don't try to be fancy)
KEY (userId)
UNIQUE KEY (titleHash)
On User:
PRIMARY KEY (userId)
Don't try to get fancy with composite primary keys. Primary keys which just consist of an autoincrementing integer are handled more efficiently by InnoDB, as the key can be used internally as a row ID. In effect, you get one integer primary key "for free".
Above all else, test with real data and look at the results from EXPLAINing your query.