In MySQL, how to "join" three tables - mysql

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.

Related

How to best categorize values in a table

I'm in the process of designing a new database for a project at work. I want to create a table that stores Assignments for a digital classroom. Each Assignment can be one of 2 categories: "Individual" or "Group".
The first implementation that comes to mind is the following:
CREATE TABLE `assignments` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`title` varchar(255) DEFAULT NULL,
`category` varchar(10) NOT NULL DEFAULT 'individual',
PRIMARY KEY (`id`),
KEY `category_index` (`category`(10))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
I would then select all assignments of a given category with:
SELECT title FROM assignments WHERE category = "individual"
However, because we've had performance issues in the past, I'm trying to optimize the design as much as possible. As such, I'm wondering whether or not storing the category as a VARCHAR is a good idea (considering the table will get quite large)? Would indexing an INT perform better over a VARCHAR?
Aside from just performance, I'm also curious what would be considered a good solution from a design-perspective. Suggestions?

When to put payments in their own SQL table?

I have a system where users posts data and they can upgrade their post by optionioally paying to upgrade. This is the information I want to store from stripe on their payment response:
CREATE TABLE IF NOT EXISTS `db`.`pay` (
`payments_id` int(11) NOT NULL AUTO_INCREMENT
payment, unique index',
`stripe_id`
`card_id`
`brand`
`amount`
`created`
`currency`
`paid`
`refunded`
`exp_month`
`exp_year`
`last4`
`country`
`fingerprint`
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci COMMENT='payments';
Should this be in the same table as the one containing the main post data, or should it be a separate and linked table. What logic is used to make this decision?
On one hand it seems nice to separate it but then you also have the overhead of linking the tables. Only one payment will ever be associated with one post.
Card data should be in a different table with a userId that links to your user table.
With limited knowledge of what you're trying to achieve I would say you need at least 3 tables.
User Table
Post Table with userId linking back to user table
Payment Card Table with userId linking back to user table

Best way with relation tables

I have a question about tables and relations tables ...
Actually, I have these 3 tables
CREATE TABLE USER (
ID int(11) NOT NULL AUTO_INCREMENT,
NAME varchar(14) DEFAULT NULL
);
CREATE TABLE COUNTRY (
ID int(11) NOT NULL AUTO_INCREMENT,
COUNTRY_NAME varchar(14) DEFAULT NULL
);
CREATE TABLE USER_COUNTRY_REL (
ID int(11) NOT NULL AUTO_INCREMENT,
ID_USER int(11) NOT NULL,
ID_COUNTRY int(11) NOT NULL,
);
Ok, so now, 1 user can have one or more country, so, several entries in the table USER_COUNTRY_REL for ONE user.
But, my table USER contains almost 130.000 entries ...
Even for 1 country by user, it's almost 10Mo for the USER_COUNTRY_REL table.
And I have several related tables in this style ...
My question is, is it the fastest, better way to do?
This would not be better to put directly in the USER table, COUNTRY field that contains the different ID (like this: "2, 6, ...")?
Thanks guys ;)
The way you have it is the most optimal as far as time constraints go. Sure, it takes up more space, but that's part of space-time tradeoff - If you want to be faster, you use more space; if you want to use less space, it will run slower (on average).
Also, think of the future. Right now, you're probably selecting the countries for each user, but just wait. Thanks to the magic of scope creep, your application will one day need to select all the users in a given country, at which point scanning each user's "COUNTRY" field to find matches will be incredibly slow, as opposed to just going backwards through the USER_COUNTRY_REL table like you could do now.
In general, for a 1-to-1 or 1-to-many correlation, you can link by foreign key. For a many-to-many correlation, you want to have a relation table in between the two. This scenario is a many-to-many relationship, as each user has multiple countries, and each country has multiple users.
Why not try like this: Create table country first
CREATE TABLE COUNTRY (
CID int(11) NOT NULL AUTO_INCREMENT,
COUNTRY_NAME varchar(14) DEFAULT NULL
);
Then the table user:
CREATE TABLE USER (
ID int(11) NOT NULL AUTO_INCREMENT,
NAME varchar(14) DEFAULT NULL,
CID Foreign Key References CID inCountry
);
just Create a Foreign Key relation between them.
If you try to put this as explicit relation , there will lot of redundancy data.
This is the better approach. You can also make that Foreign Key as index . So that the databse retrieval becomes fast during search operations.
hope this helps..
Note : Not sure about the exact syntax of the foreign key

mysql query help, getting values from table using relational id's from another table

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

Mysql table architecture suggestion needed

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