On my site (which uses the CMS eFiction) users can leave reviews with a rating on chapters of 1-10, if they don't rate the chapter it's logged on the table as a -1. I want to prevent users from leaving multiple ratings. My PHP knowledge is very limited, I believe the code should go before line 144 in review.php (on github here)
dbquery("INSERT INTO ".TABLEPREFIX."fanfiction_reviews (item, type, reviewer, review, rating, date, uid, chapid) VALUES ('$item', 'ST', '$reviewer', '$review', '$rating', now(), '".(USERUID && isNumber(USERUID) ? USERUID : 0)."', '$chapid')");
I'm trying to write a query for the fanfiction_reviews table and if finds an entry that matched the uid (user) and chapid and has a rating, throw them an error message that they already rated the chapter. ETA: To answer a question below, I want the 2nd rating to be ignored or not logged, rather than replacing a previous rating.
As I mentioned, my PHP is limited, I just try to mimic what I find elsewhere in eFiction. Edit: in the following initially had changed useruid = '$uid' which was giving me a fatal mysql error. I changed it to uid = '$uid' and the stopped the error
$ratecount = dbquery("select count(rating) as count FROM ".TABLEPREFIX."fanfiction_reviews WHERE item = '$item' AND chapid = '$chapid' AND uid = '$uid' AND rating != '-1'");
if($ratecount != 0) $output .= write_error(_MULTIPLERATINGS);
With the above pasted in the file, it does give me the "multiple rating error" message BUT it's still logging the rating. How do I get it to not log the rating (or change it to the non-rating of -1)
Thanks in advance for any help!
Try if($ratecount.count != 0) $output .= write_error(_MULTIPLERATINGS);. It's also possible you may need to format it as if($ratecount["count"] != 0) $output .= write_error(_MULTIPLERATINGS);.
As yet another contingency, you might need to loop through the result set, or put a [0] in there to indicate that you want to access the "count" field of the first row in the result set. Your mileage may vary depending on exactly what dbquery() does. Keep in mind that there are many different ways of connecting to, and interacting with a database in php, and different implementations may return results in different ways, and may adhere to slightly different conventions. AFAIK, dbquery() is not a standard function in php.
Related
I have made a small app where a table of data is presented to the user. The data can be sorted by different column headers and filter using inputs.
When the user clicks on a row it opens a small popup which contains two arrows for going to the next and previous record, in the same order as they appear in the table.
Originally I had (for "previous" as an e.g.):
SELECT ed.id
FROM entity_details AS ed, users
WHERE ed.id > ?
AND ed.typeRef = ?
AND ed.ownerRef = users.id
$filter
$SQLOrder LIMIT 1
Which works fine if the table is sorted by ed.id, but will not work properly if sorted by another column e.g. ed.name, because the next alphabetical name might have a much higher or lower id.
FYI $filter could be something like:
AND branchRef = 2
and $SQLOrder could be:
ORDER BY ed.name DESC
What do I need to do to make it cycle through the records properly, respecting current order and record position?
All the sorting and filtering parameters come through over AJAX, e.g:
$JSON->selectedbranch ;
I've come to the conclusion that all I need to know is how to start the query from a row with column X containing value Y, is that possible?
You should store the number of the row you displayed, not the ID. Then just do the ordering in SQL as your application requirements imply, then apply the knowledge contained here:
Skipping first n results in MySQL
To simplify the job, and to make this answer usable for future SO dwellers:
SELECT ed.id
FROM entity_details AS ed, users
WHERE ed.typeRef = ?
AND ed.ownerRef = users.id
$filter
$SQLOrder
LIMIT $currentRowNum,1
This scheme smells however: using this to navigate your rows implies a SQL query for each navigation action. That might have an bad effect on your response time...
PHP's mysql_data_seek function may helps.
mysql_data_seek
I found it, seeing that other guys answer gave me an idea, but his answer has disappeared :(
For the next button I have:
$result = $dbh->prepare("SELECT ed.id
FROM entity_details AS ed, users
WHERE $WHERE < ?
AND ed.typeRef = ?
AND ed.ownerRef = users.id
$filter
ORDER BY ed.name DESC LIMIT 1") ;
$WHERE is just the column name "ed.name".
I just have to sort out the dynamics for $where and the ORDER BY clause and it'll be good to go.
Thanks for every ones input!
I have a web page where people are able to post a single number between 0 and 10.
There is like a lotto single number generation once daily. I want my PHP script to check on the the posted numbers of all the users and assign a score of +1 or -1 to the relative winners (or losers).
The problem is that once I query the DB for the list of the winning users, I want to update their "score" field (in "users" table). I was thinking of a loop like this (pseudocode)
foreach winner{
update score +1
}
but this would mean that if there are 100 winners, then there will be 100 queries. Is there a way to do some sort of batch inserting with one single query?
Thanks in advance.
I'll assume you are using a database, with sql, and suggest that would probably want to do something like
UPDATE `table` SET `score`=`score`+1 WHERE `number`=3;
and the corresponding -1 for losers (strange, can't see a reason to -1 them).
Without more details though, I can't be of further help.
You didn't specify how the numbers were stored. If there is a huge number of people posting, a good option is to use a database to store their numbers.
You can have for example a table called lotto with three fields: posted_number, score and email. Create an (non-unique!) index on the posted_number field.
create table lotto (posted_number integer(1) unsigned, score integer, email varchar(255), index(posted_number));
To update their score you can execute two queries:
update lotto set score = score+1 where posted_number = <randomly drawn number here>
update lotto set score = score-1 where posted_number = <randomly drawn number here>
Let's just assume we have a datatable named posts and users.
Obviously, users contain the data of the gambler (with a convenient id field and points for the number of points they have), and posts contain the post_id ID field for the row, user_id, which is the ID of the user and value, the posted number itself.
Now you only need to implement the following SQL queries into your script:
UPDATE users INNER JOIN posts ON users.id = posts.user_id SET users.points = (users.points + 1)
WHERE posts.value = 0;
Where 0 at the end is to be replaced with the randomly drawn number.
What will this query do? With the INNER JOIN construct, it will create a link between the two tables. Automatically, if posts.value matches our number, it will link posts.user_id to users.id, knowing which user has to get his/her points modified. If someone gambled 0, and his ID (posts.user_id) is 8170, the points field will update for the user having user.id = 8170.
If you alter the query to make it (users.points - 1) and WHERE posts.value != 0, you will get the non-winners having one point deducted. It can be tweaked as much as you want.
Just be careful! After each daily draw, the posts table needs to be truncated or archived.
Another option would be storing the timestamp (time() in PHP) of the user betting the number, and when executing, checking against the stored timestamp... whether it is in between the beginning and the end of the current day or not.
Just a tip: you can use graphical database software (like Microsoft Access or LibreOffice Base) to have your JOINs and such simulated on a graphical display. It makes modelling such questions a lot easier for beginners. If you don't want desktop-installed software, trying out an installation of phpMyAdmin is another solution too.
Edit:
Non-relational databases
If you are to use non-relational databases, you will first need to fetch all the winner IDs with:
SELECT user_id FROM posts WHERE value=0;
This will give you a result of multiple rows. Now, you will need to go through this result, one-by-one, and executing the following query:
UPDATE users SET points=(users.points + 1) WHERE id=1;
(0 is the drawn winning number, 1 is the concurrent id of the user to update.)
Without using the relation capabilities of MySQL, but using a MySQL database, the script would look like this:
<?php
$number = 0; // This is the winning number we have drawn
$result = mysql_query("SELECT user_id FROM posts WHERE number=" .$number);
while ( $row = mysql_fetch_assoc($result) )
{
$curpoints_result = mysql_query("SELECT points FROM users WHERE user_id=" .$row['user_id']);
$current_points = mysql_fetch_assoc($curpoints_results);
mysql_query("UPDATE users SET points=" .($current_points['points'] + 1). " WHERE user_id=" .$row['user_id']);
}
?>
The while construct make this loop to run until every row of the result (list of winners) is updated.
Oh and: I know MySQL is a relational database, but it is just what it is: an example.
I have a social network that allows users to post blogs, ask questions, and message. We have had a slight issue with spamming. For example, we had users sign up, and write about 6 blogs a minute trying to sell stuff.
I am looking for a quick way to limit these activities by the id of the user, let's say only allowing them to post 3 blogs a day.
I have the users stored in a $auth variable and through OOP bring them up by $auth->id for example to be more specific.
Looking for a fast,simple way to do this via php.
thanks in advance /**EDIT*****/
this is what I have so far, and I know for sure $too many is counting as it should, but for some reason my if(statement is not stopping the 4th blog from posting. HERE is the code
Something like CAPTCHA would be appropriate. However, if they're being entered manually, it will do little to stop them. Regardless, no reason you can't implement both methods.
I'm assuming you have a created field in your blogs table. Simply query the table for the number of blogs with today's date before allowing another to be posted. Not sure what database/API you're using. In MySQL, you could do:
SELECT COUNT(*)
FROM blogs
WHERE user_id = USERID
AND DATE(created) = '2011-11-30'
When the user writes and submits a post, save the date they posted on the post's table. Select and count the amount of times they posted today. If they are under their limit, allow the post or else give them the error/warning message.
When a post is made, do something like:
// Get last post time and number of posts today from database
$query = "SELECT
last_post,
posts_today
FROM
users
WHERE
id = '$auth->id'";
$result = mysql_fetch_assoc(mysql_query($query));
// See if this is the first post today
$isNewDay = date('Y-m-d') != date('Y-m-d',strtotime($result['last_post']));
$postsToday = ($isNewDay) ? 0 : (int) $result['posts_today'];
// Only add post if user is allowed
if ($isNewDay || $postsToday < 3) {
/*
Add the post to the database here
*/
// Update the number of posts today in the database
$query = "UPDATE
users
SET
last_post = '".date('Y-m-d H:i:s')."',
posts_today = '".($postsToday + 1)."'
WHERE
id = '$auth->id'";
mysql_query($query);
} else {
echo "You have already made 3 posts today!";
}
...or you could just use a CAPTCHA (as mentioned by others). That's what they're for. Really, you should have one in the signup process...
I'll admit I know next to nothing on PHP programming, but another option (or addition to the CAPTCHA) would be to use a service such as StopForumSpam
There's an example of how to use it here (no idea how good it is, as I don't code PHP (yet)) :)
OK - I'll get straight to the point - here's the PHP code in question:
<h2>Highest Rated:</h2>
<?php
// Our query base
$query = $this->db->query("SELECT * FROM code ORDER BY rating DESC");
foreach($query->result() as $row) {
?>
<h3><?php echo $row->title." ID: ";echo $row->id; ?></h3>
<p class="author"><?php $query2 = $this->db->query("SELECT email FROM users WHERE id = ".$row->author);
echo $query2->row('email');?></p>
<?php echo ($this->bbcode->Parse($row->code)); ?>
<?php } ?>
Sorry it's a bit messy, it's still a draft. Anyway, I researched ways to use a Ratings system - previously I had a single 'rating' field as you can see by SELECT * FROM code ORDER BY rating DESC. However I quickly realised calculating averages like that wasn't feasible, so I created five new columns - rating1, rating2, rating3, rating4, rating5. So when 5 users rating something 4 stars, rating4 says 5... does that make sense? Each ratingx column counts the number of times the rating was given.
So anyway: I have this SQL statement:
SELECT id, (ifnull(rating1,0) + ifnull(rating2,0) + ifnull(rating3,0) + ifnull(rating4,0) + ifnull(rating5,0)) /
((rating1 IS NOT NULL) + (rating2 IS NOT NULL) + (rating3 IS NOT NULL) + (rating4 IS NOT NULL) + (rating5 IS NOT NULL)) AS average FROM code
Again messy, but hey. Now what I need to know is how can I incorporate that SQL statement into my script? Ideally you'd think the overall query would be 'SELECT * FROM code ORDER BY (that really long query i just stated) DESC' but I can't quite see that working... how do I do it? Query, store the result in a variable, something like that?
If that makes no sense sorry! But I really appreciate the help :)
Jack
You should go back to the drawing board completely.
<?php
$query = $this->db->query("SELECT * FROM code ORDER BY rating DESC");
foreach($query->result() as $row) {
$this->db->query("SELECT email FROM users WHERE id = ".$row->author;
}
Anytime you see this in your code, stop what you're doing immediately. This is what JOINs are for. You almost never want to loop over the results of a query and issue multiple queries from within that loop.
SELECT code.*, users.email
FROM code
JOIN users ON users.id = code.author
ORDER BY rating DESC
This query will grab all that data in a single resultset, removing the N+1 query problem.
I'm not addressing the rest of your question until you clean up your question some and clarify what you're trying to do.
if you would like to change your tables again, here is my suggestion:
why don't you store two columns: RatingTotal and RatingCount, each user that rates it will increment RatingCount by one, and whatever they vote (5,4,4.2, etc) is added to RatingTotal. You could then just ORDER BY RatingTotal/RatingCount
also, I hope you store which users rated each item, so they don't vote multiple times! and swing the average their way.
First, I'd decide whether your application is write-heavy or read-heavy. If there are a lot more reads than writes, then you want to minimize the amount of work you do on reads (like this script, for example). On the assumption that it's read-heavy, since most webapps are, I'd suggest maintaining the combined average in a separate column and recalculating it whenever a user adds a new rating.
Other options are:
Try ordering by the calculated column name 'average'. SQL Server supports this. . not sure about mysql.
Use a view. You can create a view on your base table that does the average calculation for you and you can query against that.
Also, unrelated to your question, don't do a separate query for each user in your loop. Join the users table to the code table in the original query.
You should include it in the SELECT part:
SELECT *, (if ....) AS average FROM ... ORDER BY average
Edit: assuming that your ifnull statement actually works...
You might also want to look into joins to avoid querying the database again for every user; you can do everything in 1 select statement.
Apart from that I would also say that you only need one average and the number of total votes, that should give you all the information you need.
Some excellent ideas, but I think the best way (as sidereal said that it's more read heavy that write heavy) would be to have columns rating and times_rated, and just do something like this:
new_rating = ((times_rated * rating) + current_rating) / (times_rated + 1)
current_rating being the rating being applied when the person clicks the little stars. This simply weights the current user's rating in an average with the current rating.
I have an app that works with an idea of "redemption codes" (schema: ID, NAME, USES, CODE). And example would be "32, Stack Overflow, 75, 75%67-15hyh"
So this code is given to the SO community, let's say, and it has 75 redemptions. You redeem it by entering some shipping info and the code. When entered, this check is preformed:
if (code exists){
if (count_entries_where_code=$code < $uses_set_at_creation){
//enter information into DB for processing
{
//echo "sorry, not a real code"
}
So the number of total uses is hardcoded, but the current # of redemptions is generated with a SQL query (count_results from entry_data WHERE code=$code). This part works fine, but here is the question:
At the view page where I manage the codes, I have the basic setup (in pseudo PHP, with the real code separated into an MVC setup):
$results = "SELECT * FROM codes";
foreach ($result as $code){
echo $code->code;
echo $code->name;
//etc. It's actually all in a nice HTML table.
}
So I want to have a column listing "# of uses remaining on code". Should something like this be stored in the DB, and drawn out that way? It would be easier to generate with the foreach loop, but I don't usually prefer to store "generated" statistics like that. Is there a clever way to get those results onto the correct rows of the table created with the foreach loop?
(I'm fine with code so I don't need a working/great syntax example, just an explanation of a pattern that might fit this problem, and maybe a discussion of a common design for something like this. Am I right to avoid storing generate-able data like # of uses left? etc.)
Am I right to avoid storing generate-able data like # of uses left?
Yes, you are correct to not store computed values.
Computation logic can change, and working with a stored computed value to reverse engineer it can be a nightmare - if it is possible at all in some cases.
It sounds like you want to combine the two queries:
SELECT c.id,
c.name,
c.uses,
c.code,
x.num_used
FROM CODES c
JOIN (SELECT ed.code,
COUNT(*) 'num_used'
FROM ENTRY_DATA ed
GROUP BY ed.code) x ON x.code = c.code
When you run your query to get the codes for the page add a subquery to get the number of used codes from the entry_data table.
select codes.id, codes.name, codes.uses, codes.code (select count(code) from entry_data where entry_data.code=codes.code ) as used_codes
Id use code_id as a foreign key and not code.
This is all assuming i'm reading your problem correctly