I got some code that need to look through three tables to display some information for a minecraft top list.
The tables are the following:
servers
id | user_id | name | information | websitename | websiteurl | postdate.. etc
This is the main table that contains server name, website information(Full description of the server) etc.
vote
id | server_id | username | ipaddress | votetimestamp
On the website I allow players to vote every 24 hour. All votes get inserted into this table with the users in-game name(username), the server id and then time of the vote.
ping
id | server_id | min_player | max_player | motd | pingtimestamp
This table get updated by another script every 10 mins with CronJobs that is running on my web server with the use of fsock's.
Doing this I can find out if the server is offline or online, how many players there is online and how many players that can be online at a time.
On my index page I got a script that should pull out data from all three tables onto the web page and display every server in the database order after the server that got the most votes to the server that got the least votes.
I can pull out every server that already got a vote in the voting table however if there is a server that haven't received a vote yet it wont get listed which is should.
This is the SQL code I use.
SELECT DISTINCT(ping.server_id), COUNT(vote.server_id) AS count, servers.id,
servers.name, servers.server_ip, ping.min_player,ping.max_player,ping.motd
FROM servers,vote,ping
WHERE servers.id = vote.server_id
AND servers.id = ping.server_id
GROUP BY servers.id
ORDER BY count DESC
LIMIT $start, $per_page
I'm sure this is simple enough but I've tried a few things now but nothing really seem to work.
Would be a good idea to mention at this point that SQL is not really my strong suit.
Edit
I have tried to remove the ´DISTINCT´ in the string but for some reason it returns multiple rows of every server which is displaying the server more then once.
Every server should only be displayed once, sorting from top to bottom after the server that got the most votes to the least.
First, don't use the comma-delimited syntax where you simply list the tables in your From clause; instead use the Join syntax. It would help if you showed us some sample inputs and expected outputs. You stated that you want all servers whether or not they have a vote means you will probably need to use Left Joins:
Select servers.id
, Count(vote.server_id) As cnt
, servers.name
, servers.server_ip
, Min(ping.min_player) As min_player
, Max(ping_ping.max_player) As max_player
, Min(ping.motd) As Min_Motd
From servers
Left Join votes
On votes.server_id = servers.id
Left Join ping
On ping.server_id = servers.id
Group By servers.id
, servers.name
, servers.server_ip
, ping.server_id
You were not clear on the structure and purpose of the ping table nor the nature of the results if there are multiple rows in the ping table for the same servers.id value. In the above example, I guessed as to the aggregate functions to use for ping.min_player, ping.max_player and ping.motd. In addition, I assumed you really did want all servers rows and not necessarily those that contained a value in the ping table and so I used a Left Join from servers to ping.
In addition, if servers.id is the primary key of the servers table and since you are using MySQL, you do not need to enumerate all of its columns in the Group By (but you would in other database products). Since it wasn't stated whether servers.id is the primary key, I enumerated them.
Related
I am building a pretty simple web app to check to see if a website is available (returns a http 200 code) using a cURL call. I need to get the domains to be checked which I currently have a SELECT *. I was told it needs to be sorted by the domain_key(last_checked) with a corresponding domain_key which has a timestamp. This way we can check the domain with the oldest last_checked value. The first table has these columns:
ID | CREATED | DOMAIN | accountID | server | status
Then I have a details table:
ID | domainID | domain_key | domain_value
I was also told to process the requests by server? So my question is:
How can I order or group the domains by server? Is that possible to do in one query or would I need to have separate SELECT statements for each group of domains on a particular server?
select domain, server, (select max(convert(domain_value, datetime)) from details where
domainID = domains.id and domain_key = 'last_checked') as last_checked
from domains group by domain, server;
Does this serve your purpose?
select domain, server,
(select max(convert(domain_value, datetime)) from details
where domainID = domains.id and domain_key = 'last_checked') as last_checked
from domains order by server, last_checked desc
I have a web application that stores points in a table, and total points in the user table as below:
User Table
user_id | total_points
Points Table
id | date | user_id | points
Every time a user earns a point, the following steps occur:
1. Enter points value to points table
2. Calculate SUM of the points for that user
3. Update the user table with the new SUM of points (total_points)
The values in the user table might get out of sync with the sum in the points table, and I want to be able to recalculate the SUM of all points for every user once in a while (eg. once a month). I could write a PHP script that could loop through each user in the user table and find the sum for that user and update the total_points, but that would be a lot of SQL queries.
Is there a better(efficient) way of doing what I am trying to do?
Thanks...
A more efficient way to do this would be the following:
User Table
user_id
Points Table
id | date | user_id | points
Total Points View
user_id | total_points
A view is effectively a select statement disguised as a table. The select statement would be: SELECT "user_id", SUM("points") AS "total_points" FROM "Points Table" GROUP BY "user_id". To create a view, execute CREATE VIEW "Total Points View" AS <SELECT STATEMENT> where SELECT STATEMENT is the previous select statement.
Once the view has been created, you can treat it as you would any regular table.
P.S.: I don't know that the quotes are necessary unless your table names actually contain spaces, but it's been a while since I worked with MySQL, so I don't remember it's idiosyncrasies.
You have to user Triggers for this, to make the users total points in sync with the user_points table. Something like:
Create Trigger UpdateUserTotalPoints AFTER INSERT ON points
FOR EACH ROW Begin
UPDATE users u
INNER JOIN
(
SELECT user_id, SUM(points) totalPoints
FROM points
GROUP BY user_id
) p ON u.user_id = p.user_id
SET u.total_points = p.totalPoints;
END;
SQL Fiddle Demo
Note that: As noted by #FireLizzard, if these records in the second table, are frequently updated or delted, you have to have other AFTER UPDATE and AFTER DELETE triggers as well, to keep the two tables in sync. And in this case the solution that #FireLizzard will be better in this case.
If you want it once a month, you can’t deal with just MySQL. You have too « logic » code here, and put too logic in database is not the correct way to go. The trigger of Karan Punamiya could be nice, but it will update the user_table on every insert in points table, and it’s not what you seem to want.
For the fact you want to be able to remove points, just add bsarv new negated rows in points, don’t remove any row (it will break the history trace).
If you really want it periodically, you can run a cron script that does that, or even call your PHP script ;)
I have a SQLite DB with about 24k records in one of the tables, 15 in the other. The table with 15 records holds information about forms that need to be completed by users (roughly 1k users). The table with 24k records holds information about which forms have been completed by who, and when. When a user logs in, there is about a ~3/4 second wait time while the queries run to determine what the user has finished so far. Too long for my client. I know I can't be doing my queries in the best way, because they are contained within a loop. But I cannot seem to figure out how to optimize my query.
The queries run as follows:
1) Select all of the forms and information
$result = $db->query("SELECT * FROM tbl_forms");
while($row = $result->fetchArray()){
//Run other query 2 here
}
2) For each form/row, run a query that figures out what is the most recent completion information about that form for the user.
$complete = $db->querySingle("SELECT * FROM tbl_completion AS forms1
WHERE userid='{$_SESSION['userid']}' AND form_id='{$row['id']}' AND forms1.id IN
(SELECT MAX(id) FROM tbl_completion
GROUP BY tbl_completion.userid, tbl_completion.form_id)", true);
There are 15 forms, so there is a total of 16 queries running. However, with my table structure, I'm unsure as how to get the "most recent" (aka max form id) form information using 1 joined query instead.
My table structure looks like so:
tbl_forms:
id | form_name | deadline | required | type | quicklink
tbl_completion:
id | userid | form_id | form_completion | form_path | timestamp | accept | reject
Edit: Index on tbl_forms (id), Index on tbl_forms (id, form_name), Index on tbl_complete (id)
I've tried using a query that is like:
SELECT * FROM tbl_completion AS forms1
LEFT OUTER JOIN tbl_forms ON forms1.form_id = tbl_forms.id
WHERE forms1.userid='testuser' AND forms1.id IN
(SELECT MAX(id) FROM tbl_completion GROUP BY tbl_completion.userid, tbl_completion.form_id)
Which will give me the most up-to-date information about the forms completed, as well as the form information, but the only problem with this is I need to output all the forms in a table (like: Form 1-Incomplete, Form 2-Completed, etc) I cannot seem to figure out how to get it to work with the left table being tbl_forms and getting all form info, as well as "latest" form tbl_completion info. I also tried doing a 3 LOJ with the last "table" as a temp table holding the maxid, but it was very slow AND didn't give me what I wanted.
Can anybody help?? Is there a better optimized query I can run once, or can I do something else on the DB side to speed this up? Thank you in advance.
You're missing indexes. See:
DOs and DONTs for Indexes
Also, the SELECT MAX(id) FROM tbl_completion GROUP BY tbl_completion.userid, tbl_completion.form_id could presumably discard unneeded rows if you toss in your userid in a where clause.
It sounds like you might be running into the concurrency limitations of SQLite. SQLite does not support concurrent writes, so if you have a lot of users, you end up having a lot of contention. You should consider migrating to another DBMS in order to satisfy your scaling needs.
How can I number my results where the lowest ID is #1 and the highest ID is the #numberOfResults
Example: If I have a table with only 3 rows in it. whose ID's are 24, 87, 112 it would pull like this:
ID 24 87 112
Num 1 2 3
The reason why I want this, is my manager wants items to be numbered like item1, item2, etc. I initially made it so it used the ID but he saw them like item24, item87, item112. He didn't like that at all and wants them to be like item1, item2, item3. I personally think this is going to lead to problems because if you are deleting and adding items, then item2 will not always refer to the same thing and may cause confusion for the users. So if anyone has a better idea I would like to hear it.
Thanks.
I agree with the comments about not using a numbering scheme like this if the numbers are going to be used for anything other than a simple ordered display of items with numbers. If the numbers are actually going to be tied to something, then this is a really bad idea!
Use a variable, and increment it in the SELECT statement:
SELECT
id,
(#row:=#row+1) AS row
FROM table,
(SELECT #row:=0) AS row_count;
Example:
CREATE TABLE `table1` (
`id` int(11) NOT NULL auto_increment,
PRIMARY KEY (`id`)
) ENGINE=InnoDB
INSERT INTO table1 VALUES (24), (87), (112);
SELECT
id,
(#row:=#row+1) AS row
FROM table1,
(SELECT #row:=0) AS row_count;
+-----+------+
| id | row |
+-----+------+
| 24 | 1 |
| 87 | 2 |
| 112 | 3 |
+-----+------+
How it works
#row is a user defined variable. It is necessary to set it to zero before the main SELECT statement runs. This can be done like this:
SELECT #row:=0;
or like this:
SET #row:=0
But it is handy to tie the two statements together. This can be done by creating a derived table, which is what happens here:
FROM table,
(SELECT #row:=0) AS row_count;
The the second SELECT actually gets run first. Once that's done, it's just a case of incrementing the value of #row for every row retrieved:
#row:=#row+1
The #row value is incremented every time a row is retrieved. It will always generate a sequential list of numbers, no matter what order the rows are accessed. So it's handy for some things, and dangerous for other things...
Sounds like it would be better just making that number in your code instead of trying to come up with some sort of convoluted way of doing it using SQL. When looping through your elements, just maintain the sequentiality there.
What is the ID being used for?
If it's only for quick and easy reference then that's fine, but if it's to be used for deleting or managing in any way as you mentioned then your only option would be to assign a new ID column that is unique for each row in the table. Doing this is pointless though because that duplicates the purpose of your initial ID column.
My company had a similar challenge on a CMS system that used an order field to sort the articles on the front page of the site. The users wanted a "promote, demote" icon that they could click that would move an article up or down.
Again, not ideal, but the strategy we used was to build a promote function and accompanying demote function that identified the current sort value via query, added or subtracted one from the previous or next value, respectively, then set the value of the initially promoted/demoted item. It was also vital to engineer the record insert to accurately set the initial value of newly added records so inserts wouldn't cause a duplicate value to be added. This was also enforced at the DB level for safety's sake. The user was never allowed to directly key in the value of the sort, only promote or demote via icons. To be honest, it worked quite well for the user.
If you have to go this route.....it's not impossible. But there is brain damage involved....
If we look at the stackoverflow website we have votes. But the question is what is the bestway to store who has voted and who has not. Lets also simplify this even more and say that we can only vote Up, and we can only Remove the Up vote.
I was thinking having the table to be in such form
question - Id(INT) | userId(INT) | title(TEXT) | vote(INT) | ratedBy(TEXT)
Thre rest is self explanitory but ratedBy is a Comma Seperated Id values of the Users.
I was thinking to read the ratedBy and compare it with the userId of the current logged in User. If he dosent exist in the ratedBy he can vote Up, otherwise he can remove his vote. Which in turn will remove the value from ratedBy
I think to make another table "vote" is better. The relationship between users and votes is n to n, therefore a new table should be created. It should be something like this:
question id (int) | user id (int) | permanent (bool) | timestamp (datetime)
Permanent field can be used to make votes stay after a given time, as SO does.
Other fields may be added according to desired features.
As each row will take at least 16B, you can have up to 250M rows in the table before the table uses 4GB (fat32 limit if there is one archive per table, which is the case for MyISAM and InnoDB).
Also, as Matthew Scharley points out in a comment, don't load all votes at once into memory (as fetching all the table in a resultset). You can always use LIMIT clause to narrow your query results.
A new table:
Article ID | User ID | Rating
Where Article ID and User ID make up the composite key, and rating would be 1, indicating upvote, -1 for a downvote and 0 for a removed vote (or just remove the row).
I believe your design won't be able to scale for large numbers of voters.
The typical thing to do is to create to tables
Table 1: question - Id(INT) | userId(INT) | title(TEXT)
Table 2: question - ID(INT) | vote(INT) | ratedBy(TEXT)
Then you can count the votes with a query like this:
SELECT t1.question_Id, t1.userId, t1.title, t2.sum(vote)
FROM table1 t1
LEFT JOIN table2 t2 ON t1.question_id = t2.question_id