I have a search input box where a user can enter one or more word and the php script should return all the row where all those words are include in any column.
Suppose a table like this:
|car_id|make|model|year|plate|
+----------------------------+
| 1 |Audi|A4 |2010|AAAAA|
| 2 |Audi|A4 |2012|AAAAB|
| 3 |Audi|Q5 |2010|AAAAC|
+----------------------------+
If a user enter "Aud", he get the 3 rows. If it enter "2010 Audi", he get row 1 and 3, and if he enter "Aud q5 2010", he get only row 3.
What I have done.
If the input as only 1 term, i can build a query like
SELECT * FROM car WHERE make LIKE '%term%' OR model like '%term%' OR year like '%term%'
If the user enter 2 terms, I have to create this query instead (even don't consider the plate column)
SELECT * FROM car WHERE
(make LIKE '%term%' AND model LIKE '%term2%')
OR (make LIKE '%term2%' AND model LIKE '%term%')
OR (make LIKE '%term%' AND year LIKE '%term2%')
OR (make LIKE '%term1%' AND year LIKE '%term%')
OR (model LIKE '%term%' AND year LIKE '%term%')
OR (model LIKE '%term2%' AND year LIKE '%term1%')
If a user enter 3 terms, the query get exponentialy more complexe.
I read about MATCH keyword but some column are not text, like year in this exemple.
What is the correct way to do this kind of search? Should I change database schema? Concat all searchable columns into a string and do some regex,
Thank you.
Create an extra column, all. Have it contain the CONCAT_WS(' ', ...) of all the columns you need to search on.
Create a FULLTEXT(all) index on that column.
Construct the query with just one clause:
WHERE MATCH(all) AGAINST("+Audi +q5 +2010" IN BOOLEAN MODE)
Issues:
FULLTEXT has a minimum word length; either change that (alas, it is a global setting) or deal with short "words" differently. If, say, the min word len is 3, then remove th '+' from before 'q5' in the AGAINST string. Otherwise, InnoDB will find no rows.
Similarly deal with "stop words". Or remove the stop list.
Deal with hyphenated, etc, words in some way.
It may help to do ORDER BY MATCH ... LIMIT 20 to (1) give some preference to the oddball cases, and (2) limit the output when the user does not specify enough.
Or you might want to play fair and do ORDER BY RAND() LIMIT 20.
If you are using 5.7 or MariaDB, a "generated" column may be useful.
Related
I am doing queries to a database that stores one, very long, word in each of the records. The are no spaces, no special characters, only ualpha. Right now I am using the LIKE keyword to look for records that contain all of the words from my input. I would like the script to show me results with the highest relevance first.
So for example, I type in "be me if". This script should first return records that contain all of those words, like this:
"XSFBENSUMEPOPKLIF"
Then it would show those that contain only two of those words and then finally those that contain only one of those words.
I tried using "ORDER BY CASE" and giving points to the best matches, like this:
- ORDER BY CASE
- WHEN `code` LIKE '%word1%' AND `code` LIKE '%word2%' THEN 1
- WHEN `code` LIKE '%word1%' THEN 2
- ELSE 3 END;
It worked but when given more than three words the server crashed because it had to browse through almost 4 million records and simply couldn't handle it.
I tried fulltext search after that but it doesn't allow me to search for partial words, only prefixes.
Is there any way to achieve what I am looking for?
I would suggest adding the values. MySQL treats booleans as integers, so you can do:
order by ((code like '%word1%') +
(code like '%word2%') +
(code like '%word3%')
) desc
EDIT:
Of course, this should be after a where clause:
where code like '%word1%' or code like '%word2%' or code like '%word3%'
I have a table on MySQL, called Items with 2 columns: Id and Name. I want to show my items, filtering by its name first character. To do this, now I'm using this query:
Select Id,Name from Items WHERE Name LIKE 'a%'
I have 2 questions:
1) It is this the best method to achieve that?
2) To create the filter view, I want to know which characters have at least one item name starting with it. For example, I don't have any item name starting with "X". How could I know which ones have with a single query?
Thanks
If you have an Index on Name then your query is fine.
To get all characters having Names
Select substr(Name, 1, 1) as starting_character
from Items
group by starting_character
order by starting_character
You could run a large OR query like....
select * from Items where nobre like "A%" or nombre like "B%" or nombre like "C%" //etc etc
But if speed and table size is an issue, I would create another column, and just enter the letter value as an INT into that column. Kinda like an index.
So when you insert any new values, just code up a function to return the letter value of the first letter, and insert that into the new column.
Now you can query the column for any letter, based on that integer.
Hope that came out clearly, Im not very good at explaining things sometimes.....
This will give you all the first characters - only once (hence the DISTINCT)
SELECT DISTINCT SUBSTR(Name,1,1) FROM Items ORDER BY SUBSTR(Name,1,1)
Then you can use this to create your filters.
I am having a table with a column that has few ids that were put into database with multi select. Column for example contains: 1,4,5,7,9. Is it possible to check if this column contains for example number 5 or not in it through MySQL query ?.
I need to select all the people that have number 5 or some other listed in that field and print them through php.
http://dev.mysql.com/doc/refman/5.6/en/string-functions.html#function_find-in-set
SELECT ...
WHERE FIND_IN_SET(5, list_column)
But understand that this search is bound to be very slow. It cannot use an index, and it will cause a full table-scan (reading every row in the table). As the table grows, the query will become unusably slow.
Please read my answer to Is storing a delimited list in a database column really that bad?
You can use #MikeChristensen's answer to be more standard. Another trick with standard SQL is this:
select * from TableName
where ',' || ids || ',' LIKE '%,5,%'
(in standard SQL, || is the string concatenation operator, but in MySQL, you have to SET SQL_MODE=PIPES_AS_CONCAT or SET SQL_MODE=ANSI to get that behavior.)
Another MySQL-specific solution is to use a special word-boundary regular expression, which will match either the comma punctuation or beginning/end of string:
select * from TableName
where ids RLIKE '[[:<:]]5[[:>:]]'
None of these solutions scale well; they all cause table-scans. Sorry I understand you cannot change the database design, but if your project next requires to make the query faster, you can tell them it's not possible without redesigning the table.
Perhaps:
select * from TableName
where ids = '5' -- only 5
or ids like '5,%' -- begins with 5
or ids like '%,5' -- ends with 5
or ids like '%,5,%' -- 5 in the middle somewhere
It probably won't be very fast on large amounts of data. I'd suggest normalizing these multi-selection values into a new table, where each selection is a single row with a link to TableName.
select * from your_table where concat(',',target_column,',') like '%,5,%'
you can write the sql query like this, for example you are looking for the number 5
select * from your_table_name where ids='5'
if you want to check the result with php just tell me i will write it for you :)
I am building a comics website and have implemented a search. The search originally used the image title... This worked fine:
if (strtolower($input)==strtolower(substr($row['title'],0,strlen($input)))) {
But now I feel that many people won't remember the title. So I've added a column to the database called "keywords", varchar [55]... and my test entry is the string "five, second, rule, food, rules". I figured I could replace $row['title'] with $row['keywords'] and the search would still work.
It worked if I start searching from the beginning of the string, like I enter "five, seco..." But not if I start from the middle of the string, like "second", or "rule", etc.
Any ideas on how I can make that work?
Thanks!
Doing it in a comic's column will only bring tears as it breaks normalization. What you should do is make a keyword table that has one word per entry and then a pivot table that matches keywords to comics. Then you can just do a join to find all the comics that match.
It's more work to setup, but more flexible in the long run.
EDIT:
Keyword table:
id keyword
1 x-men
2 action
3 mystery
4 batman
etc.
comic_keyword table
comic_id keyword_id
45 3
678 1
678 2
77 3
77 4
etc.
The second table (the pivot table) matches the ids of the comic to the ids of the keywords they're associated with. That's how many-to-many relationships should be modeled in most cases.
The straightforward solution would be to use stripos instead:
if(stripos($input, $row['title']) !== false) {
// row matches
}
However, this isn't really a good solution. It would be much better to offload the filtering to your database, so non-matching rows don't have to make the trip to your front end at all. If you keep keywords as a comma-separated field then LIKE or REGEXP would be a good choice of tool; if you normalized your database schema so that the 1-to-many relationship of comics to keywords is modeled with a separate comic_keywords table then matching would be even easier.
For example, assuming that comic has an id and comic_keywords has a comic_id and a keyword, you could select matching comics with
SELECT DISTINCT(comic_id) FROM comic_keywords WHERE keyword = 'blah'
You can split the column on the fly:
if (in_array(strtolower($input), explode(', ', $row['title']))) {
/* found it */
}
Looks like you need Sphinx or (less likely, but easier to set up) MySQLs full-text search.
I have an application with images stored in multiple categories, currently being stored by category ID in a column as a space separated list (eg. 1 5 23 2).
I have a query from a search filter, which is currently an array of IDs, (eg. 1 5).
Ideally, I'd find a solution using something like WHERE IN that would see if any of my array values exist in the stored column, although I don't see an easy solution.
At the moment I have to query all the images, bring them into PHP and check there, using "array_intersect". I see this as being a problem if I have 100,000s of images in the future to pull and then check.
Can anyone think of an elegant solution? The application is still in development, so I could arguably change the structure of my tables.
I think adding a map table would probably be best here which maps the image_id with the category_id.
refactor your database tables!!!
use sth like this:
table_image
id int
name text,
content text,
...
and a second table for the categories:
table_category
id int,
image_id int,
category int
this way, you can store categories in a separate table using foreign keys. now, you can do simple sql queries like
SELECT table_image.id FROM table_image, table_category WHERE table_image.id = table_category.image_id and table_category.category = $cat_arr[0] OR table_category.category = $cat_arr[1] ...
H Hatfield has the best answer. If you really must use a single column (which I do not recommend) you could store the categories as a comma separated list instead of spaces. You can then use the MySql function find_in_set, as such:
WHERE FIND_IN_SET('3', categoryListColumnName) > 0 OR FIND_IN_SET('15', categoryListColumnName) > 0
Using your current database design you could use an IN query:
WHERE categoryListColumnName LIKE '% 3 %' OR categoryListColumnName LIKE '% 15 %'
and add more OR's for every category you want to find. When using this query you have to make sure your list separated by spaces ends and starts with a space, otherwise it won't work.
Let me just reiterate, that these methods will work, but they are not recommended.