PHP/MySQL make top 10 out of selection - php

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

Related

SQL: in calculation, I want the result to be 0, not null

I have a SQL query in PHP that recalculates the average rating when the user cancels his vote:
$delete_vote = $conn->prepare("UPDATE table SET
votes = votes - 1,
total_value = total_value - :recall_vote,
average = (total_value * 10) / (total_votes * 10)
WHERE id=id");
The problem is with the recalculation of the average vote. Even if I set the default value for the column 'total_value' and for 'average' as zero, if the query in question has only one vote, and that vote is being recalled, the average value will be set to null instead of 0, as I wish it to be. One possible way of solving this would be to retrieve these two values from the database, check if they're null, and then have them changed to 0--but that's a bit of a hassle. So instead, I want to know if there's a simpler solution.
Another thing I've tried is to add a zero to the calculation, hoping that the null would convert to 0:
$vote_count = $conn->prepare("UPDATE table SET
total_votes = (0 + total_votes) + 1
That doesn't work either. Is there a simple solution to this?
Since you're using PHP, I'm assuming you're using MySQL with it. But I think every RDBMS has a similar function... In MySQL you can define a fallback value for when a value is NULL. If the value isn't NULL, it will return the original value:
IFNULL(something, 0)
Besides that, slightly offtopic maybe; I usually try to avoid denormalisation like you did by saving the average - which in most cases can be calculated when querying the database. But this depends on the situation.
I totally do not understand your where clause -- which would almost always evaluate to true.
The correct syntax to use is the ANSI standard COALESCE() or CASE which are available in almost all databases. You could write this as:
UPDATE table
SET votes = votes - 1,
total_value = total_value - :recall_vote,
average = COALESCE((total_value * 10) / NULLIF(total_votes * 10, 0), 0)
WHERE id = $id -- something other than `id`
I think I would be inclined to be explicit:
UPDATE table
SET votes = votes - 1,
total_value = total_value - :recall_vote,
average = (CASE WHEN total_votes > 0
THEN total_value / total_votes
ELSE 0
END)
WHERE id = $id -- something other than `id`
By the way, why are you multiplying by 10 in both the numerator and denominator? Seems unnecessary.

select count doesn't count

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

Finding Maximum value from a Varchar Field

I have a database field know as SCORES which has Scores
the value may be like the following
123
14
56*
342
423*
I am storing that in a Varchar Field in the database.
Suppose If I convert that to a integer a datatype, then I can write
max(SCORES) and get the Maximum score or Highest Scores.
But Integer doesnot allow special character like *.
(Here * represent some clause for that scores)
To accomadate that I have made that to the varchar.
What will be best way to get the Highest score very easily with minimum programming method.
So that If I execute a query I should be get the answer as
423*
Please suggest me
The best way to handle this situation is to change you table structure to make
SCORES of int data type.
Add a new field in the table called
clause
If most of your SCORES are without a
clause, you must normalize the table
to move the clause field to a
different table.
You should change table schema ...
SELECT scores
FROM tablename
ORDER BY replace(scores, '*', '') DESC
LIMIT 1;
I think your query should be generic for getting maximum score, today you have only '*' is attached with score but in future may be you use some others character or may be you use some combination of character so you should take care of that scenario.
so i thing it will be better if you create a user define function which takes varchar as a input and return number from input string like if you pass '1234*' then function will return 1234 and if you pass 1234** it will return 1234.
CREATE FUNCTION dbo.ParseNumeric
(
#string VARCHAR(8000)
)
RETURNS VARCHAR(8000)
AS
BEGIN
DECLARE #IncorrectCharLoc SMALLINT
SET #IncorrectCharLoc = PATINDEX('%[^0-9A-Za-z]%', #string)
WHILE #IncorrectCharLoc > 0
BEGIN
SET #string = STUFF(#string, #IncorrectCharLoc, 1, '')
SET #IncorrectCharLoc = PATINDEX('%[^0-9]%', #string)
END
SET #string = #string
RETURN #string
END
GO
then use:
select max(cast(dbo.ParseNumeric(score) as int)) from tableName
SELECT MAX(CAST(scores AS SIGNED))
FROM tablename
But this query will be quite slow, since it cannot be optimized using indexes.
You could move the asterisk to a new field (depends if you need it or not). After that you can change the datatype.
UPDATE
tableName
SET
score = REPLACE(score, '*', ''),
specialCharField = IF(CONTAINS(score, '*'), '*', '');
Try this SQL:
SELECT MAX(CONVERT(REPLACE(FIELD,'*',''),signed)) FROM TABLE
It:
Replace the * from your varchar
Covnert the result into integer (signed)
Select the MAX value
EDIT 1:
I have been tring the SQL with some data, i have a table with one field (varchar, no key), I've inserted 62962 values (one each 1000 have a '*', the results as follow :)
SELECT MAX(CONVERT(REPLACE(FIELD,'*',''),signed)) FROM TABLE
Took 0.0666sec.
SELECT scores FROM tablename ORDER BY replace(scores, '*', '') DESC LIMIT 1;
Took 0.089sec to execute and got the wrong value:
HTH :)

Best update query for mysql table

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;

How to find missing data either in array or in mySQL table?

I have an array filled with values (twitter ids) and I would like to find the missing data between the lowest id and the highest id? Any care to share a simple function or idea on how to do this?
Also, I was wondering if I can do the same with mySQL? I have the key indexed. The table contains 250k rows right now, so a temporary table and then a join wouldn't be very fast or efficient. I could do a PHP loop to loop through the data, but that would also take a long time, and a great deal of memory. Is there a specific mysql query I can run? or can I somehow use the function from above with this?
Thanks,
James Hartig
http://twittertrend.net
I had a similar requirement and wrote a function that would return a list of missing IDs.
---------------------------
create function dbo.FreeIDs ()
---------------------------
returns #tbl table (FreeID int)
as
begin
declare #Max int
declare #i int
select #Max = MAX(ID) from [TheTable]
set #i = 0
while #i < #Max begin
set #i = #i + 1
if not exists (select * from [TheTable] where ID = #i)
insert into #tbl select #i
end
return
end
Do you mean sequential ID's?
In that case
$new_ids = range($lowid, $highid, 1);
$ids = array_merge($ids, $new_ids);
$ids = array_unique($ids);
sort($ids);
And in SQL (with placeholders)
SELECT key, other_data from `table` WHERE key > :low_id AND key < :high_id
Your range() gave me a good idea, your code didn't work as unique preserves unique keys, so I was just left with the range functions result.
However, this worked:
$diff = array_values(array_diff(range(min($array), max($array), 1), $array)); //returns array of incomplete values

Categories