I have a file hosting site where I provide a point for every unique download to user.
Sample of my table
These points can be redeemed by user. So for example if a user redeems 100 points than what is the best query to reduce points available from each row till 100 points are reduced.
Thank You.
You should create two tables for this:
Table files
- id
- name
- size
Table points
- id
- file_id
(- user)
- points
Insert a new file:
INSERT INTO files (name, size) VALUES ('kat92a.jpg', 105544); // New file with ID 1
Now you can give points to a file, negative or positive:
INSERT INTO points (file_id, points) VALUES (1, 100); //Positive points
INSERT INTO points (file_id, points) VALUES (1, -10); //Negative points
And you can select the total number of points:
SELECT
files.name,
files.size,
(SELECT sum(points) FROM points WHERE file_id = 1) AS points
FROM files
WHERE id = 1
Alright, then, here's the SQL-dumb way I would do it. Hopefully an SQL guru will come around with a better solution. Note: This is pure pseudocode; write your own code based on this--it's not going to work out of the box.
$total_to_deduct = 100;
// Each time, get the row with the highest points
$top_points_query = "SELECT id, points FROM my_table ORDER BY points DESC LIMIT 1;"
do {
$result = do_query($top_points_query);
if($result) {
// I'm assuming you don't want to deduct more points from a row than it has
$num_to_deduct = min($result['points'], $total_to_deduct);
// Now deduct the points from the row we got earlier
$update_query = "UPDATE my_table SET points = points - $num_to_deduct
WHERE id = $result['id']";
if(do_query($update_query)) {
$total_to_deduct -= $num_to_deduct;
}
}
} while($total_to_deduct > 0); // If we still have points to deduct, do it again
Seems like you just need a simple update Statement and allows you to update the row and if it's more than 100 not update it.
update table set points = if( (points+<VALUE>) <= 100,points+<VALUE>,points) where id = <FILE ID>
This will check to see if the points is higher than 100, if it is then the update statement will just return no results. If the value is less than 100, then it will update the table and give you back the amount of rows that were updated.
Just add a column in your user table with the amount of redeemed points. Is that a viable solution for you?
Here is a pure SQL solution, but I warn you that (a) this is untested and (b) it's just a concept.
DECLARE curs CURSOR FOR
SELECT
id,
points,
FROM
points
WHERE
points > 0;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET remPoints = 0;
OPEN curs;
SET remPoints = 100; /* modify this value, probably in your app */
REPEAT
FETCH curs INTO cId, cPoints;
IF remPoints >= cPoints THEN
UPDATE points SET points = 0 WHERE id = cId;
ELSE
UPDATE points SET points = points - remPoints WHERE id = cId;
END IF;
SET remPoints = remPoints - cPoints;
UNTIL remPoints <= 0;
CLOSE curs;
Related
A user can input it's preferences to find other users.
Now based on that input, I'd like to get the top 10 best matches to the preferences.
What I thought is:
1) Create a select statement that resolves users preferences
if ($stmt = $mysqli->prepare("SELECT sex FROM ledenvoorkeuren WHERE userid = you"))
$stmt->bind_result($ownsex);
2) Create a select statement that checks all users except for yourself
if ($stmt = $mysqli->prepare("SELECT sex FROM ledenvoorkeuren WHERE userid <> you"))
$stmt->bind_result($othersex);
3) Match select statement 1 with select statement 2
while ($stmt->fetch()) {
$match = 0;
if ($ownsex == $othersex) {
$match = $match + 10;
}
// check next preference
4) Start with a variable with value 0, if preference matches -> variable + 10%
Problem is, I can do this for all members, but how can I then select the top 10???
I think I need to do this in the SQL statement, but I have no idea how...
Ofcourse this is one just one preference and a super simple version of my code, but you'll get the idea. There are like 15 preference settings.
// EDIT //
I would also like to see how much the match rating is on screen!
Well, it was a good question from the start so I upvoted it and then wasted about 1 hour to produce the following :)
Data
I have used a DB named test and table named t for our experiment here.
Below you can find a screenshot showing this table's structure (3 int columns, 1 char(1) column) and complete data
As you can see, everything is rather simple - we have a 4 columns, with id serving as primary key, and a few records (rows).
What we want to achieve
We want to be able to select a limited set of rows from this table based upon some complex criteria, involving comparison of several column's values against needed parameters.
Solution
I've decided to create a function for this. SQL statement follows:
use test;
drop function if exists calcMatch;
delimiter //
create function calcMatch (recordId int, neededQty int, neededSex char(1)) returns int
begin
declare selectedQty int;
declare selectedSex char(1);
declare matchValue int;
set matchValue = 0;
select qty, sex into selectedQty, selectedSex from t where id = recordId;
if selectedQty = neededQty then
set matchValue = matchValue + 10;
end if;
if selectedSex = neededSex then
set matchValue = matchValue + 10;
end if;
return matchValue;
end//
delimiter ;
Minor explanation
Function calculates how well one particular record matches the specified set of parameters, returning an int value as a result. The bigger the value - the better the match.
Function accepts 3 parameters:
recordId - id of the record for which we need to calculate the result(match value)
neededQty - needed quantity. if the record's qty matches it, the result will be increased
neededSex - needed sex value, if the record's sex matches it, the result will be increased
Function selects via id specified record from the table, initializes the resulting match value with 0, then makes a comparison of each required columns against needed value. In case of successful comparison the return value is increased by 10.
Live test
So, hopefully this solves your problem. Feel free to use this for your own project, add needed parameters to function and compare them against needed columns in your table.
Cheers!
Use the limit and offset in query:
SELECT sex FROM ledenvoorkeuren WHERE userid = you limit 10 offset 0
This will give the 10 users data of top most.
You can set a limit in your query like this:
SELECT sex FROM ledenvoorkeuren WHERE userid <> yourid AND sex <> yourpreferredsex limit 0, 10
Where the '0' is the offset, and the '10' your limit
More info here
you may try this
SELECT sex FROM ledenvoorkeuren WHERE userid = you limit 0, 10 order by YOUR_PREFERENCE
I have the following challenge:
a "Tasks" table:
tasksId int
listId int
taskOrder float
in case i want to move all the tasks from list 2 to list 3 i would do something like:
// pseodo code //
#lastTaskOrder = last task order in list 3
loop - {
UPDATE tasks SET taskOrder = #lastTaskOrder + 1, listId = 3 WHERE listId = 2;
#lastTaskOrder++
}
thus the taskOrder stays unique.
in case i want to move all the tasks from list 2 to the beginning of list 3 i would do something like:
// pseodo code //
#firstTaskOrder = first task order in list 3
#delta = #firstTaskOrder / #numberOfTasksToMove
UPDATE tasks SET taskOrder = #firstTaskOrder + #delta, listId = 3 WHERE listId = 2;
#firstTaskOrder = #firstTaskOrder + #delta
is it possible with mySQL + PDO + PHP?
Short answer: yes.
Longer answer involves some code. To update your list_ids and increment them based on the highest current value in the old list, I had to use a subquery with a window function:
UPDATE tasks
SET list_id = :toList,
task_order =
(SELECT MAX(task_order) from tasks where list_id = :toList)
+ t2.task_sort_order
FROM ( SELECT task_id,
row_number() OVER (PARTITION BY list_id order by task_order)
AS task_sort_order
FROM tasks ) t2
WHERE tasks.task_id = t2.task_id AND tasks.list_id = :fromList
Edit This is heavily edited from the first version. I've thrown away all the PHP in favor of just showing the SQL. I changed the column names because my version of Postgres was complaining about the camel-case names.
this proved to be easier than i thought it would be.
It took me 2 hours but i got it figured out:
SET #start=100;
SET #delta=1.5;
UPDATE tasks SET taskOrder = #start:= (#start+#delta), listId = 3
WHERE listId=2
ORDER BY taskOrder
I PDOed this query with the correct values
I try to build a variable that integrates some other variable.
one of that will be the number of an auto-increment-field where later on an insert-query will happens.
I tried to use:
$get_num = $db/*=>mysqli*/->query("SELECT COUNT (*) auto_increment_column FROM table1");
$num = $query->fetch_assoc($get_num);
$end = $num + 1;
I don't have any update/insert query before that so I can't use
$end = $db->insert_id;
that's why i thought i can just count the numbers of the auto_increment rows and have my last variable that is necessary to build my new variable.
for a reason this wonT count the entries and outputs 0. i dont understand why this happens.
i really would appreciate if there is someone who could tell me what am i doing wrong. thanks a lot.
UPDATE
For everyone who likes to know about what's the goal:
I like to create a specific name or id for a file that later on will be created by the input of the fields from the insert query. I like to have an unique key. this key consists of an user_id and a timestamp. at the end of this generated variable it should be placed the auto_increment nr. of the query that will be placed in the table. so the problem is, that I create an variable before the insert query happens so that this variable will be part of the insert query like:
$get_num = $db->query("SELECT COUNT (*) FROM tableA");
$num = $query->fetch_assoc();
$end = $num + 1;
$file_id = $id .".". time() .".". $end;
$insert = $db->query("INSERT INTO tableA ( file_id, a, b, c) VALUES('".$file_id."','".$a."','".$b."','".c."')");{
hope now, it will be clear what I like to approach.
If you need an auto-incrementing column in MySQL then you should use AUTO_INCREMENT. It implements it all for you and avoids race conditions. The manual way you are trying to implement it has a couple of flaws, namely
If two scripts are trying to insert concurrently they might both get the same COUNT (say 10) and hence both try to insert with ID 11. One will then fail (or else you will have duplicates!)
If you add 10 items but then delete item 1, the COUNT will return 9 but id 10 will already exist.
try
SELECT COUNT(*) FROM table1
This is a high level/design type question rather than anything specific.
In this game http://koreanwordgame.com/ users have to get as many correct answers as possible in a minute (though its set to 5 seconds for development purposes). As soon as the minute is up I fire an Ajax call with JQuery passing the users score.
On the server side I use this SQL query to get the 5 scores above and 5 below
$getquery = "(SELECT *
FROM highscore
WHERE score >= '$score'
ORDER BY score ASC
LIMIT 5)
UNION ALL
(SELECT *
FROM highscore
WHERE score < '$score'
ORDER BY score DESC
LIMIT 5)";
This returns an HTML table to the client which you will see upon completing the (5 sec) challenge (you don't have to get any answers right).
Now, ultimately I want the table to be about 100 rows long (although it will still only display the surrounding 10 cells, 5 above, 5 below), with the rows position (ID) determined not chronological as it is now but by the score, so that if someone gets 1000 answers right they would go straight into position 1 on the database. Obviously if a user gets lower than any of the 100 rows already existing in the table then it should simply be discarded. Then when the table comes back I want a text input to appear inside the table cell where the user is placed so they can enter their name and fire off another Ajax call so their entry gets inserted into the right place in the database.
I know it would be easier just to ask the user to enter their details first and then scoop off the top 10 results whether they got there or not, but the idea is that I want anyone to be able to get some kind of entry as this will encourage people to play more.
A tough one perhaps..I certainly haven't been able to find anything similar by Googling, all help much appreciated!
EDIT:
In case anyone is interested this is what I ended up with, it's far from finished I need to make it way more robust for example entering an anonymous username in case the user closes the browser and also prevent double posts (though this will be client side).
but thank you everyone for the help, I would not have done it without you. If you see any obvious improvements that could be made feel free to point them out!
<?php
$dbcon = mysql_connect("localhost", "XXXX", "XXXX") or die(mysql_error());
mysql_select_db("tulesblo_koreangame", $dbcon) or die(mysql_error());
mysql_query("SET NAMES utf8");
$name = $_POST["name"];
$email = $_POST["email"];
$score = $_POST["score"];
$table = "";
$submit = "";
$input = "";
$newposition = $_POST['position'];
$position = mysql_query("(SELECT position
FROM highscore
WHERE score < '$score'
ORDER BY score DESC
LIMIT 1)");
if(!$name){
$gethigherrows = "(SELECT *
FROM highscore
WHERE score >= '$score'
ORDER BY score ASC
LIMIT 5)";
$getlowerrows = "(SELECT *
FROM highscore
WHERE score < '$score'
ORDER BY score DESC
LIMIT 5)";
$higherrows= mysql_query($gethigherrows);
$lowerrows= mysql_query($getlowerrows);
if(mysql_error())echo mysql_error();
while($row=mysql_fetch_array($higherrows))
{
$uppertable .= "<tr><td>$row[position]</td><td>$row[name]</td> <td>$row[score]</td></tr>";
}
$x = 0;
if (mysql_num_rows($lowerrows) > 0)
{ mysql_query("UPDATE highscore SET position = position + 1 WHERE score < '$score'")or die("update failed");
while($row=mysql_fetch_array($lowerrows))
{
if ($x == 0)
{$position = $row['position'];};
$x++;
$newpos = $row[position]+1;
$lowertable.= "<tr><td>$newpos</td><td>$row[name]</td> <td>$row[score]</td></tr>";
}
$input = "<tr><td id='position'>$position</td><td><input id='nameinput'type='text' /></td><td>$score</td></tr>";
$submit = "<br />Enter email if you want to receive a prize!<br /><input id='emailinput'type='text' /><br /><input id='submithighscore'type='submit' value='Submit'>";
}
$table .= "<table id='scoretable'><tr><th>Position</th><th>Name</th><th>Score</th></tr>";
$table .= $uppertable;
$table .= $input;
$table .= $lowertable;
$table .= "</table>";
$table .= $submit;
$table .= "<br /><span class='msg'></span>";
echo $table;
}else{ echo($newposition);
mysql_query("INSERT INTO highscore VALUES (NULL, '$score', '$name', '$email', '$newposition')");
}
?>
You did not say where lies your problem, but I suppose it's mostly a misconception about SQL. Here's how I would do the whole app.
For the SQL schema:
You shouldn't care about the order of the rows in the DB. You can set the order in the queries (SELECT, etc). There is no "right place" to insert.
You don't need a "rank" column, for the same reason.
Your table can be as simple as: id (INT AUTO_INCREMENT), name, score, when (timestamp).
Initialize it with 100 dummy scores.
For the workflow:
Do not care for performance now, aim for readability.
As you wrote, once a player has finished, use AJAX to find the 10 surrounding scores. You already have the SQL queries for this part. You can either prepare the HTML on the server side in PHP, or send raw data in order build the HTML with Javascript. This can be done in one request (use json_encode() in PHP to return an array/object).
If there are no scores below, then it's not one of the 100 best scores, do nothing.
Else, ask the user its name, and send a new AJAX request. The SQL can be kept simple:
INSERT INTO Highscore SET name=?, score=?, when=NOW()
After each insertion, delete the lowest score
DELETE FROM Highscore ORDER BY score ASC, when DESC LIMIT 1
If you want to prevent cheating, you'll need to add hashes in your ajax calls. For instance, send (username, score, hash=md5(username.score."secretphrase") and check the hash still matches the username and score.
If you want to display the ranks along with the scores, then put a "rank" colum in your table. When you insert a score, set its rank to the max rank of the lower scores (you already computed this for the first AJAX request, so send it in the second AJAX request). Then UPDATE Highscore SET rank = rank + 1 WHERE score < ?.
I'm havinga pickle to understand what you're exactly after. Here's a query to insert the result in the highscore.
$str_user_id = $_GET['user_id'];
$str_score = $_GET['score'];
mysql_query("
INSERT INTO highscore (position, user_id, score)
SELECT (#rownum := $rownum + 1) AS position, user_id, score
FROM (SELECT user_id, score
FROM (SELECT '{$str_user_id}' AS user_id, '{$str_score}' AS score
UNION ALL
SELECT user_id, score
FROM highscore) AS h, (#rownum := 0) AS vars
WHERE '{$str_score}' <= 100
ORDER BY score ASC) AS h2)
LIMIT 100
ON DUPLICATE KEY UPDATE user_id = VALUES(user_id), score = VALUES(score)
");
I couldn't entirely make sense of your question, but based on this:
"I know it would be easier just to ask the user to enter their details first and then scoop off the top 10 results whether they got there or not, but the idea is that I want anyone to be able to get some kind of entry as this will encourage people to play more"
I suspect you want the score table shown to the user to look something like this:
1. John Doe (score: 1,000,000)
2. Larry Roe (score: 999,999)
3. Jane Doe (score: 985,742)
...
5746. Susan Player (score: 894)
5747. *** YOU *** (score: 893)
5748. Joe R. Player (score: 889)
I second Mytskine's initial suggestion that you shouldn't try to store the ranks in the DB at all — keeping a rank column up to date is possible, like Mytskine shows at the end of their answer, but it's a lot of work for little if any gain. Just have your score table consist of the obvious columns like name and score and time, plus maybe an AUTO_INCREMENT column id.
Now, let's say you want to show the N highest-scoring players, plus K players before and after the new score. First, we need to figure out the player's rank:
SELECT COUNT(*) + 1 FROM scores WHERE score > ?
Here, the ? is a placeholder for the player's score. This counts how many players have a higher score than the one we're going to insert below. If we favor the latest scores in the case of ties, this plus one will be the player's rank. Let's denote this rank by R.
Now, if R < N + K, we might was well just show the top N + 2*K (or max(N, R+K) or whatever) entries:
SELECT * FROM scores ORDER BY score DESC, time DESC LIMIT N+2*K
Otherwise, we need to do it in three steps: first, fetch the top N entries:
SELECT * FROM scores ORDER BY score DESC, time DESC LIMIT N
then, the K entries above the player:
SELECT * FROM scores WHERE score > ? ORDER BY score ASC, time ASC LIMIT K
and finally the K entries below the player:
SELECT * FROM scores WHERE score <= ? ORDER BY score DESC, time DESC LIMIT K
Note that the second result set will be in reverse order, but that's OK — just reverse it in PHP. Now we need to assign ranks to the entries we've fetched, but that's simple: the first set has ranks 1 to N, the second set (after reversal) has ranks R-K to R-1, and the last set has ranks R+1 to R+K. (Rank R belongs to the current player, of course.)
Finally, after the player has entered their name, we can insert the player's score into to the database simply with:
INSERT INTO scores (name, score, time) VALUES (?, ?, ?)
If you want, you can also remove the lowest scores from the database at this point to limit its size, but in practice it probably won't matter unless you have literally billions of players. (A single score entry is going to take a few dozen bytes, mostly depending on how long a name the player enters, so a million entries will still be just a few dozen megabytes.) You should, however, remember to create an index on (score, time) to keep the queries efficient.
I have a script that has a GET variable: $_GET['percentage']
I have a MySQL table of data.
Now lets say that there are 100 rows of data in this table.
In pseudo-code:
SELECT data FROM table
Now would it be possible to select $_GET['percentage'] of random data from table?
For example (again in pseudo-code):
$_GET['percentage'] = 10;
SELECT 10% of data from table order by rand()
If this IS possible, how could I do it?
In MySQL, it's probably easiest to do this in two queries. First, get the count of rows in the table:
SELECT COUNT(*) FROM MyTable;
Then prepare the query to get random rows:
SELECT ... FROM MyTable ORDER BY RAND() LIMIT ?;
Then execute the prepared query and send the value of the count divided by 10.
Not every problem needs to be solved by a single query.
Here's an example PHP script, edited to use the old mysql extension.
<?php
// Get the total number of rows in the table.
$sql = "SELECT COUNT(*) FROM Kingdoms";
$result = mysql_query($sql);
$row = mysql_fetch_array($result);
$rows_in_table = $row[0];
// We only want a portion of the rows, specified by the user
// choice of percentage. The count we want is therefore equal
// to the total number of rows in the table multiplied by the
// desired percentage.
$percentage = intval($_GET["percentage"]) / 100.0;
$count = intval(round($rows_in_table * $percentage));
// LIMIT makes the query return at most the number of rows specified.
// Sort randomly first (if the table has too many rows this will be slow),
// then return the first $count rows.
$sql = "SELECT * FROM Kingdoms ORDER BY RAND() LIMIT {$count}";
$result = mysql_query($sql);
while ($row = mysql_fetch_array($result)) {
print_r($row);
}
PS: Always be careful when interpolating a variable into an SQL expression. You should force the variable to a known format -- an integer value in this case. Otherwise you risk creating an SQL Injection vulnerability.
If you have auto incremented ID field you may use
HAVING ID_FIELD<=ceil(count(*)*10/100);
Otherwise a stored procedure can help in this.
select columnvalue from mytable WHERE RAND() <= 0.5 .....will directly result in very near to 50% of the records
May be this event rise the solution
drop event OEAuditEvent;
DELIMITER $$
CREATE EVENT OEAuditEvent
ON SCHEDULE EVERY 1 SECOND
STARTS '2012-09-05 09:00:00'
DO
BEGIN
DECLARE a CHAR(20);
DECLARE b,c,d INT;
DECLARE done INT DEFAULT FALSE;
IF CURRENT_TIME() = '23:40:00' THEN
begin
DECLARE cur CURSOR FOR select OE_User,count(OE_User) from RNCM_Status where date(OE_Date)=CURDATE() group by OE_User;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
OPEN cur;
read_loop: LOOP
FETCH cur INTO a, b;
SET c=ceil((b*5)/100);
IF done THEN
LEAVE read_loop;
ELSE
insert into OE_Audit(MDN,CAF,UploadedDate,OEUser,OEDate,UserCount,QCCount,intime) select MDN,CAF,UploadedDate,OE_User,OE_Date,b,c,now() from RNCM_Status where OE_User=a and date(OE_Date)=CURDATE() order by rand() limit c;
END IF;
END LOOP;
CLOSE cur;
end ;
END IF;
END $$
DELIMITER ;