I have a table say 3 fields in MySQL. I need to search for a string using PHP
My code is like
<?PHP
//SET THE SEARCH TERM
$term = "Search Term";
//QUERY THE TITLE AND CONTENT COLUMN OF THE PAGES TABLE RANK IT BY THE SCORE
$sql = "SELECT *, MATCH(title, content) AGAINST('". $term ."') as score FROM pages WHERE MATCH (title, content) AGAINST('". $term ."') ORDER BY score DESC";
$query = mysql_query($sql);
//BUILD A LIST OF THE RESULTS
while($result = mysql_fetch_assoc($query)) {
echo("{$result['title']} - {$result['score']}");
}
?>
Here it searched for the single word which is exactly in the database.
I need to search for multiple words..How can I change the above code.
Can someone suggest some idea.
MATCH (title,content) AGAINST ('Search Term' IN BOOLEAN MODE)
would search for one of the words
MATCH (title,content) AGAINST ('+Search +Term' IN BOOLEAN MODE)
would search both words
MATCH (title,content) AGAINST ('-Search +Term' IN BOOLEAN MODE)
would search Term without Search
just split the words and build it with using + - | , whatever needed.
check documentation on : http://dev.mysql.com/doc/refman/5.5/en/fulltext-boolean.html
You could just add the boolean mode switch to the match - but this does not sort the results by relevance.
You could try something like.....
$words=explode(' ', $term);
$score='CASE ';
$filter=array();
foreach ($words as $try_word) {
$score.="WHEN MATCH(title, content) AGAINST('$try_word') THEN 1 ";
$filter[]="MATCH(title, content) AGAINST('$try_word')";
}
$score.="ELSE 0 END CASE";
$qry="SELECT SUM(SCORE), " . ** add explicit list of fields here **
. " FROM pages WHERE (" . implode(' OR ', $filter) . ")"
. " GROUP BY " . ** add explicit list of fields here **
. " ORDER BY 1 DESC";
Note that this gets highlights the problem that full text searching in mysql is not the ideal solution to the problem - a better approach is to implement a separate table of extracted keywords and a temporary set of search words and perform a query against all three tables:
SELECT COUNT(*) as matched_fields, " . ** add explicit list of fields from page table here ** . "
FROM pages p, keywords k, search_terms s
WHERE k.word=s.word
AND s.session_id='" . some_session_or_request_identifier() . "'
AND k.page_id=p.id
GROUP BY " . ** add explicit list of fields from page table here ** . "
ORDER BY 1 DESC
In addition to simplifying the code somewhat and allowing very large sets of search terms but efficient searching, you can backpopulate relevance scores into the keywords table and use those to generate the score.
Related
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.
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);
We're having a tough time debugging because our retired predecessor has a query statement that puts pulled data into a series of temporary tables before uploading to the database. It's not working now (we added items to TestPatterns table, and running for tp7 instead of tp21 so data may differ) and we're having a tough time debugging it. If we could somehow print or access the data put in the temporary tables, maybe we could debug it better. Any ideas on how to debug this better, and maybe see the data? We can print our first select/where statement, but not the data it pulls when the qry is executed. Plus there's the Source/Target sql merges we do. We know everything seems to work until we execute the $qry1d.
I looked online for how to view this info, and see the sql profiler, but I'm worried the statements are too complex to decipher the profiler output. When I tried to profile withhttp://youtu.be/mJ8Dyv4Uk6E, for a simple select top 1000 rows, it said:
exec sp_executesql N'SELECT
clmns.name AS [Name]
FROM
sys.tables AS tbl
INNER JOIN sys.all_columns AS clmns ON clmns.object_id=tbl.object_id
WHERE
(CAST(clmns.is_sparse AS bit)=#_msparam_0)and((tbl.name=#_msparam_1 and SCHEMA_NAME(tbl.schema_id)=#_msparam_2))
ORDER BY
clmns.column_id ASC',N'#_msparam_0 nvarchar(4000),#_msparam_1 nvarchar(4000),#_msparam_2 nvarchar(4000)',#_msparam_0=N'0',#_msparam_1=N'Measurements',#_msparam_2=N'dbo'
It's failing with
The MERGE statement attempted to UPDATE or DELETE the same row more
than once. This happens when a target row matches more than one source
row. A MERGE statement cannot UPDATE/DELETE the same row of the target
table multiple times. Refine the ON clause to ensure a target row
matches at most one source row, or use the GROUP BY clause to group
the source rows.
This is a couple of the php sql merge statements:
$qry = 'SELECT "PrintSamples"."PrintSampleID", "PrintSamples"."TestPatternName", "PrintSamples"."PrintCopyID",
"DigitalImages"."CaptureTime", "PrintSampleAnalyses"."psaTicket", "Measurements"."MeasurementID", "Measurements"."MeasurementUuid",
SUBSTRING("OperatorLastName",1,1) AS "lastInitial", SUBSTRING("OperatorFirstName",1,1) AS "firstInitial",
"ParameterValues"."ParameterID", "ParameterName", "TargetName", "ParameterValues"."ParameterValue"
FROM "ParameterValues"
LEFT JOIN "Measurements" ON "ParameterValues"."MeasurementID"="Measurements"."MeasurementID"
LEFT JOIN "PrintSampleAnalyses" ON "PrintSampleAnalyses"."psaID"="Measurements"."psaID"
LEFT JOIN "DigitalImages" ON "DigitalImages"."ImageID"="PrintSampleAnalyses"."ImageID"
LEFT JOIN "PrintSamples" ON "DigitalImages"."PrintSampleID"="PrintSamples"."PrintSampleID"
LEFT JOIN "Sessions" ON "Sessions"."SessionID"="PrintSampleAnalyses"."SessionID"
LEFT JOIN "Operators" ON "Operators"."OperatorID"="Sessions"."OperatorID"
LEFT JOIN "ParameterNames" ON "ParameterNames"."ParameterID"="ParameterValues"."ParameterID"
LEFT JOIN "Targets" ON "Targets"."TargetID"="Measurements"."TargetID"
WHERE (';
//----------------------------------------------------------------------------------------------------
// The two statements where added to make AvgGhostValAsSir77 and MaxNegGhostingValAsSir77 work.
// After much testing it was found that the query did not recognize these to ParameterNames in the
// Postgres database. It was never discovered why when testing the ParameterName to be equal to
// AvgGhostValAsSir77 or MaxNegGhostingValAsSir77 the query fails but using the LIKE statement
// corrected the problem. It is possible that the names contain a hidden character or space that
// caused the proble. More investigation will need to be done to find a better resolution to this
// strange problem. On 8/1/13 - It was found that the two Parameters AvgGhostValAsSir77 and MaxNegGhostingValAsSir77
// have a trailing space to thier names in the Postgres database and that's why these two parameters
// were NOT working. It was decided instead of having IQAF people modify the database that the two
// statements using the LIKE will remain in place. The file was modified on this date 8/1/13.
//----------------------------------------------------------------------------------------------------
// only take values that actually get reported on the dashboard
// this list comes from the "MeasurementNames" table
foreach ($measurementIDs as $mid){
if($mid[0] == "AvgGhostValAsSir77") $qry .= '(("ParameterName" LIKE ' . "'%AvgGhostValAsSir77%'" . ') AND ("TargetName"=' . "'" . $mid[1] . "')) OR ";
else if($mid[0] == "MaxNegGhostingValAsSir77") $qry .= '(("ParameterName" LIKE ' . "'%MaxNegGhostingValAsSir77%'" . ') AND ("TargetName"=' . "'" . $mid[1] . "')) OR ";
else $qry .= '(("ParameterName"=' . "'" . $mid[0] . "'" . ') AND ("TargetName"=' . "'" . $mid[1] . "')) OR ";
}
$qry = substr ($qry, 0, -4);
$qry .= ") ";
if (isset($captureTime)){ // used for incremental updates
$qry .= ' AND ("CaptureTime">' . "'" . $captureTime . "')";
}
// steve invalid reading code is -99999.
$qry .= ' AND ("ParameterValues"."ParameterValue" != -99999) ORDER BY "PrintSampleID", "MeasurementID"';
$actionString = '$action';
$qryCreate = "CREATE TABLE #tmpMeasurementTable (TestGUID uniqueidentifier,
IQAFid uniqueidentifier, pqID int, MeasurementID int,
EvaluationDate datetime, EvaluatorID int,
TestUnitID int, TestUnitCountID int,
TestPatternID int, ColorID int,
TargetID int, ParameterID int,
ParameterValue real)
CREATE TABLE #MergeOutput (ActionType nvarchar(10))";
//start putting data into measurement tables
$qry1a= "INSERT INTO #tmpMeasurementTable VALUES ";
//put 1a data in MeasurementData
$qry1b = "
MERGE INTO MeasurementData AS Target
USING #tmpMeasurementTable AS Source
ON Target.pqID=Source.pqID
AND Target.MeasurementID=Source.MeasurementID
AND Target.TargetID=Source.TargetID
AND Target.ParameterID=Source.ParameterID
AND Target.TestPatternID=Source.TestPatternID
AND Target.IQAFMeasurementID=Source.IQAFid
WHEN MATCHED THEN
UPDATE SET Target.ParameterValue = Source.ParameterValue,
Target.TestUnitID=Source.TestUnitID,
Target.TestUnitCountID=Source.TestUnitCountID,
Target.EvaluationDate=Source.EvaluationDate,
Target.EvaluatorID=Source.EvaluatorID
WHEN NOT MATCHED BY Target THEN
INSERT (TestGUID, IQAFMeasurementID, pqID,
MeasurementID, EvaluationDate, EvaluatorID,
TestUnitID, TestUnitCountID, TestPatternID, ColorID,
TargetID, ParameterID, ParameterValue)
VALUES (Source.TestGUID, Source.IQAFid, Source.pqID,
Source.MeasurementID, Source.EvaluationDate, Source.EvaluatorID,
Source.TestUnitID, Source.TestUnitCountID,
Source.TestPatternID, Source.ColorID,
Source.TargetID, Source.ParameterID, Source.ParameterValue)
OUTPUT
$actionString INTO #MergeOutput;
DROP TABLE #tmpMeasurementTable";
$qryOutput = "SELECT ActionType, COUNT(ActionType)AS [count] FROM #MergeOutput GROUP BY ActionType";
$qryCleanup = "DROP TABLE #MergeOutput";
$qry1c = "IF EXISTS (SELECT * FROM tempdb.dbo.sysobjects WHERE ID = OBJECT_ID(N'tempdb..#MergeOutput') AND xtype='U')
DROP TABLE #MergeOutput
CREATE TABLE #MergeOutput (ActionType nvarchar(10))
SELECT TestIndex AS TestID, TestID AS TestNumber, MeasurementData.TestGUID, pqID, TestUnitID, TestUnitCountID, TestPatternID,
ColorID, MeasurementData.MeasurementID, TargetID, ParameterID,
CAST(ROUND(AVG(ParameterValue*Multiplier), 2)AS DECIMAL(18,2)) AS Value
INTO #tmpTable
FROM MeasurementData
LEFT JOIN Measurements ON Measurements.MeasurementID=MeasurementData.MeasurementID
LEFT JOIN Tests ON Tests.TestGUID=MeasurementData.TestGUID AND Tests.PiggybackID IS NULL
WHERE MeasurementData.TestGUID='" . $TestGUID . "' AND pqID>=" . $startPQid . " AND pqID<=" .$endPQid;
if (isset($captureTime)) // used for incremental updates
{
$qry1c .= " AND EvaluationDate>'" . makeDateTime($captureTime) . "'";
}
$qry1c .= " GROUP BY TestIndex, TestID, MeasurementData.TestGUID, pqID, TestUnitID, TestUnitCountID, TestPatternID,
ColorID, MeasurementData.MeasurementID, TargetID, ParameterID
ORDER BY ColorID, TestPatternID, TestUnitCountID, MeasurementData.MeasurementID, TargetID, ParameterID;
SELECT ##ROWCOUNT AS rows INTO #DashboardRows;";
//put temporary table in dashboard table
$qry1d = ";
MERGE INTO DashboardData AS Target
USING #tmpTable AS Source
ON Target.pqID=Source.pqID
AND Target.MeasurementID=Source.MeasurementID
AND Target.TargetID=Source.TargetID
AND Target.ParameterID=Source.ParameterID
AND Target.TestPatternID=Source.TestPatternID
WHEN MATCHED THEN
UPDATE SET Target.ParameterValue=Source.Value,
Target.TestUnitID=Source.TestUnitID,
Target.TestUnitCountID=Source.TestUnitCountID
WHEN NOT MATCHED BY Target THEN
INSERT (TestGUID, pqID,
MeasurementID,
TestUnitID, TestUnitCountID, TestPatternID, ColorID,
TargetID, ParameterID, ParameterValue,
TestNumber, TestIndex)
VALUES (Source.TestGUID, Source.pqID,
Source.MeasurementID,
Source.TestUnitID, Source.TestUnitCountID,
Source.TestPatternID, Source.ColorID,
Source.TargetID, Source.ParameterID, Source.Value,
Source.TestNumber, Source.TestID)
OUTPUT
$actionString INTO #MergeOutput;
DROP TABLE #tmpTable";
The queries get executed like this:
$result = $ms_conn->query($qry1c); $recordCount = $ms_conn->fetchOne
("SELECT rows FROM #DashboardRows"); //dataLog ("OLAP database " .
$recordCount . " records to process", true); $result =
$ms_conn->query($qry1d); dataLog ("end dashboard query");
To look at the intermediate tables, remove the first character # from the table names. This will make the tables non temporary, as SQL Server ha the convention that a table is temporary if its name starts with this character. You might also want to remove the drop table statements from the code. Then you can just look into these intermediate tables like into any other table.
Of course, to be able to re-run the code, you would have to drop the tables manually.
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
I have a comics site and have implemented a tagging system similar to that of StackOverflow's.
I wanted to give users more sorting options by introducing a SO-style tagging system - each user can select and remove 1 or many tags... each comic id is associated to 1 or many tags.
The goals are:
Function will display all comics if no category and no tags are chosen
If category x is selected, will display only that category's comics
If category x AND tag x is selected, will display all comics in category x AND tag x
If NO category is selected, but tags x, y, z are selected, function will display only those comics associated with those tags
I'm using a relational table to check if there's a matching imgid to the selected tagids.
So in the database, in order for the image to be associated to multiple tags (which it should be able to), I have to add the same imgid again, with a different tagid.
Here's the query:
$sql =
"SELECT tbl.*, t.*
FROM $table tbl
LEFT JOIN $assocTable a ON (tbl.id = a.imgID)
LEFT JOIN $tagsTable t ON (t.tagid = a.tagID)
WHERE ";
$sql .= !empty($_SESSION[$sessiontagIDs]) ? "a.tagID IN (" . implode(', ', $_SESSION[$sessiontagIDs]). ") " : "1 = 1";
$sql .= $catquery ." " . $order;
Unfortunately, that means that a comic is now showing twice from the query:
Do I have the tables or query set up incorrectly?
Thank you!
The problem is that you are fetching the comic for every tag that it matches. Perhaps you want the query to get:
select distinct tbl.*
. . .
Or, if you want the tags at the same time, use group_concat() to get them in a list:
select tbl.*, group_concat(t.tagname)
. . .
group by tbl.id