In my database I have a number: 00 48 76 848
The string to match against is: 004876848
I have:
...
WHERE tel REGEXP '\s($q)'
...
But that doesn't work.
What regexp do I need to achieve this?
$q = preg_replace("/[\\D]+/", "", $q);
return DB::select('SELECT contacts_phones.id, CONCAT(firstName, " ", lastName) AS name, tel, "phoneNumber" AS type, entities.name AS company,
entities.id AS companyId
FROM contacts_phones, contacts, entities
WHERE tel REGEXP "[[:space:]]"
AND contacts_phones.contactId = contacts.id
AND entities.id = contacts.ownerId
AND contacts.ownerTypeId = 1
AND contacts.archived = 0
LIMIT ' . $limit, array('%' . $q . '%'));
You can't tell regex just to "ignore" whitespace. You can only match it, which is next to impossible with a value like yours.
An easier approach, without regex, would be to remove the spaces with REPLACE() before you test it against your value:
WHERE REPLACE(`tel`, ' ', '') = '004876848'
Good practice would be to normalize your values before you store them in the database. That makes everything else a lot easier.
WHERE tel REGEXP '[[:space:]]';
Related
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);
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
Say I have some records in my database with data like so:
John Murdoch
I am John Murdoch
My first name is john and my second name is murdoch
I have a search form and I type in "john murdoch" which will run this query:
$search //contain the search string in this case is john murdoch
$sql = mysqli_query($sql, "SELECT * FROM table WHERE column LIKE '%$search%'");
while($row = mysql_fetch_assoc($sql)){
echo $row['first']."<br>";
}
This will return the first two rows only because it is only those rows that have the both words beside each other. How can I return the other row even though the words are split up? I know I could explode the string and check each piece, but I was looking for a more stable and efficient way of doing this.
Just replace punctuation and spaces with the wildcard % before your query.
$search = str_replace ( array( '.', ' ', "'", '-' ), '%', $search );
This does still require the first word to appear in the text before the second word. So if your search was for "Murdoch John", you would not return any results.
The better answer is to employ FULLTEXT searching on the column (must do this in MySQL), and do a MATCH() query against the column, like so:
Add a plus sign before each word (to indicate that word is required)
$words = '+' . str_replace( ' ', ' +', $search );
And your query:
SELECT * FROM table MATCH ( column ) AGAINST ( '$words' IN BOOLEAN MODE)
More info here: http://dev.mysql.com/doc/refman/5.5/en/fulltext-boolean.html
SELECT * FROM table WHERE `column` LIKE '%john%' AND `column` LIKE '%murdoch%'
I would construct that query like this:
$search_terms = array('john','murdoch');
$field_to_search = 'some_column';
$sql = 'SELECT * FROM table WHERE' . "\n";
$sql .= '`' . $field_to_search . '` LIKE \'%'
. implode('%\' AND `' . $field_to_search . '` LIKE \'%',$search_terms)
. '%\'';
That PHP can be used with any number of search terms. It requires matches for all because it's connected with AND.
Here's a link to a php fiddle: http://phpfiddle.org/main/code/igv-3qc
When I run my code I cannot retrieve the entry that I am targeting because the keywords that are being run through the query are not the same as the keywords in the database.
Keywords:
$keywords = 'spaghetti,macaroni,hamburger';
Query:
mysql_query("SELECT * FROM ---- WHERE keywords LIKE '%$keywords%' ORDER BY id DESC LIMIT 1");
I am targeting an entry with these keywords:
food, restaurant, hamburger, spaghetti, taco,pie
I may be missing something simple here since I've been working for 12 hours straight. However, I've had no luck with anything I've tried to do to work around this issue.
From my point of view the query should be returning the target entry since there are 2 matching keywords.
What should I be doing differently?
$like = array();
$keywords = explode(',' , $keywords);
foreach($keywords as $keyword) {
$like[] = "`keywords` LIKE '%$keyword%'";
}
$like = implode(' OR ', $like);
$query = "SELECT * FROM `table` WHERE $like ORDER BY id DESC LIMIT 1";
You need to break up the keywords and do a separate LIKE comparison for each one
SELECT * FROM mytable
WHERE keywords LIKE '%spaghetti%' OR keywords LIKE '%macaroni%'
-- ...
You could do something like this
<?php
$keywords = 'spaghetti,macaroni,hamburger';
$query = "SELECT * FROM mytable WHERE 1=1";
$keywords = explode(',', $keywords);
if (count($keywords)) {
$query .= sprintf(
" AND (%s)",
implode(' OR ', array_map(function($word){
return "keywords LIKE '%{$word}%'";
}, $keywords))
);
}
echo $query;
// SELECT * FROM mytable WHERE 1=1 AND (keywords LIKE '%spaghetti%' OR keywords LIKE '%macaroni%' OR keywords LIKE '%hamburger%')
As you can see from the comments, there are better ways to handle this, but I'm assuming that this rudimentary question is looking for a simple answer.
I'm not doing any input sanitization, or adding any other fail safes. Building queries like this is not very reliable and you should continue to learn about PHP/MySQL to avoid common pitfalls in the future. However, I believe more advanced approaches are a bit out of your reach at the moment. If someone else would like to provide better techniques that they think the OP can grasp, by all means, go for it :)
Storing multiple values as a CSV inside a column is a common SQL anti-pattern. It's hard to use and even harder to optimize:
How to fix the Comma Separated List of Doom
You have to use LIKE with separeted works, you can try this:
$keywords = 'spaghetti,macaroni,hamburger';
$arrayWords = explode(',', $keywords);
$like = '';
foreach($arrayWords as $word)
$like .= empty($like) ? " keywords LIKE '%$word%' " : " OR keywords LIKE '%$word%' ";
mysql_query("SELECT * FROM ---- WHERE $like ORDER BY id DESC LIMIT 1");
mysql_query("SELECT * FROM ---- WHERE keywords LIKE '%". implode("%' OR keywords LIKE '%", explode(",", $keywords)) ."%' ORDER BY id DESC LIMIT 1");
Try this, it may be faster than using multiple or clauses
Select * from mytable
where keywords REGEXP 'spaghetti|macaroni|hamburger'
I need to implement the following functionality:
I have a name field which contains both name and surname. This is stored in a database and is in the order 'surname name'.
I am implementing a script which searches through these records. Currently, I managed to check if a string contains a space, if it contains a space it means it is a name and not an ID Card Number for instance. Here is the code:
$query = "John Doe";
$checkIfSpaceExists = strpos($query, " ");
if ($checkIfSpaceExists == "")
{
//No Space therefore it is not a name
}
else
{
//Contains space
$queryExploded = explode(" ", $query);
foreach ($queryExploded as $q)
{
//Here I need the functionality so that if someone entered John Doe
//2 different strings are saved, which are
//$string1 = John Doe
//$string2 = Doe Johns
//If the name consists of 3 parts, strings for every combination is saved
}
Then I will insert these strings in an SQL statement with the LIKE attribute and there will be a LIKE for both JOHN DOE and DOE JOHN. Hence, if the user can either enter John Doe or Doe John in order to find the result.
Any suggestions on how to do this?
Many thanks
chris
Ok, from the start - be sure to read the manual carefully. strpos doesn't do exactly what you think it's doing. Here's how you should check for a space:
if (strpos($query, ' ') === false) // the triple-equals is important!
After that, it's simply a matter of permutations and combinations. Here's another answer on Stack Overflow which shows you how to do it: algorithm that will take number or words and find all possible combinations
What about using these exploded 3 strings in separate AND-combined LIKE-constraints?
Something like
"... WHERE name LIKE '%$name[0]%' AND name LIKE '%$name[1]%' AND name LIKE '%$name[2]%'"
You could build this String in a foreach loop.
echo preg_replace('/^(\w+) (\w+)$/', '$2 $1', "John Doe");
Doe John
I guess you cannot split this field into name and surname? I suggest creating new table, tags in database. Tags will be any word - might be surname, may be name, may be ID number... Then make a connection record_tags with record_id and tag_id. Then the query will look like
SELECT record.* FROM record
INNER JOIN record_tags rt1 ON rt1.record_id = record.id
INNER JOIN tag t1 ON t1.id = rt1.tag_id
INNER JOIN record_tags rt2 ON rt2.record_id = record.id
INNER JOIN tag t2 ON t2.id = rt2.tag_id
WHERE
t1.name = "John"
AND t2.name = "Doe"
This will be better approach to searching, as you can then use any amount of words/tags. I think the SQL can be even easier. the multiple-like approach is I think much slower, especially as your database grows.
With some looping and string manipulation, I managed to implement this script.
Here is what I did:
I checked if the query contains a space using strpos. If it contains a space, then it means its a name with both name and surname so I enter a loop in order to output one string with 'name surname' and the other string with 'surname name'
Here is the code:
$like = ""; //This is the LIKE sql command.
foreach ($selectedFields as $field)
{
$checkIfSpaceExists = strpos($query," ");
if ($checkIfSpaceExists != "")
{
$query1 = $query; //No space, so query1 is equal ta original query
$queryExploded = explode(" ", $query);
for ($i=0; $i<count($queryExploded); $i++) //First loop (name surname)
{
$tmp1 = $tmp1 . " " . $queryExploded[$i];
}
for ($i=count($queryExploded); $i>=0; $i--) //Second loop (surname name)
{
$tmp2 = $tmp2 . " " . $queryExploded[$i];
}
$query2 = $tmp2;
$query2 = trim($query2);
$like = $like . $field . " LIKE '%" . $query1 . "%' or " . $field . " LIKE '%" . $query2 . "%' or ";
I hope this helps someone else in need :)