Mysql relating two tables with functions - php

I have two tables one contains a list of teams, the other contains a list of schedules for these teams and score results. I want to be able to update the teams table when I insert a row into the schedules table. So for example if I have:
Teams table
Team1
Team2
.
Schedules table
team1, 3
team2, 1
what I want to be able to do is when the score fields are updated I need to update different fields in the teams table. So when I update this schedule row,
It would insert 3 into one of the rows for team1, and 1 for the team2
Also I would like to be able to calculate the difference between the scores and also insert this into the teams table.
What would be the best way to implement this?
And I would need a function in order to do the comparison of the scores right?
Thanks,
So far I thought of doing something like this.
update table teams set teams.gamesplayed = teams.gameplayed +1 /*this would add one to the games played field because they just played.*/
then for the goals scored something like
update teams set teams.gf =
(
select t.goalsscored
from schedule t, teams s
where t.teamname = s.team1name AND )
)

you need add ON UPDATE and ON INSERT triggers to Teams table - thats all.
In trigger you can do anything you need. Of course you can create standalone function and call it from triggers.

Related

JOIN query too slow on real database, on small one it runs fine

I need help with this mysql query that executes too long or does not execute at all.
(What I am trying to do is a part of more complex problem, where I want to create PHP cron script that will execute few heavy queries and calculate data from the results returned and then use those data to store it in database for further more convenient use. Most likely I will make question here about that process.)
First lets try to solve one of the problems with these heavy queries.
Here is the thing:
I have table: users_bonitet. This table has fields: id, user_id, bonitet, tstamp.
First important note: when I say user, please understand that users are actually companies, not people. So user.id is id of some company, but for some other reasons table that I am using here is called "users".
Three key fields in users_bonitet table are: user_id ( referencing user.id), bonitet ( represents the strength of user, it can have 3 values, 1 - 2 - 3, where 3 is the best ), and tstamp ( stores the time of bonitet insert. Every time when bonitet value changes for some user, new row is inserted with tstamp of that insert and of course new bonitet value.). So basically some user can have bonitet of 1 indicating that he is in bad situation, but after some time it can change to 3 indicating that he is doing great, and time of that change is stored in tstamp.
Now, I will just list other tables that we need to use in query, and then I will explain why. Tables are: user, club, club_offer and club_territories.
Some users ( companies ) are members of a club. Member of the club can have some club offers ( he is representing his products to the people and other club members ) and he is operating on some territory.
What I need to do is to get bonitet value for every club offer ( made by some user who is member of a club ) but only for specific territory with id of 1100000; Since bonitet values are changing over time for each user, that means that I need to get the latest one only. So if some user have bonitet of 1 at 21.01.2012, but later at 26.05.2012 it has changed to 2, I need to get only 2, since that is the current value.
I made an SQL Fiddle with example db schema and query that I am using right now. On this small database, query is working what I want and it is fast, but on real database it is very slow, and sometimes do not execute at all.
See it here: http://sqlfiddle.com/#!9/b0d98/2
My question is: am I using wrong query to get all this data ? I am getting right result but maybe my query is bad and that is why it executes so slow ? How can I speed it up ? I have tried by putting indexes using phpmyadmin, but it didn't help very much.
Here is my query:
SELECT users_bonitet.user_id, users_bonitet.bonitet, users_bonitet.tstamp,
club_offer.id AS offerId, club_offer.rank
FROM users_bonitet
INNER JOIN (
SELECT max( tstamp ) AS lastDate, user_id
FROM users_bonitet
GROUP BY user_id
)lastDate ON users_bonitet.tstamp = lastDate.lastDate
AND users_bonitet.user_id = lastDate.user_id
JOIN users ON users_bonitet.user_id = users.id
JOIN club ON users.id = club.user_id
JOIN club_offer ON club.id = club_offer.club_id
JOIN club_territories ON club.id = club_territories.club_id
WHERE club_territories.territory_id = 1100000
So I am selecting bonitet values for all club offers made by users that are members of a club and operate on territory with an id of 1100000. Important thing is that I am selecting club_offer.id AS offerId, because I need to use that offerId in my application code so I can do some calculations based on bonitet values returned for each offer, and insert data that was calculated to the field "club_offer.rank" for each row with the id of offerId.
Your query looks fine. I suspect your query performance may be improved if you add a compound index to help the subquery that finds the latest entry from users_botinet for each user.
The subquery is:
SELECT max( tstamp ) AS lastDate, user_id
FROM users_bonitet
GROUP BY user_id
If you add (user_id, tstamp) as an index to this table, that subquery can be satisfied with a very efficient loose index scan.
ALTER TABLE users_bonitet ADD KEY maxfinder (user_id, tstamp);
Notice that if this users_botinet table had an autoincrementing id number in it, your subquery could be refactored to use that instead of tstamp. That would eliminate the possibility of duplicates and be even more efficient, because there's a unique id for joining. Like so.
FROM users_botinet
INNER JOIN (
SELECT MAX(id) AS id
FROM users_botinet
GROUP BY user_id
) ubmax ON users_botinet.id = ubmax.id
In this case your compound index would be (user_id, id.
Pro tip: Don't add lots of indexes unless you know you need them. It's a good idea to read up on how indexes can help you. For example. http://use-the-index-luke.com/

Update rows while counting rows in other tables

I would like to know if its possible to solve the following action with just one SQL-statement:
Table A: User-Table
Table B: Entries with foreign-keys for users
The relation between table A and Table B is 1 -> *
Because I don't want to count entries in table B for a specific user every time, I want to keep a column for this count in the user table. For that I would need a query that updates this column for each user.
Is this possible?
You can try this: UPDATE USERS U SET COUNT_ENTRIES=(SELECT COUNT(*) FROM ENTRIES WHERE USERID=U.ID)

Is it possible to have a cronjob to keep running an SQL query where multiple rows match in same table?

I have two tables, 'pendingresults' and 'confirmedresults'
For a football site I am trying to create a report score page which allows two users with a team to report a score against each other after they have played, as an example i'll use team1 and team2..
So lets say team1 beat team2, 3 goals to 1 (3-1) the manager of team1 would goto his reportscore page and insert the score and select that he had played team2, a query of this would put the results into the 'pendingresults' table which contains:
id, TeamA, TeamAScore, TeamBScore, TeamB
so team1 adds the score and the query is successful so now the pendingresults table looks a little like this:
id TeamA TeamAScore TeamBScore TeamB
1 team1 3 1 team2
I have the above coded and working..
What I need help with is once team2 reports the score as a 3-1 loss, the pendingresults table will have two of the exact same rows (other than the id), how would i make it so that every few hours something like a cronjob runs to check if two rows match then make a query run to insert the teams and scores into the 'confirmedresults' table, which has the exact same columns as 'pendingresults' and then delete the two rows from the pendingresults table.
Thanks.
I suggest another approach:
If a team manager A wants to enter the results, your script first checks whether the other team's manager B already entered results. If this is the case, A simply confirms or corrects the results entered by B.
With your approach and a typo in the scores by either A or B, you will not get a match (of the records) at all.
Just to improve this idea further, you simply have one table called results with an additional column confirmed. If A enters the results, you set "A" to confirmed. If B they does not agree and updates the scores, you set "B" to confirmed. A then checks and agrees, so you set "AB" to confirmed.
All results with confirmed set to "AB" then are confirmed match results all others are pending/unconfirmed match results.

How can I make this database schema better?

I currently have two tables in which stores the attendances of a student in a course. I have the hub_attendance table which stores the total attendances of a student and the hub_attendance_lesson where it stores the attendance of each lesson that a student has or has not attended. I'm not sure if this is correct or if I'm doing anything wrong, I'm a beginner in databases!
hub_attendance:
id
student_id
course_id
total_lessons
total_lessons_so_far
total_attended
total_absent
total_excused_absent
total_late
total_excused_late
hub_attendance_lesson:
id
lesson_id
course_id
student_id
date
attended
absent
excused_absent
late
excused_late
EDIT:
So I've gotten rid of the first table completely and this is my new single table.
Hub_Attendance:
id
lesson_id
course_id
student_id
date
attendance
As Dutchie432 said, you don't need the first table because it introduces unnecessary redundancy and you can count those statistics on the fly. Such aggregate tables can be a good solution if performance is an issue, but they should be used only as a last resort.
About the second table - you have separate fields attended, absent, excused_absent,
late and excused_late. Aren't these mutually exclusive? So only one of them can be true for one row? If so, you may be better off with one enumeration field called for example attendance, which would take different values for each of those states. In that way you could't have rows where none of the flags, or more than one flag, is set.
Here's what you need:
**Course**
id, name, etc...
**Lesson**
id, courseid, name, etc...
**Attendance**
id, studentid, lessonid, lateness, etc...
**Enrolment**
id, courseid, studentid, startdate, etc...
You need the enrolment table to know that students should be on a course even if they never turn up for lessons. The attendance table will allow you to have many students per lesson and many lessons per student. This is a many-to-many table. Any aggregation and counting can be done in SQL.
If I understand your schema correctly, Your first table can be totally eliminated. You should be able to fetch the totals using MySQL.
select count(id) as total_late from hub_attendance_lesson where late=true and student_id=TheUserId
Remove this first table, every total can be fetch using SQL :
SELECT count(id) AS absent
FROM hub_attendance_lesson
WHERE lesson_id = <your lesson id>
AND absent = <false / true>
I guess you'll be able to adapt this code for your needs.

PHP select row from db where ID is found in group of IDs

I have 3 employers IDs: 1, 2 and 3. I am generating tasks for each one by adding a line in database and in column "for_emp" I insert IDs I want to assign this task for and could be all 3 of them separated by comma. So let's say I got a task and "for_emp" is "1,2,3", the employers IDs. If I would like to select all tasks for the ID 2, will I be able to select from the row that has "1,2,3" as IDs and just match "2" there ? If not, how do you suggest I insert my emp IDs into one row in database ? The db is MySQL.
Any ideas ? Thanks.
Don't do it like that, you should normalize your database.
What you want to do is have a table such as task, and then task_assignee. task_assignee would have fields task_id and user_id. If a task has eg. three assignees (IDs 1, 2 and 3), then you'll create three rows in the task_assignee table for that one task, like this:
+--------+---------+
|task_id | user_id |
+--------+---------+
| 1 | 1 [
| 1 | 2 [
| 1 | 3 [
+--------+---------+
Then it's just a simple matter of querying the task_assignee table to find all tasks that are assigned to a given user.
Here's an example of how to get all the tasks for user_id 2:
SELECT t.* FROM task AS t INNER JOIN task_assignee AS ta WHERE ta.user_id = 2
EDIT.
Just as a related note, even if you didn't do it the right way (which I described in my answer previously), doing it with hacks such as LIKE would still be far from the optimal solution. If you did store a list of comma-separated values, and needed to check if eg. the value 2 is in the list, you could use the MySQL's FIND_IN_SET function:
SELECT * FROM task WHERE FIND_IN_SET(2, for_emp)
But you shouldn't do this unless you have no choice (eg. you're working with someone's shitty DB design), because it's way more inefficient and won't let you index the the employee ID.
The following query should do what you want:
SELECT * FROM tasks WHERE for_emp LIKE '%2%';
However, be aware that that would also match employers 12, 20, 21 etc; so take care if you expect you might end up in double-digits.
However, the other answers about renormalising your database are definitely preferable.
You're doing it wrong. Create a relation table with two fields: employee id and task id. If one task should be assigned to three employees, insert three rows in the relation table.
You then use JOIN to join the task, employee and relation tables.
then its no proper relation...
I would suggest a "mapping table" for the n:m relation
employee
id
task
id
employeetask
task_id
employee_id
Make a table for your employers. Insert your three rows in it.
Then make a table for mapping tasks to employers. If a task is assigned to three employers, insert three rows into this table. This is basic entity-relation work.
I would make 2 different tables.
1 with employees, and 1 with tasks.
then make another table which combines the two tables, I will call it Assigned Tasks.
Then in assigned tasks I make a assigned id, a employeenumber which is a FK to the employee table and a taskid which is a FK to the Tasks table.
If an employee has more than 1 task. Just insert another row in the assigned table. ;)
When its about Databases, try to think in solo entities! Combining those entities is able in antoher table.
sql example:
Select * from Assignedtasks where employeeID = 1 will give you all his/her tasks. :)
You could use a LIKE '%,2,%' clause in your SELECT statement.
eg:
SELECT * FROM table where for_emp LIKE '%,2,%'
However performance of such non-sargable queries is usually quite bad.
I would suggest that you insert a row each for each employee who is assigned to the task using a separate TASK_EMPLOYEE_MAPPING table with taskId, employeeId as a composite primary key.
With such a design, your query will be
SELECT * FROM TASK_EMPLOYEE_MAPPING WHERE employeeId = '2'

Categories