A "Search" mysql query issue - mysql

I have this query where I can search the TABLE_GLOBAL_PRODUCTS
$catglobal_sql = "
select p.*,
case when
p.specials_new_products_price >= 0.0000
and p.expires_date > Now()
and p.status != 0
then p.specials_new_products_price
else p.products_price
end price
from ".TABLE_GLOBAL_PRODUCTS." p
INNER JOIN ".TABLE_STORES." s ON s.blog_id = p.blog_id
where
MATCH (p.products_name,p.products_description) AGAINST ('%".$search_key."%')
OR p.products_name like '%".$search_key."%'
order by p.products_date_added DESC, p.products_name";
The issue here is that, when I search with phrases like Cotton Shirts it displays correct results. However, when I only input a single word like Cotton it displays no results instead of displaying the same as when you input a phrase like Cotton Shirts.

Use * instead of % as a wildcard when using MATCH ... AGAINST ...
So the match part of your code should look like:
...
MATCH (p.products_name,p.products_description) AGAINST ('*".$search_key."*')
...
In MATCH the wildcards are slightly different
To match zero or more characters use
In MATCH '*'
In LIKE '%'
To match any single character use
In MATCH '?'
In LIKE '_'

Related

MySQL ORDER BY CASE + operator

I am trying to do a search that would be sorted by relevance.
Let's say the search term contains 3 words: A, B and C. What I am trying to do is to check if the search term is present in the SELECT result and if yes that would increase its rank.
ORDER BY CASE
(
WHEN search_word_A_is_present THEN +1
WHEN search_word_B_is_present THEN +1
WHEN search_word_C_is_present THEN +1
ELSE 0
END
)
DESC
While there is no syntax error and the search runs and sorts by something (that seems different from what I want) but I am not sure what is being added up if anything. How would I go about seeing what the final rank (sum) is at the end for each result? Is this the correct way to do it?
Since in MySQL boolean conditions result in 1 and 0, you can simply add those up
ORDER BY search_word_A_is_present + search_word_B_is_present + search_word_C_is_present
DESC
A more practical example:
ORDER BY col1 = 1 + col2 = 'A' + col3 = 44 DESC

Matching key words in mysql database

m trying to match key word but not exactly, something relevant, i use sql LIKE with wilcard %% something like '%mens, shoe%', will match every data with either men, mens shoe or even both, but the problem is if a user uses an apostrophe sign like this '%men\'s, shoe%', this will only match record having men's shoe or having men's with apostrophe and will not match men or mens is there a way i can make input with apostrophe sign match records without apostrophe sign thanks for any help
I'm editing my question and adding the SQL statement how it looks like
SELECT SQL_CALC_FOUND_ROWS p.product_id,p.title,p.price,p.unit_sold,p.slug,p.discount,p.free_shipping,free_return,
p.profile_img,p.store_name,p.item_number,
(
(-- Title score
if (title LIKE '%men\'s, shoe%',6,0) + if (title LIKE '%men\'s%',5,0) + if (title LIKE '%shoe%',5,0)
)+
(-- description
if (description LIKE '%men\'s, shoe%',5,0) + if (description LIKE '%men\'s%',4,0) + if (description LIKE '%shoe%',4,0)
)+
(-- item number
if (item_number = 'men\'s, shoe',4,0) + if (item_number = 'men\'s',3,0) + if (item_number = 'shoe',3,0)
)+
(-- category id
if (category_id = 'men\'s, shoe',4,0) + if (category_id = 'men\'s',3,0) + if (category_id = 'shoe',3,0)
)
) as relevance
FROM products p
WHERE p.is_active = '1'
HAVING relevance > 0
ORDER BY relevance DESC LIMIT 2,2
Your condition should look like:
someColumn LIKE '%men''s%' or someColumn LIKE '%men\''s%' or someColumn LIKE '%shoe%'
where someColumn is column name you are interested in. It will match any occurences like: men's, men\'s, etc.
Remove apostrophes before comparing:
(-- Title score
(case when replace(title, '''', '') LIKE '%mens, shoe%' then 16
when replace(title, '''', '') LIKE '%men%' then 5
when replace(title, '''', '') LIKE '%shoe%' then 5
else 0
end) +
. . .
That said, you might want to look into full text search. It might be a simpler way to accomplish what you want.

finding a number in space separated list with REGEXP

I am writing a SQL query to select row, where a field with space separated numbers contains a single number, in this example the 1.
Example fields:
"1 2 3 4 5 11 12 21" - match, contains number one
"2 3 4 6 11 101" - no match, does not contain number one
The best query so far is:
$sql = "SELECT * from " . $table . " WHERE brands REGEXP '[/^1$/]' ORDER BY name ASC;";
Problem is that this REGEXP also finds 11 a match
I read many suggestions on other post, for instance [\d]{1}, but the result always is the same.
Is it possible to accomplish what I want, and how?
You don't need regex: You can use LIKE if you add a space to the front and back of the column:
SELECT * from $table
WHERE CONCAT(' ', brands, ' ') LIKE '% 1 %'
ORDER BY name
Try:
WHERE brands REGEXP '[[:<:]]1[[:>:]]'
[[:<:]] and [[:>:]] match word boundaries before and after a word.
Why not FIND_IN_SET() + REPLACE() ?
SELECT
*
FROM
`table`
WHERE
FIND_IN_SET(1, REPLACE(`brands`, ' ', ','))
ORDER BY
`name` ASC;

MySQL order by "best match"

I have a table that contains words and an input field to search that table using a live search. Currently, I use the following query to search the table:
SELECT word FROM words WHERE word LIKE '%searchstring%' ORDER BY word ASC
Is there a way to order the results so that the ones where the string is found at the beginning of the word come first and those where the string appears later in the word come last?
An example: searching for 'hab' currently returns
a lphabet
h abit
r ehab
but I'd like it this way:
hab it (first because 'hab' is the beginning)
alp hab et (second because 'hab' is in the middle of the word)
re hab (last because 'hab' is at the end of the word)
or at least this way:
hab it (first because 'hab' is the beginning)
re hab (second because 'hab' starts at the third letter)
alp hab et (last because 'hab' starts latest, at the fourth letter)
Would be great if anyone could help me out with this!
To do it the first way (starts word, in the middle of the word, ends word), try something like this:
SELECT word
FROM words
WHERE word LIKE '%searchstring%'
ORDER BY
CASE
WHEN word LIKE 'searchstring%' THEN 1
WHEN word LIKE '%searchstring' THEN 3
ELSE 2
END
To do it the second way (position of the matched string), use the LOCATE function:
SELECT word
FROM words
WHERE word LIKE '%searchstring%'
ORDER BY LOCATE('searchstring', word)
You may also want a tie-breaker in case, for example, more than one word starts with hab. To do that, I'd suggest:
SELECT word
FROM words
WHERE word LIKE '%searchstring%'
ORDER BY <whatever>, word
In the case of multiple words starting with hab, the words starting with hab will be grouped together and sorted alphabetically.
Try this way:
SELECT word
FROM words
WHERE word LIKE '%searchstring%'
ORDER BY CASE WHEN word = 'searchstring' THEN 0
WHEN word LIKE 'searchstring%' THEN 1
WHEN word LIKE '%searchstring%' THEN 2
WHEN word LIKE '%searchstring' THEN 3
ELSE 4
END, word ASC
You could use the INSTR function to return the starting position of the search string within the word,
ORDER BY INSTR(word,searchstring)
To make the resultset more deterministic when the searchstring appears in the same position in two different words, add a second expression to the ORDER BY:
ORDER BY INSTR(word,searchstring), word
(For example, searchstring hab appears in second position of both chablis and shabby)
In your case it would be:
ORDER BY INSTR(word, '%searchstring%')
INSTR search in the word column for '%searchstring%' and return it's position, if no match then it will be 0 and cause result go down in order.
You also can add DESC for changing direction, eg:
ORDER BY INSTR(word, '%searchstring%') DESC
I got the best match for multiple columns using this query:
SELECT
id,
wordA,
wordB,
(CASE
WHEN wordA = 'keywordA%' THEN 0
WHEN wordA LIKE 'keywordA%' THEN 1
WHEN wordA LIKE '%keywordA%' THEN 2
WHEN wordA LIKE '%keywordA' THEN 3
ELSE 4
END) AS 'wordA_keywordA_score',
(CASE
WHEN wordA = 'keywordB%' THEN 0
WHEN wordA LIKE 'keywordB%' THEN 1
WHEN wordA LIKE '%keywordB%' THEN 2
WHEN wordA LIKE '%keywordB' THEN 3
ELSE 4
END) AS 'wordA_keywordB_score',
(CASE
WHEN wordB = 'keywordA%' THEN 0
WHEN wordB LIKE 'keywordA%' THEN 1
WHEN wordB LIKE '%keywordA%' THEN 2
WHEN wordB LIKE '%keywordA' THEN 3
ELSE 4
END) AS 'wordB_keywordA_score',
(CASE
WHEN wordB = 'keywordB%' THEN 0
WHEN wordB LIKE 'keywordB%' THEN 1
WHEN wordB LIKE '%keywordB%' THEN 2
WHEN wordB LIKE '%keywordB' THEN 3
ELSE 4
END) AS 'wordB_keywordB_score'
FROM
words
WHERE
wordA like '%keywordA%'
OR
wordA like '%keywordB%'
OR
wordB like '%keywordA%'
OR
wordB like '%keywordB%'
ORDER BY
(
wordA_keywordA_score +
wordA_keywordB_score +
wordB_keywordA_score +
wordB_keywordB_score
)
ASC;

MYSQL ORDER BY with an array of values in WHERE clause

I am working on a part of a search script where the submitted search string is parsed and each significant term placed into an array. The array is then looped through in the WHERE clause to search several columns in the MYSQL database. Here's a sample SQL code:
$sql = "SELECT title, question, tag1, tag2, tag3, tag4, tag5 FROM question WHERE ";
while(list($key,$val)=each($split_stemmed)){
if($val<>" " and strlen($val) > 0){
$sql .=
"(title LIKE '$val%'
OR question LIKE '$val%'
OR tag1 LIKE '$val%'
OR tag2 LIKE '$val%'
OR tag3 LIKE '$val%'
OR tag4 LIKE '$val%'
OR tag5 LIKE '$val%') OR";
}
}
$sql=substr($sql,0,(strLen($sql)-3));
$sql .= "GROUP BY q_id ORDER BY
((title LIKE '$val%') +
(question LIKE '$val%') +
(tag1 LIKE '$val%') +
(tag2 LIKE '$val') +
(tag3 LIKE '$val%') +
(tag4 LIKE '$val%') +
(tag5 LIKE '$val%')) desc, title asc";
The problem I am having is getting the ORDER BY to work correctly. The ORDER BY's purpose is to arrange the results of the query from the order of most hits on the search string to the least. My assumption is that I somehow need to loop through the array again in the ORDER BY clause, but am not sure of how to do this or if I am even correct in that assumption. Any helpers out there?
I know I can probably do this simpler using mysql full text search but the tables being used are InnoDB, so I dont think that is an option as of yet.
A couple of issues to consider...
1) a NULL value in any of the columns tag1, tag2, tag3... in the ORDER BY expression is going to return a NULL for the entire expression. Consider:
SELECT 0 + NULL + 1 + 0
If all of those columns are defined as NOT NULL, then this isn't an issue. But more generally, you'd want to insulate from NULL values...
ORDER BY ( IFNULL(title LIKE '$val%'),0) +
IFNULL(question LIKE '$val%'),0) +
IFNULL(tag1 LIKE '$val%'),0) +
IFNULL(tag2 LIKE '$val' ),0) +
-or alternatively-
ORDER BY ( IFNULL(title ,'') LIKE '$val%') +
IFNULL(question,'') LIKE '$val%') +
IFNULL(tag1 ,'') LIKE '$val%') +
IFNULL(tag2 ,'') LIKE '$val' ) +
)
(Seems like you're missing a % on tag2 based on the pattern, but it's entirely possible that omission is intentional
2) It's not at all clear why you need a GROUP BY q_id in your query.
I'd recommend you test by taking the expression in the ORDER BY clause, and copy it to your SELECT list, and run the query to see the values that it's returning.
In MySQL, if you add that expression to the SELECT list and give it an alias, you can reference the alias on the ORDER BY.
SELECT expr AS match_count, ...
FROM
ORDER BY match_count DESC
The functionality you're looking for -- searching multiple columns for search terms and ordering by relevance -- is exactly what full-text search was designed for. If at all possible, you should use this as it will reduce your headaches in the long run.
If you absolutely must implement this way, then it's necessary to move the LIKE statements into the SELECT clause so that you can sum them. Like this:
SUM (CASE WHEN title LIKE '$val%' THEN 1 ELSE 0 END
+ CASE WHEN question LIKE '$val%' THEN 1 ELSE 0 END
+ CASE WHEN tag1 LIKE '$val%' THEN 1 ELSE 0 END
... etc
) AS relevance
Then to select only the matches, you could use HAVING relevance > 0. And to order, you would simply use ORDER BY relevance DESC (since the relevance column as above will give you a count of the number of matching columns).