PHP+MySQL: searching using wildcards - php

So, I have the following rows in the DB:
1 | /users/
2 | /users/admin/
3 | /users/admin/*
4 | /users/admin/mike/
5 | /users/admin/steve/docs/
The input URL is /users/admin/steve/, and the goal is to find the URL match from the DB.
I want to return #3 as the correct row, since the wildcard "*" specifies that anything can go in place of the asterisk. What would be the most efficient method for doing this?
Here's my initial thoughts, but I'm sure they could be improved upon:
Make a query to see if there's an exact URL match
If no matches, then retrieve all rows with "*" as the last character, in reverse order (so the more specific URLs take precedence)
For each row, if it (minus the "*") matches the input URL, then return it
If nothing is found, then we're SOL

Here's how I'd do it:
SELECT * FROM mytable AS m
WHERE <input-url> = m.urlpattern
OR <input-url> REGEXP REPLACE(m.urlpattern, '*', '.*');
The REPLACE() is to change the globbing-style wildcard into an equivalent regular-expression wildcard.

If I understand this correctly, something like this should work:
SELECT COALESCE(
(SELECT whatever FROM your query to see if there is an exact url),
(SELECT next approximation),
'SOL'
)

Related

how to get from mysql using in same sequence using laravel?

I have this record in my database
| 29 | Mac 190:193:194:195:196:197:198:199:200 |
the last column name is
path_address
if I have a string like this
190:193:194:195
I want MySQL to select the path_address that has same sequence
so I used this command
$query_data = $module_model::where('path_address','LIKE',"$structure%")->limit(7)->get();
where structure is :190:193:194:195 and it is working fine, but what is happening is that if I have string like this
190:193:194:195:197
it is also return mac as a result of this query ,
how can I set my query to bring the string that have same sequence only and stop if it has broken sequence , so the accepted sequence will be like this
190
190:193
190:193:194
190:193:194:195
190:193:194:195:196 and so on
but theses sequence must be rejected
190:194
190:193:194:196
190:193:196
190:193:194:195:196:198 etc .
For example, you may use
WHERE path_address LIKE '$structure%'
OR '$structure' LIKE CONCAT(path_address, '%')
First condition will return rows where path_address is not shorter than $structure, second - where it is not longer respectively.
fiddle

mySQL more precision using LIKE function

I'm aware of the LIKE function in SQL, but I need to do something slightly more complex.
In my table people I have a text field called banned which stores a string of all banned names seperated with a #. So let's banned = Roger#Bobjob#Billy
Say I want to check if the name Bob appears
SELECT * FROM people WHERE banned LIKE '%$Bob%'
This would presumably find results because of the Bobjob in the string.
Is there any way I can make it so it only finds full names within the # delimiters?
Forget about using index in either of these.... but since you're using %var% i am assuming you expected that already.
We could concat a # at start and end of banned so that every name is surrounded by #'s then use the # in the name search. (Expects no name to have # in it.... and expects banned to not start nor end with a # (and if they do it really won't matter to the below))
Where concat('#', banned,'#') like '%#Bob#%'
Use find in set by converting the # to a , and if the result is > 1 then it was found.
find_in_set('Bob',replace(banned,','))>1
If you know the list is always composed of 2 or 3 names (as stated in your comment), the most straightforward way is to check for all 3 possible cases:
SELECT *
FROM people
WHERE list LIKE 'bob#%' /* name in first position */
OR list LIKE '%#bob#%' /* name in second position */
OR list LIKE '%#bob' /* name in third, final position */
You should use concat for build a proper like condition with vars and #
SELECT * FROM people WHERE banned LIKE concat('%#%', '$Bob','%#%')
This isn't an ideal data structure, but leaving that aside for the moment:
If you can change the delimiter to a comma, mysql has a built-in function for that: FIND_IN_SET
SELECT * FROM people WHERE FIND_IN_SET('Bob', list);
Otherwise, you can do a regular expression match.
SELECT * FROM people WHERE list REGEXP '(^|#)Bob($|#)';
Neither of these is necessarily very performant, so I wouldn't try it on large data sets.
It's probably not the best db architecture but you might use regular expressions for you purposes like so
SELECT * FROM people WHERE banned RLIKE '(^|#)Bob($|#)'

Perform partial search on MySQL table when exact match may be available

I am running the following SQL statement from a PHP script:
SELECT PHONE, COALESCE(PREFERREDNAME, POPULARNAME) FROM distilled_contacts WHERE PHONE LIKE :phone LIMIT 6
As obvious, the statement returns the first 6 matches against the table in question. The value I'm binding to the :phone variable is goes something like this:
$search = '%'.$search.'%';
Where, $search could be any string of numerals. The wildcard characters ensure that a search on, say 918, would return every record where the PHONE field contains 918:
9180078961
9879189872
0098976918
918
...
My problem is what happens if there does exist an entry with the value that matches the search string exactly, in this case 918 (the 4th item in the list above). Since there's a LIMIT 6, only the first 6 entries would be retrieved which may or may not contain the one with the exact match. Is there a way to ensure the results always contain the record with the exact match, on top of the resulting list, should one be available?
You could use an order by to ensure the exact match is always on top:
ORDER BY CASE WHEN PHONE = :phone THEN 1 ELSE 2 END
Using $search = ''.$search.'%' will show result, that matches the starting value.

SQL Query for closest match at the beginning of a string

Am currently using Mysql and PHP.
Looking for a query that will take a number and find the closet match for the begining of a set of digits, for example I have the number 019235678910, 026725678910, 026825678910 and my table looks like this.
Table - Destintation
Name Number
Watford 01923
Oxford 026
Romford 026
Crawford 0267
Topford 02672
So when I pass 019235678910 the result would be Watford, 026725678910 would be Topford and 026825678910 would be Oxford and Romford.
I'm also not sure if MYSQL can do this directly or would need to work in conjunction with PHP?
Here one way for getting all of them:
select d.*
from Destination d join
(select length(Number) as maxlen, number
from destination d
where YOURVALUE like concat(Number, '%')
order by maxlen desc
limit 1
) dsum
on d.Number = dsum.Number
Because you are looking for initial sequences, there is only one maximum match on the numbers (hence the limit 1 works).
By the way, the field called number is clearly a character field. Personally, I think it bad practice to call a character field "number" -- something called cognitive dissonance.
SELECT Name, Number
FROM Destintation
WHERE LEFT('026725678910', LENGTH(Number)) = Number
or perhaps
WHERE '026725678910' LIKE CONCAT(Number, '%')

PHP mysql search queries

I'm trying to create a search engine for an inventory based site. The issue is that I have information inside bbtags (like in [b]test[/b] sentence, the test should be valued at 3, whereas sentence should be valued at 1).
Here is an example of an index:
My test sentence, my my (has a SKU of TST-DFS)
The Database:
|Product| word |relevancy|
| 1 | my | 3 |
| 1 | test | 1 |
| 1 |sentence| 1 |
| 1 | TST-DFS| 10 |
But how would I match TST-DFS if the user typed in TST DFS? I would like that SKU to have a relevancy of say 8, instead of the full 10..
I have heard that the FULL TEXT search feature in MySQL would help, but I can't seem to find a good way to do it. I would like to avoid things like UNIONS, and to keep the query as optimized as possible.
Any help with coming up with a good system for this would be great.
Thanks,
Max
But how would I match TST-DFS if the user typed in TST DFS?
I would like that SKU to have a relevancy of say 8, instead of the full 10..
If I got the question right, the answer is actually easy.
Well, if you forge your query a little before sending it to mysql.
Ok, let's say we have $query and it contains TST-DFS.
Are we gonna focus on word spans?
I suppose we should, as most search engines do, so:
$ok=preg_match_all('#\w+#',$query,$m);
Now if that pattern matched... $m[0] contains the list of words in $query.
This can be fine-tuned to your SKU, but matching against full words in a AND fashion is pretty much what the user presumes is happening. (as it happens over google and yahoo)
Then we need to cook a $expr expression that will be injected into our final query.
if(!$ok) { // the search string is non-alphanumeric
$expr="false";
} else { // the search contains words that are no in $m[0]
$expr='';
foreach($m[0] as $word) {
if($expr)
$expr.=" AND "; // put an AND inbetween "LIKE" subexpressions
$s_word=addslashes($word); // I put a s_ to remind me the variable
// is safe to include in a SQL statement, that's me
$expr.="word LIKE '%$s_word%'";
}
}
Now $expr should look like "words LIKE '%TST%' AND words LIKE '%DFS%'"
With that value, we can build the final query:
$s_expr="($expr)";
$s_query=addslashes($query);
$s_fullquery=
"SELECT (Product,word,if((word LIKE '$s_query'),relevancy,relevancy-2) as relevancy) ".
"FROM some_index ".
"WHERE word LIKE '$s_query' OR $s_expr";
Which shall read, for "TST-DFS":
SELECT (Product,word,if((word LIKE 'TST-DFS'),relevancy,relevancy-2) as relevancy)
FROM some_index
WHERE word LIKE 'TST-DFS' OR (word LIKE '%TST%' AND word LIKE '%DFS%')
As you can see, in the first SELECT line, if the match is partial, mysql will return relevancy-2
In the third one, the WHERE clause, if the full match fails, $s_expr, the partial match query we cooked in advance, is tried instead.
I like to lower case everything and strip out special characters (like in a phone number or credit card I take everything out on both sides that isn't a number)
Rather than try to create your own FTS solution, you could try to fit the MySQL FTS engine to your requirements. What I've seen done is create a new table to store your FTS data. Create a column for each different piece of data that you want to have a different relevance. For your sku field you could store the raw sku, with spaces, underscores, hyphens and any other special character intact. Then store a stripped down version with all these things removed. You may also want to store a version with leading zeros removed, as people often leave things like that out. You can store all these variations in the same column. Store your product name in another column, and the product description in another column. Create a separate index on each column. Then when you do your search, you can search each column individually, and multiply the rank of the results based on how important you think that column is. So you could multiply sku results by 10, title by 5 and leave description results as is. You may have to do a little experimentation to get the results you want, but it may ultimately be simpler than creating your own index.
Create a keywords table. Something along the lines of:
integer keywordId (autoincrement) | varchar keyword | int pointValue
Assign all possible keywords, skus, etc, into this table. Create another table, a post-keywords bridge, (assuming postId is the id you've assigned in your original table) along the lines of:
integer keywordId | integer postId
Once you have this, you can easily add keywords to each post as it is interested. To calculate total point value for a given post, a query such as the following should do the trick:
SELECT sum(pointValue) FROM keywordPostsBridge kpb
JOIN keywords k ON k.keywordId = kpb.keywordId
WHERE kpb.postId = YOUR_INTENDED_POST
I think the solution is quite straightforward unless I missed something.
Basically run two search, one is exact match, the other is like match or regex match.
Join two resultsets together, like match left join exact match. Then for example:
final_relevancy = (IFNULL(like_relevancy, 0) + IFNULL(exact_relevancy, 0) * 3) / 4
I didn't try this myself though. Just an idea.
I would add a column that is stripped of all special character's, misspellings, and then upcased (or create a function that compares on text that has been stripped and upcased). That way your relevancy will be consistent.
/*
q and q1 - you table
this query takes too much resources,
make from it update-query ( scheduled task or call it on_save if you develop new system )
*/
SELECT
CASE
WHEN word NOT REGEXP "^[a-zA-Z]+$"
/*many replace with junk characters
or create custom function
or if you have full db access install his https://launchpad.net/mysql-udf-regexp
*/
THEN REPLACE(REPLACE( word, '-', ' ' ), '#', ' ')
ELSE word
END word ,
CASE
WHEN word NOT REGEXP "^[a-zA-Z]+$"
THEN 8
ELSE relevancy
END relevancy
FROM ( SELECT 'my' word,
3 relevancy
UNION
SELECT 'test' word,
1 relevancy
UNION
SELECT 'sentence' word,
1 relevancy
UNION
SELECT 'TST-DFS' word,
10 relevancy
)
q
UNION
SELECT *
FROM ( SELECT 'my' word,
3 relevancy
UNION
SELECT 'test' word,
1 relevancy
UNION
SELECT 'sentence' word,
1 relevancy
UNION
SELECT 'TST-DFS' word,
10 relevancy
)
q1
it is a page coading where query result shows
**i can not use functions by use them work are more easier**
<html>
<head>
</head>
<body>
<?php
//author S_A_KHAN
//date 10/02/2013
$dbcoonect=mysql_connect("127.0.0.1","root");
if (!$dbcoonect)
{
die ('unable to connect'.mysqli_error());
}
else
{
echo "connection successfully <br>";
}
$data_base=mysql_select_db("connect",$dbcoonect);
if ($data_base==FALSE){
die ('unable to connect'.mysqli_error($dbcoonect));
}
else
{
echo "connection successfully done<br>";
***$SQLString = "select * from user where id= " . $_GET["search"] . "";
$QueryResult=mysql_query($SQLString,$dbcoonect);***
echo "<table width='100%' border='1'>\n";
echo "<tr><th bgcolor=gray>Id</th><th bgcolor=gray>Name</th></tr>\n";
while (($Row = mysql_fetch_row($QueryResult)) !== FALSE) {
echo "<tr><td bgcolor=tan>{$Row[0]}</td>";
echo "<td bgcolor=tan>{$Row[1]}</td></tr>";
}
}
?>
</body>
</html>

Categories