I have 2 tables, Organisation and State(where it is). Each Organisation has a state. I would now like to implement a Integer column for the table State which keeps tracks of how many organisations are in it. Kinda like whenever a new organisation is inserted into Organisations table, update integer column in corresponding State table where org.state_id = state_id.
Is this possible using triggers?
Rather than keeping track of a computed column, it is often preferable to just calculate what you want on demand. So if you have these two tables:
organization(id, name, state_id)
state(id, name)
A query such as the following gets the counts:
SELECT s.name, COUNT(*) AS organization_count
FROM state s JOIN organization o ON (s.id = o.state_id)
GROUP BY s.name
This avoids integrity issues. If you are dealing with hundreds of thousands of records or operating in a reporting environment, then you might want to consider computing the info.
I agree with Glenn's answer that, all things being equal, on-demand is a better solution, but the answer is that, yes, it can be done with a trigger. Very easily.
CREATE TRIGGER organization_trigger_insert AFTER INSERT ON organization
FOR EACH ROW BEGIN
UPDATE state SET total = total + 1 WHERE NEW.state_id = state_id;
END;
CREATE TRIGGER organization_trigger_delete BEFORE DELETE ON organization
FOR EACH ROW BEGIN
UPDATE state SET total = total - 1 WHERE OLD.state_id = state_id;
END;
Demo
Related
Heres a description screen shot of the keys in a table that I use:
Table keys
Each row in the table is a representation of purchases by a specific client in a specific hour.
So a typical row would be like:
typical row (screenshot)
I need to merge two clients data, so one client will have all the purchases values summed up in his rows, for each hour.
In pseudo code what i want to perform is:
For every hour (row), Add the 'purchase amount' of the rows that have
client id of '526' to all rows that have client id '518'
.
At first, I tried to execute this but then got an error due to the multiple keys configured in the table:
UPDATE purchases set client id = 518 where client id = 526;
since the client '518' already has rows for the same hours, I can not perform the above query that creates new rows.
How should I tackle this?
You will require three queries:
One to do the sum if a record exists for both customers with the same time value:
update purchase p1
inner join purchase p2 on p2.client_id=528 and p2.date=p1.date
set p1.amount = p1.amount + p2.amount
where p1.client_id=526;
A second one to handle the records where only one exists (and not the one that will continue to exist):
insert into purchase
(select 526, date, amount
from purchase p1
where p1.client_id=528 and
not exists (select *
from purchase p2
where p2.client_id=526 and
p2.date=p1.date));
Note - the above can probably also be done (and more elegantly) using an update query.
And a final query to remove the merged records:
delete from purchase where client_id=528;
Note - I used client_id values 526 and 528 throughout - you may need to alter these numbers to fit your purpose.
I am struggling about how to grant the consistency of the following operation in this scenario: we are developing a reservation portal, where you can sign for courses. Every course has a number of direct reservations: when the available reservations are over, users can still sign but the fall in the waiting list.
So this is the situation: for example, let's imagine that a course has 15 slots available and I make my reservation. In a simple logic, as I made my reservation the system has to decide if I am in the direct reservation list (one of the 15 available slots) or in the waiting list. In order to do so, we must count if the total direct reservations is less than the total available reservations, something like this in pseudocode:
INSERT new_partecipant IN table_partecipants;
(my_id) = SELECT ##IDENTITY;
(total_reservations_already_made) = SELECT COUNT(*) FROM table_partecipants WHERE flag_direct_reservation=1;
if total_reservations_already_made <= total_reservations_available then
UPDATE table_partecipants SET flag_direct_reservation=1 WHERE id=my_id
The question is: how I can manage the concurrency in order to be sure that two subscriptions are managed correctly? If I have only one reservation left and two users apply at the same time, I think it's possible that the result of the COUNT operation can give the same result to both the requests and insert both the users in the list.
What is the correct way of locking (or similar procedure) in order to be sure that if a user starts the subscription procedure no one can finish the same task before the request has been completed?
Ok, it seems we have found a solution without locking, any comment appreciated:
INSERT
INSERT INTO table1 (field1, field2, ..., fieldN, flag_direct_reservation)
SELECT #field1, #field2, ..., #fieldN, ,CASE WHEN (SELECT COUNT(*) FROM sometable WHERE course=#course_id) > #max_part THEN 1 ELSE 0 END
UPDATE
(only for determining the subscription status, in case of subscription deletion)
UPDATE corsi_mytable p1 INNER JOIN
(
SELECT COUNT(*) as actual_subscritions
FROM mytable
WHERE course=#course_id
)p2
SET p1.flag_direct_reservation= CASE WHEN p2.actual_subscritions > #max_part THEN 0 ELSE 1 END
WHERE p1.id =#first_waiting_id;
In this way the operation is performed by only one SQL statements and the transactional engine should ensure the consistency of the operation.
Don't lock the table. Instead try and book a row, and if it fails, throw them on the waiting list. For example:
UPDATE table_partecipants SET booking_id=? WHERE booking_id IS NULL LIMIT 1
The WHERE clause here should include any other exclusion factors, like if it's the right day. If the query successfully modifies a row the booking worked. If not, it's sold out. No lock required.
The booking_id here is some unique value that can be used to associate this course with the person booking it. You can delete any records that aren't used due to over-booking.
I would advise you agains locking the full table for this. Go for a row lock.
You can have a separate table that has only 1 line with your reservation counter of 15, and start by updating this counter before adding new reservations.
So you are sure the lock is managed by this separate table, which enables your reservation table to be updated by any other scenario where you don't care about the counter.
The image below is what I have so far.
The Design
The Emitters Table
Emitters have their own properties such as location (in terms of x and y), id, level, and the last time this information was updated. More importantly, emitters can be defended by any clan member. Emitters can only be defended by a member of that clan. One emitter can be defended by many players.
The Players Table
Or rather, clan members. This table contains information about the players specifically. This also has location in terms of x and y and other information about that player. (I'll probably add lastupdated here, too) One player can defend multiple emitters.
The Emitter_Player Join Table
This table contains a single emitter_player combination. (providing one such relationship exists) Let me first ask if this is a proper relationship between emitters and players? I figured it would be many-to-many. Now, I could make this easy on myself and just add defensepoints to this join table (what I really care about is how much "defense" a player has in a specific emitter) Is this also a correct thing to do? However, I'd like to "do it right the first time" and add information about each specific unit a player has defending this emitter.
The Units Table
This table contains information about all of the units in the game. This table has the units id and it's associated defensive and offensive values. This table will very rarely be inserted into, and when it is (new units are added to the game) it will be updated manually.
The Problem
Fixing the design
Emitters do not contain units outside of players. A player must be the owner of every unit in an emitter. So, there is no relationship between emitters and units. Also, while a player likely has units outside of the emitters, I do not care for this example. I only care about the units that are in an emitter. So, I figured that there would be a many-to-many relationship between the Emitter_Player join table and the Units table. My reasoning behind this is that an Emitter_Player combination can easily have many different types of units and one type of unit can be in many different Emitter_Player combinations.
Inserting information
With two join tables, I am now extremely confused on how to insert information into this database.
Querying information
Again, I am extremely lost how to access information from this database.
The Goal
Graphs
I would like to eventually create graphs (from both players and emitters) showing their progress over time. How I will query this I do not know.
Weekly Changes
I would like to be able to inform players whether they have made progress since last week or lost progress. (and in this case, flag them for review)
Conclusion
I tried to make this as detailed as possible, if you need any more information please let me know. I'm hoping to get this finished soon and I'm really at a complete loss of any ideas further.
You're off to a fairly decent start, but I'd recommend a few suggestions:
It seems to me that you can consolidate the emitters_has_players_has_units table into the emitters_has_players table. Simply make the unit_id a third component in the primary key of emitters_has_players:
You'll also notice I added a quantity column to the particular emitter-player-unit associations. This is necessary to keep track of how many of a particular unit a particular player has for a particular emitter.
It's also good practice to keep your column names consistent throughout your database. The way you named your id columns originally was quite long (as it included the full table names as prefixes).
So here are some examples of how you can query the above design:
-- Get all associated emitters of a particular player
SELECT a.*
FROM emitters a
JOIN
(
SELECT DISTINCT emitter_id
FROM emitters_has_players
WHERE player_id = 1
) b ON a.emitter_id = b.emitter_id
-- Get all players associated with a particular emitter
SELECT a.*
FROM players a
JOIN
(
SELECT DISTINCT player_id
FROM emitters_has_players
WHERE emitter_id = 1
) b ON a.player_id = b.player_id
-- Get the count of players for a particular emitter
SELECT COUNT(DISTINCT player_id) AS player_count
FROM emitters_has_players
WHERE emitter_id = 1
-- Get all units associated with a particular player-emitter association
SELECT b.*
FROM emitters_has_players a
JOIN units b ON a.unit_id = b.unit_id
WHERE a.emitter_id = 1 AND a.player_id = 1
-- Get the defense points of a particular player-emitter association
SELECT SUM(b.averagedefense * a.quantity) AS total_def_pts
FROM emitters_has_players a
JOIN units b ON a.unit_id = b.unit_id
WHERE a.emitter_id = 1 AND a.player_id = 1
-- Create a new player-emitter-unit association
INSERT INTO emitters_has_players
VALUES (1,1,1,1) -- Where the fourth "1" is the quantity of units initially.
-- Player adds on one more of a particular unit for a particular emitter
UPDATE emitters_has_players
SET qty = qty + 1
WHERE emitter_id = 1 AND
player_id = 1 AND
unit_id = 1
I have a faculty table, and each faculty has a certain number of students under him/her. So there is a 'current' column, which is the number of students currently under them.
However, I don't want to ++ and -- every time I switch a student to another faculty. Is there a way to keep the column updated with a query that uses count()? I find it is easier and more accurate to use the query 'select count() from student where advisor = 2' for example that using my current column.
To do this, use a view:
CREATE VIEW studentCount AS
SELECT
profID,
profName,
whatever,
(SELECT COUNT(*)
FROM studentTable
WHERE studentTable.profID=profTable.profID
) AS studentCount
FROM profTable;
Obviously, this needs to be massaged a little to fit your schema, but essentially, setup your view to have all the columns of the table with the faculty info and add a column at the end that counts the number you want in it.
Triggers could be a solution to you problem?
http://dev.mysql.com/doc/refman/5.5/en/triggers.html
You could create a trigger that automaticly updates your faculty table every time a student switch faculty.
Hello all and thanks in advance
I have the tables accounts, votes and contests
A vote consists of an author ID, a winner ID, and a contest ID, so as to stop people voting twice
Id like to show for any given account, how many times theyve won a contest, how many times theyve come second and how many times theyve come third
Whats the fastest (execution time) way to do this? (Im using MySQL)
After using MySQL for a long time I'm coming to the conclusion that virtually any use of GROUP BY is really bad for performance, so here's a solution with a couple of temporary tables.
CREATE TEMPORARY TABLE VoteCounts (
accountid INT,
contestid INT,
votecount INT DEFAULT 0
);
INSERT INTO VoteCounts (accountid, contestid)
SELECT DISTINCT v2.accountid, v2.contestid
FROM votes v1 JOIN votes v2 USING (contestid)
WHERE v1.accountid = ?; -- the given account
Make sure you have an index on votes(accountid, contestid).
Now you have a table of every contest that your given user was in, with all the other accounts who were in the same contests.
UPDATE Votes AS v JOIN VoteCounts AS vc USING (accountid, contestid)
SET vc.votecount = vc.votecount+1;
Now you have the count of votes for each account in each contest.
CREATE TEMPORARY TABLE Placings (
accountid INT,
contestid INT,
placing INT
);
SET #prevcontest := 0;
SET #placing := 0;
INSERT INTO Placings (accountid, placing, contestid)
SELECT accountid,
IF(contestid=#prevcontest, #placing:=#placing+1, #placing:=1) AS placing,
#prevcontest:=contestid AS contestid
FROM VoteCounts
ORDER BY contestid, votecount DESC;
Now you have a table with each account paired with their respective placing in each contest. It's easy to get the count for a given placing:
SELECT accountid, COUNT(*) AS count_first_place
FROM Placings
WHERE accountid = ? AND placing = 1;
And you can use a MySQL trick to do all three in one query. A boolean expression always returns an integer value 0 or 1 in MySQL, so you can use SUM() to count up the 1's.
SELECT accountid,
SUM(placing=1) AS count_first_place,
SUM(placing=2) AS count_second_place,
SUM(placing=3) AS count_third_place
FROM Placings
WHERE accountid = ?; -- the given account
Re your comment:
Yes, it's a complex task no matter what to go from the normalized data you have to the results you want. You want it aggregated (summed), ranked, and aggregated (counted) again. That's a heap of work! :-)
Also, a single query is not always the fastest way to do a given task. It's a common misconception among programmers that shorter code is implicitly faster code.
Note I have not tested this so your mileage may vary.
Re your question about the UPDATE:
It's a tricky way of getting the COUNT() of votes per account without using GROUP BY. I've added table aliases v and vc so it may be more clear now. In the votes table, there are N rows for a given account/contest. In the votescount table, there's one row per account/contest. When I join, the UPDATE is evaluated against the N rows, so if I add 1 for each of those N rows, I get the count of N stored in votescount in the row corresponding to each respective account/contest.
If I'm interpreting things correctly, to stop people voting twice I think you only need a unique index on the votes table by author (account?) ID and contestID. It won't prevent people from having multiple accounts and voting twice but it will prevent anyone from casting a vote in a contest twice from the same account. To prevent fraud (sock puppet accounts) you'd need to examine voting patterns and detect when an account votes for another account more often then statistically likely. Unless you have a lot of contests that might actually be hard.