Existing system
- I have existing Users and Tutor_Details tables in my system.
- There are two types of users - tutors and students. Users and Tutor_Details tables are linked by id_user foreign key.
New requirement
- Every tutor can have some of the following credentials:-
Certified
Experienced
Other
A tutor can have a maximum of 3 and minimum 1 credential for now. For every credential specified, the tutor can add some description too.
Right now there are 3 credentials but later there may be more.
What would be the best way to store the credential info. Tutors may be searched by credentials. While viewing a tutor details, all his credentials should be displayed.
I was thinking about the following structure:-
A new Credentials table like-
CREATE TABLE IF NOT EXISTS `Credentials` (
`id_credential` int(10) unsigned NOT NULL auto_increment,
`credential` varchar(255) default NULL,
PRIMARY KEY (`id_credential`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=7 ;
INSERT INTO `Credentials` (`id_credential`, `credential`) VALUES
(1, 'Certified'),
(2, 'Experienced'),
(3, 'Recent Student'),
(4, 'Other');
If new credentials are added later, they are defined here.
And one new Tutor_credential_map table which will contain one record for every credential of a tutor
CREATE TABLE IF NOT EXISTS `Tutor_Credential_map` (
`id` int(10) NOT NULL auto_increment,
`id_tutor` int(10) NOT NULL,
`id_credential` int(10) NOT NULL,
`description` varchar(255) NOT NULL default '',
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=6 ;
That makes things easy for maintenance point of view, but if I want to fetch all the Tutor info as stored in Tutor_Details table along with all his credentials in a single query I get as many result sets for a tutor as there are credentials. This query:-
select td.id_tutor, tcrm.* from Tutor_Details as td inner join Users as u on td.id_user = u.id_user join Tutor_Credential_map as tcrm on td.id_tutor = tcrm.id_tutor join Credentials as cr on tcrm.id_credential = cr.id_credential where td.id_tutor = 23
Any idea to keep the separate tables as well as fetch a single tutor details record for each tutor with all the credentials info? Or is there a better way?
Thanks
It's the nature of the SQL JOIN that multiple rows will be returned for each credential matches.
The easiest way to handle this would be by processing outside of MySQL using whatever lanuguage/system you are using to run the query in the first place.
As far as I can tell, your structure is just fine!
You said "There are two types of users - tutors and students. Users and Tutor_Details tables are linked by id_user foreign key".
Can there be multiple tutors per user, i believe not. Also can a user be a Tutor and a student.
If in both the above cases the answer is "no" i suggest "User_Credential_map" instead of "Tutor_Credential_map". id_tutor can be id_user.
CREATE TABLE IF NOT EXISTS `User_Credential_map` (
`id` int(10) NOT NULL auto_increment,
`id_user` int(10) NOT NULL,
`id_credential` int(10) NOT NULL,
`description` varchar(255) NOT NULL default '',
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=6 ;
Now to answer your question based on the above obesrvations.
SELECT
Users.*,
Tutor_Details.* ,
User_Credential_map.* ,
Credentials.*
FROM
Users,
Tutor_Details,
User_Credential_map,
Credentials
WHERE
Tutor_Details.id_tutor = 23
AND Tutor_Details.id_user = Users.id_user
AND Tutor_Details.id_credential = Credentials.id_credential
Related
I have a complicated issue but rather than go into the specifics i have simplified it to the following.
Lets say we are trying to build a system, where users of the system can apply for priority levels on various services on a per zip-code basis. This system would have four tables like so...
CREATE TABLE `zip_code` (
`zip` varchar(7) NOT NULL DEFAULT '',
`lat` float NOT NULL DEFAULT '0',
`long` float NOT NULL DEFAULT '0'
PRIMARY KEY (`zip`,`lat`,`long`),
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
CREATE TABLE `user` (
`user_id` int(10) NOT NULL AUTO_INCREMENT
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
CREATE TABLE `service` (
`service_id` int(10) NOT NULL AUTO_INCREMENT
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
CREATE TABLE `service_priority` (
`user_id` int(10) NOT NULL',
`service_id` int(10) NOT NULL',
`zip` varchar(7) NOT NULL,
`priority` tinyint(1) NOT NULL
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
Now lets also say that we have 45000 zip-codes, a few hundred services and a few thousand users, and that no user can have the same priority level as another user for the same service in the same zip code.
I need a query that if given a particular zip code, radius, service, and a user_id will return the highest available priority level for all other zip codes within that radius for that service.
And, also, would like to know any suggestions for restructuring this data.
The problem that i see happening here is as the user base grows, the service_priority table is going to get huge, in theory 45000 rows bigger for every user although in practice probably only 10000 rows bigger.
What can i do to mitigate these problems?
Switch to InnoDB.
zip_code table should probably have PRIMARY KEY(zip) unless you really want multiple rows for a given zip.
"no user can have the same priority level as another user for the same service in the same zip code" -- can be enforced by
service_priority : UNIQUE(service_id, user_id, zip)
Then your query may look something like
SELECT sp.*
FROM ( SELECT b.zip
FROM ( SELECT lat, lng FROM zip_code WHERE zip = '$zip' ) AS a
JOIN zip_code AS b
WHERE ... < $radius
) AS z
JOIN service_priority AS sp
WHERE sp.zip = z.zip
AND sp.user_id = $user_id
AND sp.service_id = $service_id
ORDER BY sp.priority DESC
LIMIT 1
Notes:
The index, above, is also tailored for this query.
The innermost query gets the one lat/lng for the center point.
The middle query focuses on finding the nearby zips. See the tag I added to find many questions discussion how to do that.
The outer query then filters results based on user and service.
Finally, the highest priority row is picked.
I have been going through Tank auth library all day with some success but I could not understand user_profiles table that is created in database.
CREATE TABLE IF NOT EXISTS `user_profiles` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` int(11) NOT NULL,
`country` varchar(20) COLLATE utf8_bin DEFAULT NULL,
`website` varchar(255) COLLATE utf8_bin DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
I tried to google it but there wasn`t huge success ... There are articles about user accounts vs user profiles but there is no information about how it works in database level.
I have managed to adjust library for my needs - user now on registration are required to enter home address and it is saved in database in user table. But now I am doubting myself whether or not to save it under user_profiles for later use.
So the question - what is the reason for user_profiles table and how should I use it?
Use the users table to store the User object information i.e user_id, password etc...
Use the users_profiles table to store information about the User object i.e address information
I've seen a good amount of threads on "how to join more than two tables" but none of those threads seem to solve my problem.
I have three tables:
teams, persons and payfiles
teams Table looks like this:
DROP TABLE IF EXISTS `teams`;
CREATE TABLE IF NOT EXISTS `teams` (
`team_id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(20) DEFAULT NULL,
`leader` varchar(32) NOT NULL,
PRIMARY KEY (`team_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=30;`
persons table:
DROP TABLE IF EXISTS `persons`;
CREATE TABLE IF NOT EXISTS `persons` (
`team_id` int(2) DEFAULT '0',
`hash` varchar(32) NOT NULL,
UNIQUE KEY `hash` (`hash`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
payfiles table:
DROP TABLE IF EXISTS `payfiles`;
CREATE TABLE IF NOT EXISTS `payfiles` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`hash` varchar(32) NOT NULL,
`deals_passed` int(2) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=1298 ;
Tables have much more columns, but I trimmed them for the sake of simplicity.
teams table contains records in the following way:
1,Team 1,afaf7878af78a
The latter is the team leader's unique hash.
The table persons contains all the personal information, and a unique hash,
For instance
John Doe whose hash is afaf7878af78a who also is the leader of Team 1.
The table payfile also has a "hash" column, that table contains all the information needed to compute employees' checks.
The management team want a general vision of how much the company is making. They want to see how much money every single team is bringing in.
The problem I'm facing here, is trying to group the earnings by "teams"
My best try so far is this
SELECT hash, SUM(deals_passed) as deals FROM payfiles JOIN persons ON persons.hash = payfiles.hash GROUP BY payfiles.hash
but I can't see an optimized way to query the database in order to generate a team by team general vision of earnings without changing the structure of the database.
For instance:
John Doe, and Jane Doe belong to "Team 1" and they brought in $500 and $600 respectively.
I want to generate something like:
"Team 1 brought in $1100"
My alternative is to change the structure of the database, and add a new column to the payfile table. Such column would be team_id so I can query it easily, but the database currently has about 10,000 records so that implies updating the 10K records that didn't consider a team_id column, and make a lot of changes to the GUI, something that I don't really want to do, although if that's the easiest and best option I'll do it.
Thanks!
SELECT
teams.name,
SUM(payfiles.deals_passed) AS team_deals_passed
FROM payfiles
LEFT JOIN persons USING (hash)
LEFT JOIN teams USING (team_id)
GROUP BY teams.team_id
You can use SUM() to the get the total, and use GROUP BY for the team to get each total by team.
I have a table that contains all the data about users. Users can however have multiple email adresses so i need to store them in a seperate table.
I want to run searches based on an email adresses that return all the information about the user. (i also want to be able to search the user table by username which is stored in the user table)
What is the best way to store the email adresses in my database??
I dont think introducing a redundant email adress field in the user table is a good idea, but possibly it is??
for now i have a user table linked to the email table with 1:M relationship.
(so email adress contains user_id as part of its primary key)
after i store the email adresses what is the best way to get all the information with mySql query (so please include mySQL in your answer)
I know about JOIN opperation but i dont know how to use it with this search.
You can store your data like that, assuming all emails are unique (I added UNIQUE KEY there)
CREATE TABLE `users` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(50) NOT NULL,
PRIMARY KEY (`id`)
) DEFAULT CHARSET=utf8;
CREATE TABLE `emails` (
`email` varchar(50) NOT NULL,
`user_id` int(10) unsigned NOT NULL,
PRIMARY KEY (`email`),
KEY `user_id` (`user_id`)
) DEFAULT CHARSET=utf8;
You can retrieve data for specific email like that:
SELECT users.*, emails.email
FROM users
JOIN emails ON users.id = emails.user_id
WHERE emails.email = 'some_mail#example.com'
You can also select all emails user have like so:
SELECT users.*, GROUP_CONCAT(emails.email)
FROM users
LEFT JOIN emails ON users.id = emails.user_id
WHERE users.id = 1234
GROUP BY users.id
table user_email with (id_user,email)
after that you can do search by email or user id
THE SQL THAT BUILDS THE TABLES,
--
-- Table structure for table `careers`
--
CREATE TABLE IF NOT EXISTS `careers` (
`career_id` int(11) NOT NULL auto_increment,
`career_name` varchar(75) NOT NULL,
`career_desc` text NOT NULL,
`degree_needed` enum('Yes','No') NOT NULL,
`useful_info` text,
`useful_links` text,
PRIMARY KEY (`career_id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=34 ;
-- --------------------------------------------------------
--
-- Table structure for table `course`
--
CREATE TABLE IF NOT EXISTS `course` (
`course_id` int(11) NOT NULL auto_increment,
`course_type` varchar(75) NOT NULL,
`course_names` text NOT NULL,
`extra_needed` enum('Yes','No') default NULL,
`course_link` varchar(150) NOT NULL,
`grades_grade_id` int(11) NOT NULL,
PRIMARY KEY (`course_id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=87 ;
-- --------------------------------------------------------
--
-- Table structure for table `grades`
--
CREATE TABLE IF NOT EXISTS `grades` (
`grade_id` int(11) NOT NULL auto_increment,
`grade_desc` text NOT NULL,
`careers_career_id` int(11) NOT NULL,
PRIMARY KEY (`grade_id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=87 ;
-- --------------------------------------------------------
An overview of my theory behind the tables, is that each grade is associated with a career and one career can have many grades, from that one course is only associated to one course, but a user may need to do an extra course if the one they pick is not accredited highly enough.
So my question is how do I select the course details for the higher level courses if the user selects a low level course,
for example the user wants to be an electrician, and they have 2 D grades in school this means they can only do a level 2 course, this means that to complete the course they have to do a higher level course. I need to be able to show what the other courses are based on the fact they have selected electrician and a level 2 course, it is worth noting that courses that require extra work have a field 'extra_needed` that is marked as yes.
I cannot for the live or me work out how to get the right data out, I have tried the following,
SELECT *
FROM `course` , `grades` , `careers`
WHERE `course`.`extra_needed` IS NULL
AND `grades`.`grade_id` = `careers`.`career_id`
AND `careers`.`career_id` =6
however this brings back 59 rows of data where as it should bring back 2 rows of data, the other to rows of data that the user could select if they chose the other grade choices.
Looks to me like you are joining on the wrong fields, the relationships look like they would be as follows:
careers.career_id = grades.careers_career_id
grades.grade_id = course.grades_grade_id
so for all courses related to career.career_id = 6 the query would look as follows:
select course.*
from course,
careers,
grades
where course.grades_grade_id = grades.grade_id
and grades.careers_career_id = careers.career_id
and careers.career_id = 6
You would need a more complex query to do what you originally asked though which would involve specifying not only a career_id but also a course_id and then a conditional statement to say whether any further courses are required but I'm not sure if you have all the fields necessary to do this as you would need to know the relationship between the course they have selected and all other courses pertaining to the relevant career. If you simply wish to see all the other courses relating to that career then you would add a line like:
and course.course_id <> (The course they have selected)
If there are only ever two levels of courses then you could add a line like below as if they have selected the higher level it can't satisfy both the last statement and this one whereas if they have selected the lower level both will be true:
and course.extra_needed IS NULL
Replace your query by this one:
SELECT *
FROM careers AS c
LEFT JOIN grades AS g ON g.careers_career_id = c.career_id
LEFT JOIN course AS crs ON crs.grades_grade_id = g.grade_id
WHERE c.career_id =6
AND crs.extra_needed IS NULL
It should work,
Good luck