multiple columns index vs index for each col ? mysql - mysql

I have this tables :
business table :
bussId | nameEn | nameHe | nameAr | status | favor | cityId | categoryId
category table :
categoryId | keywords
favorite table :
userId | bussId
rating table :
userId | bussId | rating
I am running this query which filter businesses with cityId and search (business.nameEn , business.nameAr , business.nameHe , categories.keywords) then order by favor and status and nameEn.
SELECT DISTINCT bussID ,businessName, bussStatus,favor, ratingCount , ratingSum
FROM
(
SELECT DISTINCT business.bussID , business.nameEn as businessName , bussStatus,favor,
(SELECT COUNT(rating.bussId) FROM `rating` WHERE rating.bussId = business.bussID) as ratingCount ,
(SELECT SUM(rating.rating) FROM `rating` WHERE rating.bussId = business.bussID) as ratingSum
FROM business LEFT JOIN favourites ON (favourites.bussID = business.bussID AND favourites.userID = '30000')
INNER JOIN `categories` ON (`categories`.`categoryId` = `business`.`subCategoryId` )
WHERE (bussiness.cityID = 11)
AND (
( REPLACE( REPLACE(REPLACE(LOWER(`bussiness`.`nameEn`),'أ','ا'),'أ','ا') ,'ة','ه') LIKE '%test%' )
OR( REPLACE( REPLACE(REPLACE(LOWER(`bussiness`.`nameHe`),'أ','ا'),'أ','ا') ,'ة','ه') LIKE '%test%' )
OR( REPLACE( REPLACE(REPLACE(LOWER(`bussiness`.`nameAr`),'أ','ا'),'أ','ا') ,'ة','ه') LIKE '%test%' )
OR( REPLACE( REPLACE(REPLACE(LOWER(`categories2`.`keyWords`),'أ','ا'),'أ','ا') ,'ة','ه') LIKE '%test%' )
)
AND
(bussiness.bussStatus IN(1,3,5,7)
)
GROUP BY bussiness.bussID )results
ORDER BY
businessName LIKE '%test%' DESC,
FIELD(bussStatus,'1','5','3'),
FIELD(favor,'1','2','3'),
businessName LIMIT 0,10
I am using replace to search case insensitive for أ ا and ة ه letters (before adding the test word I also replace this letters) .
my question :
I want to know How should I declare the indexes properly !
should I declare multiple columns index :
ALTER TABLE `bussiness`
ADD INDEX `index9` (`nameHe` ASC, `nameEn` ASC, `nameAr` ASC, `favor` ASC, `bussStatus` ASC);
or one columns index for each col !
should I create another col allNamesLanguages which contain nameAr,nameEn,nameHe then I just search this col ?

You have two problems with this part of the query that make standard indexes unusable:
( REPLACE( REPLACE(REPLACE(LOWER(`bussiness`.`nameEn`),'أ','ا'),'أ','ا') ,'ة','ه') LIKE '%test%' )
OR( REPLACE( REPLACE(REPLACE(LOWER(`bussiness`.`nameHe`),'أ','ا'),'أ','ا') ,'ة','ه') LIKE '%test%' )
OR( REPLACE( REPLACE(REPLACE(LOWER(`bussiness`.`nameAr`),'أ','ا'),'أ','ا') ,'ة','ه') LIKE '%test%' )
OR( REPLACE( REPLACE(REPLACE(LOWER(`categories2`.`keyWords`),'أ','ا'),'أ','ا') ,'ة','ه') LIKE '%test%' )
The first is the use of functions on the columns. The second is the use of like with a pattern that starts with a wildcard ('%').
For the functionality that you seem to want, you are going to need to use full text indexes and triggers and additional columns.
Here is my recommendation:
Add (at least) four addition columns that will be used for searching names. Something like business.nameEn_search and so on.
Add insert -- and perhaps update and delete triggers that will do the replacement of the special characters when you insert new values. That is, the massive replace( . . . ) logic goes in the trigger.
Add a full text index for the four columns.
Use match . . . against for your queries.
More information about full text functionality is in the documentation.

Functions basically render indexes useless. Therefore, columns that are used in WHERE clauses like UPPER(name) and else, can be indexed by so-called "function based indexes". They are a feature of Oracle, but as far as I know not in MySQL.
How to use a function-based index on a column that contains NULLs in Oracle 10+?
http://www.mysqlab.net/knowledge/kb/detail/topic/oracle/id/5041
Function-based indexes have their preconditions, though. The function used must be deterministic. So if you would like to index a calculation like "age", it won't work because "age" defined as "now minus then" basically grows each time you select.
My advice is to create more columns and to store the information to be mined there, as prepared as possible.
If you use LIKE "%blabla%", any index will be useless because of the variable text start length. So try to organize the additional columns so that you can avoid LIKE "%... or avoid LIKE at all. According to my experience, adding more columns to indexes won't be a performance stopper for many columns. So just try what happens if you add 4 columns and one combined index for them.
As I understand, you win the game as soon as you can write:
... WHERE nameEn_idx = 'test' AND/OR nameEr_idx = 'test' ...

Related

How can I use an IF or Case function to summarize a GROUP_CONCAT column? AND then apply it to the original data table?

I am quite the novice at MYSQL and would appreciate any pointers - the goal here would be to automate a categorical field using GROUP_CONCAT in a certain way, and then summarize certain patterns in the GROUP_CONCAT field in a new_column. Furthermore, is it possible to add the new_column to the original table in one query? Below is what I've tried and errors to an unknown column "Codes" if this assists:
SELECT
`ID`,
`Code`,
GROUP_CONCAT(DISTINCT `Code` ORDER BY `Code` ASC SEPARATOR ", ") AS `Codes`,
IF(`Codes` LIKE '123%', 'Description1',
IF(`Codes` = '123, R321', 'Description2',
"Logic Needed"))
FROM Table1
GROUP BY `ID`
Instead of nested if statements, I would like to have a CASE statement as a substitute. Reason being is that I already have around 1000 lines of logical already written as "If [column] = "?" Then "?" else if" etc. I feel like using CASE would be an easier transition with the logic. Maybe something like:
SELECT
`ID`,
`Code`,
GROUP_CONCAT(DISTINCT `Code` ORDER BY `Code` ASC SEPARATOR ", ") AS `Codes`,
CASE
WHEN `Codes` LIKE '123%' THEN 'Description1'
WHEN `Codes` = '123, R321' THEN 'Description2'
ELSE "Logic Needed"
END
FROM Table1
GROUP BY `ID`
Table Example:
ID,Code
1,R321
1,123
2,1234
3,1231
4,123
4,R321
Completed Table:
ID,Codes,New_Column
1,"123, R321",Description2
2,1234,Description1
3,1231,Description1
4,"123, R321",Description2
How then can I add back the summarized data to the original table?
Final Table:
ID,Code,New_Column
1,R321,Description2
1,123,Description2
2,1234,Description1
3,1231,Description1
4,123,Description2
4,R321,Description2
Thanks.
You can't refer to a column alias in the same query. You need to do the GROUP_CONCAT() in a subquery, then the main query can refer to Codes to summarize it.
It also doesn't make sense to select Code, since there isn't a single Code value in the group.
SELECT ID, Codes,
CASE
WHEN `Codes` = '123, R321' THEN 'Description2'
WHEN `Codes` LIKE '123%' THEN 'Description1'
ELSE "Logic Needed"
END AS New_Column
FROM (
SELECT
`ID`,
GROUP_CONCAT(DISTINCT `Code` ORDER BY `Code` ASC SEPARATOR ", ") AS `Codes`
FROM Table1
GROUP BY ID
) AS x
As mentioned in a comment, the WHEN clauses are tested in order, so you need to put the more specific cases first. You might want to use FIND_IN_SET() rather than LIKE, since 123% will match 1234, not just 123, something

Need regex expression in mysql select query

I am writing the mysql query where I need to compare data entered by user with the sku column in database tab
Query is like
SELECT *
FROM `is_product_info`
WHERE REPLACE( '-', '', sku ) LIKE '%ETI2006%'
so that if sku in database contains "-" in sku, I am replacing all hyphens with "" before comparing.
so whether sju no contains ETI2006 or ETI-2006, it will come in output
I think you may just like this
SELECT * FROM is_product_info WHERE REPLACE( sku , '-', '' ) LIKE '%ETI2006%'
I didn't try on a running MySQL but this should work:
SELECT *
FROM `is_product_info`
WHERE sku REGEXP REPLACE('ETI-2006','-','-?');
I made the mistake in replace syntax, it works with the below one:
SELECT * FROM is_product_info WHERE REPLACE( sku , '-', '' ) LIKE '%ETI2006%'
Using replace() on every row is what's slowing it down.
All of these approaches should be faster - see which one works best for you:
Option 1 - use LIKE twice:
WHERE sku LIKE '%ETI-2006%'
OR sku LIKE '%ETI2006%'
Option 2 - Use RLIKE once:
WHERE sku RLIKE 'ETI-?2006'

UNION three different SELECTs in MYSQL

I have this MYSQL table named people with the columns: id|firstname|lastname|birthdate|phone.
I am quite new to MYSQL and I'm trying to UNION several SELECTs so that the result will look in the following way:
only the first 20 results must be shown
the first SELECT criteria is by the combination firstname+lastname+birthdate: WHERE (birthdate="1980-01-01") AND ((firstname LIKE "%john%") AND (lastname LIKE "%smith%"))
the second SELECT criteria is by the combination firstname+lastname: WHERE (firstname LIKE "%john%") AND (lastname LIKE "%smith%")
the third SELECT criteria is by phone: WHERE phone="0123456"
the output result must in fact have 3 columns: order|id|type; where "order" and "type" are alias columns
the exported ids must be unique: if the same id results from all the 3 SELECTs (or from more than one SELECT), then it must appear only once in the output table
the column "order" must have the value 1 for the results of the first SELECT, 2 for the 2nd SELECT and 3 for the last SELECT
if the same id value results from more than one SELECT, then its row must have the highest order value; where the highest order posible is 1, from the first SELECT
the alias column "type" must work like this: if an id results from the 1st SELECT, it's type value is "~firstname+lastname+birthdate~"; if an id results from the 2nd SELECT, it's type value is "~firstname+lastname~"; and finally if an id results from the 3rd SELECT, it's type value is "~phone~"
if the same id value results from more than one SELECT, the value on the "type" alias column must be a concatention between the SELECTs where that id was found (for example, if the same id resulted in all 3 SELECT queries then the value on the "type" column would be "~firstname+lastname+birthdate~~firstname+lastname~~phone~")
Is it possible to achieve such an output?
Here's something using CASE statements. I think you'll get into a mess with union statements, because of your order type statement. Hopefully I've understood what you're after - it's much easier if you post sample data! Anyway, even if this doesn't do exactly what you want, you get the idea....
[EDIT] I don't think you need the distinct, but I don't think it hurts, either...
SELECT DISTINCT
CASE WHEN birthdate='1980-01-01'
AND firstname LIKE '%john%' AND lastname LIKE '%smith%'
THEN 1
WHEN firstname LIKE '%john%' AND lastname LIKE '%smith%'
THEN 2
WHEN phone='0123456'
THEN 3
END AS outputorder, -- avoid confusion of using an SQL keyword as a column name,
id,
CONCAT(
CASE
WHEN birthdate='1980-01-01'
AND firstname LIKE '%john%' AND lastname LIKE '%smith%'
THEN CONCAT('~',firstname,'+',lastname,'+','~')
END ,
CASE WHEN firstname LIKE '%john%' AND lastname LIKE '%smith%'
THEN CONCAT('~',firstname,'+',lastname,'~')
END ,
CASE WHEN phone='0123456'
THEN CONCAT('~',phone,'~')
END
) -- end the concat
AS outputtype
FROM
mytable
WHERE
( birthdate='1980-01-01'
AND firstname LIKE '%john%' AND lastname LIKE '%smith%')
OR
(firstname LIKE '%john%' AND lastname LIKE '%smith%')
OR
phone='0123456'
ORDER by 1,2 LIMIT 20
In the end I did something like this and it worked just fine:
SELECT MIN(`ord`) AS `order` , `id` , GROUP_CONCAT(`spec`) as `type` FROM (
SELECT "1" AS `ord` , `id` , "~firstname+lastname+birthdate~" AS `spec` FROM `people` WHERE (`birthdate` = "1986-04-02") AND (`lastname` LIKE "%smith%") AND (`firstname` LIKE "%john%")
UNION
SELECT "2" AS `ord` , `id` , "~firstname+lastname~" AS `spec` FROM `people` WHERE (`lastname` LIKE "%smith%") AND (`firstname` LIKE "%john%")
UNION
SELECT "3" AS `ord` , `id` , "~phone~" AS `spec` FROM `people` WHERE (`phone`="0123456")
) final
GROUP BY final.`id`
ORDER BY `order` ASC
LIMIT 20
Thanks to mlinth for the alternative though...

Mysql sortation on multiple LIKE's

There are a lot of topics on sortation (like: Order Results By Occurrence) but these are all for one value.
I have a search field that people use with keywords; simply said the queries generated look like:
1 word:
SELECT *
FROM `meta`
WHERE (`keywords` LIKE '%bike%')
2 words:
SELECT *
FROM `meta`
WHERE (
`keywords` LIKE '%bike%'
OR `keywords` LIKE '%yellow%'
)
etc...
What I would like to do is sort the result on the most found keywords. How would I do this for an unknown amount of keywords LIKE's
Here is the general way to sort by the number of keyword matches in MySQL (using like):
SELECT *
FROM `meta`
ORDER BY ((`keywords` LIKE '%bike%') +
(`keywords` LIKE '%yellow%') +
. . .
) desc;
If you want to handle a flexible number of keywords, then you should use an appropriate relational data structure. Storing keywords in a single field (probably comma-separated) is not the best approach. You should have a separate table with one row per keyword.
EDIT:
To add in the number of keywords found, the expression can be put in the select statement:
SELECT m.*,
((`keywords` LIKE '%bike%') +
(`keywords` LIKE '%yellow%') +
. . .
) as NumKeywordsFound
FROM `meta` m
ORDER BY NumKeywordsFound desc;
You can also add a having clause to specify that at least one is found:
SELECT m.*,
((`keywords` LIKE '%bike%') +
(`keywords` LIKE '%yellow%') +
. . .
) as NumKeywordsFound
FROM `meta` m
HAVING NumKeywordsFound > 1
ORDER BY NumKeywordsFound desc;
If you want to find the number of times a keyword is found in each expression:
select m.*,
length(replace(keywords, 'bike', 'bike1')) - length(keywords) as NumBikes,
length(replace(keywords, 'yellow', 'yellow1')) - length(keywords) as NumYellows
FROM `meta` m

Search all columns of a table using a single where condition with single keyword in mysql

I have a table which consists of 64 different fields. i am going to search with a single keyword in it, Results should match the keyword from any field. Give some suggestions.
SELECT * FROM `some_table`
WHERE
CONCAT_WS('|',`column1`,`column2`,`column3`,`column4`,`column64`) # single condition, many columns
LIKE '%VT%'
Voila.
The '|' separator, by the way, is to prevent you finding coincidental matches where, e.g., column1 ends in 'V' and column2 starts with 'T', which would give you a false positive in a search for "VT".
I'm not sure if the above method is faster than the OR method (I would guess they're the same speed) , but it definitely involves less typing if you're writing the query by hand.
you can use the where with multiple condition with OR
like
where
name = 'expected'
OR rate ='expected'
OR country ='expected'
I can't see a way around your query being simple but long:
SET #term = "Somesearch";
SELECT id, title FROM sometable WHERE
col1 LIKE '%#term%' OR
col2 LIKE '%#term%' OR
col3 LIKE '%#term%' ...;
Instead of using a MySQL variable, you can just use a language-specific variable but for the sake of examples, I thought I'd stick with MySQL itself.
The "..." is where you'd place the other 61 columns/fields.
Another possibility would be to use FOR XML to get all columns to print to a single field... like this:
SELECT c.*
FROM (
SELECT a.*
,( SELECT *
FROM table_to_search b
WHERE a.KeyField = b.KeyField
FOR XML RAW) AS `Full_Text_Record`
FROM table_to_search a) c
WHERE c.`Full_Text_Record` LIKE '%Search_string%'
Might take a while to run if it is a particularly large table, but it should brute force you to find out if that string exists in any given table.
If you can translate this SQL Server syntax to MySQL
WHERE
name = #keyword OR
country = #keyword OR
department = #keyword OR
#keyword IS NULL -- match all when search text is empty
Simplest solution would be to use multiple ORs.
select * from TAB where col1 like "%VAR%" OR col2 like "%VAR%" OR......col64 like "%VAR%";
You can use like or = as per the requirement, but it will require to change your query every time you add a new column.
As an alternative, you can take SQLDump for that table and then search that file.
With some Googling,
See if this project is useful - http://code.google.com/p/anywhereindb/. Searches all the fields and praised by many.
Try to use the information from information_schema table. Look for all the columns in the table. Now, try to form your query using this information.
You could write one query that will generate a query for every column in your table.
In the example below the schema ("owner") is 'DEV_USER'
The table with your 64 fields is called 'CUSTOMER_INFO'
The criteria in the search is any column with a value of 'VT' in it:
select 'SELECT ' || COLUMN_NAME || ' FROM CUSTOMER_INFO
WHERE ' || COLUMN_NAME || q'# LIKE '%VT%';#'
FROM ALL_TAB_COLS
WHERE OWNER = 'DEV_USER'
AND TABLE_NAME = 'CUSTOMER_INFO';
This one query will generate a query for each field for you.
The results of running the above would be;
SELECT ADDRESS_1 FROM CUSTOMER_INFO
WHERE ADDRESS_1 LIKE '%VT%';
SELECT ADDRESS_2 FROM CUSTOMER_INFO
WHERE ADDRESS_2 LIKE '%VT%';
SELECT CITY FROM CUSTOMER_ADDRESSES_QASB
WHERE CITY LIKE '%VT%';
SELECT STATE_PROVINCE FROM CUSTOMER_INFO
WHERE STATE_PROVINCE LIKE '%VT%';
SELECT ZIP_POSTAL_CODE FROM CUSTOMER_INFO
WHERE ZIP_POSTAL_CODE LIKE '%VT%';
WHERE LATITUDE LIKE '%VT%';
... and so on for each column in the table
Then you just paste those queries that were generated from your first query into another tab and run them.
Hope that helps. :-)
You can use dynamic SQL to generate and execute a query that searches all the columns.
DELIMITER $$
CREATE PROCEDURE searchAllCols(inDB VARCHAR(64), inTable VARCHAR(64), search VARCHAR(32))
BEGIN
SET #matches = (
SELECT GROUP_CONCAT(CONCAT('`', COLUMN_NAME, '` LIKE "%', search, '%"') SEPARATOR ' OR ')
FROM INFORMATION_SCHEMA.COLUMNS
WHERE table_name = inTable and table_schema = inDB);
SET #query = CONCAT('SELECT * FROM `', inDB, '`.`', inTable, '` WHERE ', #matches);
PREPARE stmt FROM #query;
EXECUTE stmt;
END
$$
DELIMITER ;
CALL searchAllCols('table_to_search', 'searchString');