MySQL Search by Relevance of all fields - php

I am sure this is possible but I think it maybe just very complex to write. I want to search every field by:
='SearchTerm'
then
Like %SearchTerm
then
like SearchTerm%
and finally
like %SearchTerm%. I want to run this on every field in my table which there is around 30 or 40. Is there an easy way to run this over multiple fields or will I have to declare every single one?
I think I have seen a query before where different matches between %query %query% etc are ranked by assigning an integer value and then ordering by this. Would that be possible on a query like this?
Any advice and help in the right direction is much appreciated.

You should use fulltext indexing on the fields you want searched and use MATCH AGAINST instead of LIKE %%. It's much faster and returns results based on relevancy. More info here:
Mysql match...against vs. simple like "%term%"

I do something very similar to what you're describing (in php and mysql)
Here's my code:
$search = trim($_GET["search"]);
$searches = explode(" ",$search);
$sql = "SELECT *,wordmatch+descmatch+usagematch+bymatch as `match` FROM (SELECT id,word,LEFT(description,100)as description,
IFNULL((SELECT sum(vote)
FROM vote v
WHERE v.definition_id = d.id),0) as votecount,
";
$sqlword = "";
$sqldesc = "";
$sqlusage = "";
$sqlby = "";
foreach ($searches as $value) {
$value = mysqli_real_escape_string($con,$value);
$sqlword = $sqlword . "+ IFNULL(ROUND((LENGTH(word) - LENGTH(REPLACE(UPPER(word), UPPER('$value'), '')))/LENGTH('$value')),0)";
$sqldesc = $sqldesc . "+ IFNULL(ROUND((LENGTH(description) - LENGTH(REPLACE(UPPER(description), UPPER('$value'), '')))/LENGTH('$value')),0)";
$sqlusage = $sqlusage . "+ IFNULL(ROUND((LENGTH(`usage`) - LENGTH(REPLACE(UPPER(`usage`), UPPER('$value'), '')))/LENGTH('$value')),0)";
$sqlby = $sqlby . "+ IFNULL(ROUND((LENGTH(`by`) - LENGTH(REPLACE(UPPER(`by`), UPPER('$value'), '')))/LENGTH('$value')),0)";
}
$sql = $sql . $sqlword ." as wordmatch,"
. $sqldesc ." as descmatch,"
. $sqlusage ." as usagematch,"
. $sqlby ." as bymatch
FROM definition d
HAVING (wordmatch > 0 OR descmatch > 0 OR usagematch > 0 OR bymatch > 0)
ORDER BY
wordmatch DESC,
descmatch DESC,
usagematch DESC,
bymatch DESC,
votecount DESC)T1";
$queries[] = $sql;
$result = mysqli_query($con,$sql);
You can see this at work http://unurbandictionary.comule.com/view_search.php?search=George+Miley+Cyrus this is when I search for "George Miley Cyrus"
What it does is it explodes the search string to find each word and returns the number of occurences of each word in each of my column, and then i do an ORDER BY to have relevance (priority) to come back first. So in my case word field has the highest relevance, then description field, then usage field, then by field.
Before this version of my code I was using LIKE but it didn't give me a count of occurences, since I want the row with the most occurences of my search word to return first before other rows.

You should really have some sort of id to select the rows in your table.
You should have put a column with
id INT NOT NULL AUTO_INCREMENT PRIMARY KEY
Then you could use
SELECT * FROM table WHERE column1 LIKE "%SearchTerm%" AND id BETWEEN 1 AND 40

Related

Search with multiple tables & ORDER BY php mysql

I have a script i want to search multiple tables how can i do that.
**Also add ORDER BY function in Mysql query **
help is appreciated.
if(isset($_GET["search"]))
{
$condition = '';
//$query = explode(" ", $_GET["search"]);
$query = explode(" ", $_GET["search"]);
foreach($query as $text)
{
$condition .= "title LIKE +'%".mysqli_real_escape_string($connect, $text)."%' OR ";
}
$condition = substr($condition, 0, -4);
$sql_query = "SELECT * FROM countries WHERE " . $condition;
$result = mysqli_query($connect, $sql_query);
if(mysqli_num_rows($result) > 0)
{
while($row = mysqli_fetch_array($result))
{
echo '<tr><td>'.$row["title"].'</td></tr>';
}
}
else
{
echo '<label>Data not Found</label>';
}
}
SELECT * FROM (
(SELECT title FROM countries WHERE title LIKE '%mystring%')
UNION
(SELECT title FROM locations WHERE title LIKE '%mystring%')
) ta
That's the sql, but would need to point out the drawbacks of using union as a search tool.
a) The longer the tables get the longer the search will get, you can add in Limits on each query and then on the union as a whole - but it's not a winner.
b) The table columns have to match up so you'll need perhaps to do myID as ID, then you will need an extra column to say which each is (0=country, 1= location)
c) I guess you are trying to do a site search of sorts, in which case there isn't a relevance in this instance.
Hence I would use something like http://sphinxsearch.com/ bit tricky to get started, but a really quick search engine. If you have a large site. Otherwise look into mysql full text searches which also have relevence and are built in so easier to get started with.

PHP Query LIMIT DESC not working properly

I am trying to get a list of highscores (top 10) of players that played my game.
Everything works except for one thing, the list isn't right.
The first 10 people are correct, but when i go to page 2 the list isn't further going done.
Variables Explain
$gamemode = "Endless"
$filter = "Score"
$startNum = Page1 = 0, Page2 = 10
$maxlimit = 10
Query:
$query = "SELECT ID, Gamemode, Name, Score, ContainersSaved, TimePlayed, Date, ScorePerMinute
FROM $dbName . `highscore`
WHERE Gamemode='$gamemode'
ORDER by `$filter` DESC
LIMIT $startNum, $maxlimit";
Does anyone know what im doing wrong?
If your Score field is varchar try: (or change it to INT)
$order=$filter;
if($filter=='Score') {
$order="ABS($filter);
}
$query = "SELECT ID,Gamemode,Name,Score,ContainersSaved,TimePlayed,Date,ScorePerMinute FROM $dbName . `highscore` WHERE Gamemode='$gamemode' ORDER by $order DESC LIMIT $startNum, $maxlimit";
I fixed it, i forgot to change the 'Score' field from varchar to an INT, so it was trying to Descend on string instead of INT.
Thanks to the tip Imaginaerum gave me :)

How to sort MySQL results by most matches when using LIKE

I´m currently working on a query that must show a list of all articles from a specific table, but it must sort the list according to a search form, so that the articles that contain most/best matches are shown first and those that do not have any matches at all will be shown last sorted alphabetically.
I have made this code which is working fine, though I cannot find a way to sort the matches by most hits / relevance.
Here is my code:
$search = $_POST["searhwords"];
$search = preg_replace('/\s+/', ' ',$search);
$SearchQueryArray = str_replace(",", "", $search);
$SearchQueryArray = str_replace(" ", ",", $SearchQueryArray);
$SearchQueryArray = explode(',', $SearchQueryArray);
$outputtt1 = '';
$outputtt2 = '';
foreach ( $SearchQueryArray as $queryword )
{
$outputtt1 .= "title LIKE '%".$queryword."%' OR ";
$outputtt2 .= "title NOT LIKE '%".$queryword."%' AND ";
}
$outputtt1 = rtrim($outputtt1, ' OR ');
$outputtt2 = rtrim($outputtt2, ' AND ');
$query_for_result = mysql_query("SELECT * from mytable
WHERE ".$outputtt1."
union all
SELECT * from mytable
WHERE ".$outputtt2."
");
So I need to find a way to sort the article that contain matches so that those that contain most matches are sorted first.
You can see the script i Have made live here:
http://www.genius-webdesign.com/test/querytest.php
Here is the SQL that does this:
select t.*
from mytable
order by ((title like '%keyword1%') +
(title like '%keyword2%') +
(title like '%keyword3%') +
. . .
(title like '%keywordn%')
) desc;
MySQL treats boolean expressions as numbers, with true being 1. So, this counts the number of matches.
By the way, if your data has any size, you might find full text search is more efficient than using like.
EDIT:
Counting the number of keywords is a bit more challenging, but you can do it as:
order by ((length(replace(title, 'keyword1', 'x')) -
length(replace(title, 'keyword1', '')
) +
(length(replace(title, 'keyword2', 'x')) -
length(replace(title, 'keyword2', '')
) +
. . .
(length(replace(title, 'keywordn', 'x')) -
length(replace(title, 'keywordn', '')
)
);
Counting the number of appearance of a keyword is more cumbersome than merely looking for where or not it is present.
Another way to do it using full-text search
SELECT *,
MATCH('title') AGAINST($_GET['query']) * 10 as score1,
MATCH('content') AGAINST($_GET['query']) * 5 AS score2
FROM articles
WHERE MATCH (title, content) AGAINST($_GET['query'])
ORDER BY (score1) + (score2) DESC;
Alter your table like this if needed
ALTER TABLE articles ENGINE = MYISAM;
ALTER TABLE articles ADD FULLTEXT(title, content);

Can I change values in a SQL array in PHP and resort?

I have a SELECT statement that pulls a limited number of items based on the value of one of the fields. (ie ORDER BY rate LIMIT 15).
However, I need to do some comparisons that and change the value of rate, and subsequently could alter the results that I want.
I could pull everything (without the LIMIT), alter the rate, re-sort, and then just process the number that I need. However, I don't know if it's possible to alter values in a php result array. I'm using:
$query_raw = "SELECT dl.dragon_list_id, dl.dragon_id, dl.dragon_name, dl.dragon_level, d.type, d.opposite, d.image, dr.dragon_earn_rate
FROM dragon_list dl
LEFT JOIN dragons d ON d.dragon_id = dl.dragon_id
LEFT JOIN dragon_rates dr ON dr.dragon_id = dl.dragon_id
AND dr.dragon_level = dl.dragon_level
WHERE dl.dragon_id IN (
SELECT dragon_id
FROM dragon_elements
WHERE element_id = 3
)
AND dl.dragon_list_id NOT IN (
SELECT dh.dragon_list_id
FROM dragon_to_habitat dh, dragon_list dl
WHERE dl.user_id = 1
AND dh.dragon_list_id = dl.dragon_list_id
AND dl.is_deleted = 0
)
AND dl.user_id = " . $userid . "
AND dl.is_deleted = 0
ORDER BY dr.dragon_earn_rate DESC, dl.dragon_name
LIMIT 15;";
$query = mysqli_query($link, $query_raw);
if (!$query) {
echo "DB Error, could not query the database\n";
echo 'MySQL Error: ' . mysqli_error($link);
exit;
}
$d = mysqli_fetch_array($d_query);
Well, after a lot of research and some trial and error I found my answers....
Yes, I CAN alter the result rows using something like:
$result['field'] = $newvalue;
I also learned I could reset the pointer by using:
mysqli_data_seek($d_query,0);
However, when I reset the counter, I lost the changes I made. So ultimately, I'm still a little stuck, but individually I had the answers.

SQL query results returned, even if exact match not found

I hope this question isn't redundant. What I am trying to accomplish is have a user select a bunch of checkboxes on a page and return the closest matching records if there are no matching rows. For example:
A person checks off [x]Apples [x]Oranges [x]Pears [x]Bananas
But the table looks like this:
Apples Oranges Pears Bananas
1 1 1 null
1 1 null 1
1 1 null null
(Obviously I missed the id column here, but you get the point I think.) So, the desired result is to have those three rows still be returned in order of most matches, so pretty much the order they are in now. I'm just not sure what the best approach to take on something like this. I've considered a full text search, the levenshtein function, but I really like the idea of returning the exact match if it exists. No need for you to go at length with code if not needed. I'm just hoping to be sent in the right direction. I HAVE seen other questions sort of like this, but I still am unsure about which way to go.
Thanks!
Write a query that adds up the number of columns that matched, and sorts the rows by this total. E.g.
SELECT *
FROM mytable
ORDER BY COALESCE(Apples, 0) = $apples + COALESCE(Oranges, 0) = $oranges + ... DESC
It's easy to sort by a score...
SELECT fb.ID, fb.Apples, fb.Oranges, fb.Pears, fb.Bananas
FROM FruitBasket fb
ORDER BY
CASE WHEN #Apples = fb.Apples THEN 1 ELSE 0 END
+ CASE WHEN #Oranges = fb.Oranges THEN 1 ELSE 0 END
+ CASE WHEN #Pears = fb.Pears THEN 1 ELSE 0 END
+ CASE WHEN #Bananas = fb.Bananas THEN 1 ELSE 0 END
DESC, ID
However, this leads to a table-scan (even with TOP). The last record may be a better match than the records found so far, so every record must be read.
You could consider a tagging system, like this
Content --< ContentTag >-- Tag
Which would be queried this way:
SELECT ContentID
FROM ContentTag
WHERE TagID in (334, 338, 342)
GROUP BY ContentID
ORDER BY COUNT(DISTINCT TagID) desc
An index on ContentTag.TagId would be used by this query.
This is fairly simple, but you can just use IFNULL() (MySQL, or your DB's equivalent) to return a sum of matches and use that in your ORDER BY
// columns and weighting score
$types = array("oranges"=>1, "apples"=>1, "bananas"=>1, "pears"=>1);
$where = array();
// loop through the columns
foreach ($types as $key=>&$weight){
// if there is a match in $_REQUEST at it to $where and increase the weight
if (isset($_REQUEST[$key])){
$where[] = $key . " = 1";
$weight = 2;
}
}
// build the WHERE clause
$where_str = (count($where)>0)? "WHERE " . implode(" OR ", $where) : "";
// build the SQL - non-null matches from the WHERE will be weighted higher
$sql = "SELECT apples, oranges, pears, bananas, ";
foreach ($types as $key=>$weight){
$sql .= "IFNULL({$key}, 0, {$weight}) + ";
}
$sql .= "0 AS score FROM `table` {$where_str} ORDER BY score DESC";
Assuming that "oranges" and "apples" are selection, your SQL will be:
SELECT apples, oranges, pears, bananas,
IFNULL(apples, 0, 2) + IFNULL(oranges, 0, 2) + IFNULL(pears, 0, 1) + IFNULL(bananas, 0, 1) + 0 AS score
FROM `table`
WHERE oranges = 1 OR apples = 1
ORDER BY score DESC
Order descending by the sum of checkbox/data matches
SELECT * FROM table
ORDER BY (COALESE(Apple,0) * #apple) + (COALESE(Orange,0) * #orange) ..... DESC
where #apple / #orange represents users selection: 1 = checked, 0 = unchecked

Categories