Increase performance query when use CONCAT in where clause - mysql

My query is:
SELECT *
FROM user u
LEFT JOIN user_detail ud ON u.id = ud.user_id
WHERE CONCAT(ud.first_name,' ',ud.last_name) LIKE 'John Smith%'
I created two index on first_name and last_name column, but I know they didn't work when use CONCAT in where clause because CONCAT scans full table.
Suppose I can't change table to create a new column as full_name. Is there anyway to increase performance in this case?
Noted:
Because the input can be any text such as first name, last name or full name so I use CONCAT and the full query is :
SELECT *
FROM user u
LEFT JOIN user_detail ud ON u.id = ud.user_id
WHERE (CONCAT(ud.first_name,' ',ud.last_name) LIKE 'search%') OR (CONCAT(ud.last_name,' ',ud.first_name) LIKE 'search%')

If your intention is to always use concat, then just create function index rather then indexing to separate columns
CREATE INDEX idx1 ON user_Details (CONCAT(ud.first_name,' ',ud.last_name));
otherwise I suggest you splitting rather than concatenation
WHERE ud.first_name = '?' AND ud.last_name LIKE '?%'

You tried if it is faster with :
ud.first_name = 'John' AND d.last_name like 'Smith%'
The comparison on the first string already may abort and equals is faster than like.
see also: Use '=' or LIKE to compare strings in SQL?

Related

MAX(Date) is giving empty result

I have a table with exchange rate like below
And I am using the maxofdate to pick all these values based on currency code. But the query is giving blank.
Select USDAMOUNT * dbo.EXCHANGERATEAMT
from dbo.Amount_monthly
Left Join dbo.EXCHANGERATE on dbo.Amount_monthly.Currencycode=dbo.EXCHANGERATE.fromcurrencycode
WHERE ValidToDateTime = (Select MAX(ValidToDateTime) from dbo.EXCHANGERATE)
AND dbo.EXCHANGERATE.EXCHANGERATETYPECODE = 'DAY'
Using this statement
CONVERT(DATE,ValidToDateTime) = CONVERT(DATE,GETDATE()-1)
instead of subquery is giving me expected result.
Can someone correct this.
thanks in advance.
If I understand correctly, you need two things. First, the condition for the max() needs to match the condition in the outer query. Second, if you really want a left join, then conditions on the second table need to go in the on clause.
The resulting query looks like:
Select . . .
from dbo.Amount_monthly am Left Join
dbo.EXCHANGERATE er
on am.Currencycode = er.fromcurrencycode and
er.ValidToDateTime = (Select max(er2.ValidToDateTime)
from dbo.EXCHANGERATE er2
where er2.EXCHANGERATETYPECODE = 'DAY'
) and
er.EXCHANGERATETYPECODE = 'DAY';
I would write this using window functions, but that is a separate issue.
Try removing WHERE clause for ValidToDateTime and include it in the JOIN as AND condition
SELECT USDAMOUNT * dbo.EXCHANGERATEAMT
FROM dbo.Amount_monthly
LEFT JOIN dbo.EXCHANGERATE
ON dbo.Amount_monthly.Currencycode = dbo.EXCHANGERATE.fromcurrencycode
AND ValidToDateTime = (SELECT MAX(ValidToDateTime) --remove WHERE clause
FROM dbo.EXCHANGERATE)
AND dbo.EXCHANGERATE.EXCHANGERATETYPECODE = 'DAY';
I cleaned up your query a bit: as the other folks mentioned you needed to close the parentheses around the MAX(Date) sub-query, and if you reference a LEFT JOINed table in the WHERE clause, it behaves like an INNER JOIN, so I changed to in INNER. You also had "dbo" sprinkled in as a field prefix, but that (the namespace) only prefixes a database, not a field. I added the IS NOT NULL check just to avoid SQL giving the "null values were eliminated" SQL warning. I used the aliases "am" for the first table and "er" for the 2nd, which makes it more readable:
SELECT am.USDAMOUNT * er.EXCHANGERATEAMT
FROM dbo.Amount_monthly am
JOIN dbo.EXCHANGERATE er
ON am.Currencycode = er.fromcurrencycode
WHERE er.ValidToDateTime = (SELECT MAX(ValidToDateTime) FROM dbo.EXCHANGERATE WHERE ValidToDateTime IS NOT NULL)
AND er.EXCHANGERATETYPECODE = 'DAY'
If you're paranoid like I am, you might also want to make sure the exchange rate is not zero to avoid a divide-by-zero error.

Alias a column name on a left join

Let's say I have two tables, and both their primary identifiers use the name 'id'. If I want to perform a join with these two tables, how would I alias the id of the table that I want to join with the former table?
For example:
SELECT * FROM `sites_indexed` LEFT JOIN `individual_data` ON `sites_indexed`.`id` = `individual_data`.`site_id` WHERE `url` LIKE :url
Now, site_id is supposed to link up with sites_indexed.id. The actual id which represents the row for individual_data however has the same title as sites_indexed.
Personally, I like to just use the name id for everything, as it keeps things consistent. When scripting server-side however, it can make things confusing.
e.g.
$var = $result['id'];
Given the aforementioned query, wouldn't this confuse the interpreter?
Anyway, how is this accomplished?
Instead of selecting all fields with "SELECT *" you should explicitly name each field you need, aliasing them with AS as required. For example:
SELECT si.field1 as si_field1,
si.field2 as si_field2,
ind_data.field1 as ind_data_field1
FROM sites_indexed as si
LEFT JOIN individual_data as ind_data
ON si.id = ind_data.site_id
WHERE `url` LIKE :url
And then you can reference the aliased names in your result set.
This thread is old and i found because i had the same problem. Now i have a better solution.
The answer given by Paul McNett and antun forces you to list all fields but in some cases this is impossible (too much fields to list), so you can keep the * and alias only the fields you want (typically the fields that have the same name and will override each other).
Here's how :
SELECT *, t.myfield as myNewName
FROM table t ... continue your query
you can add as much aliases as you want by adding comas.
Using this expression you will get results with columns id (from table sites_indexed) and id2 (alias for column id from table individual_data)
SELECT t1 . *, t2 . * FROM sites_indexed t1
LEFT JOIN (select id as id2, other_field1, other_field2 FROM individual_data) t2 ON t1.id = t2.site_id WHERE your_statement
The problem is that you're using the * wildcard. If you explicitly list the column names in your query, you can give them aliases:
SELECT `sites_indexed`.`id` AS `sites_indexed_id`,
`individual_data`.`id` AS `individual_data_id`
FROM `sites_indexed`
LEFT JOIN `individual_data` ON `sites_indexed`.`id` = `individual_data`.`site_id`
WHERE `url` LIKE :url
Then you can reference them via the alias:
$var = $result['sites_indexed_id'];
$var_b = $result['individual_data_id'];

sql left join for two tables using a CONCAT string as the argument

I need to get a title from table 2, table 2 has title and id column.
Table 1 has some data and three of these columns concatenated together makeup the id that can be found in table 1.
I used CONCAT_WS() function and gave this column an alias name and need to use the Alias for the on argument(At least this is what I understood I needed to do)
I thought this could be a simple left join, yet it is not working for me.
This is my query
SELECT
table_openers.mail,
table_openers.f_name,
table_openers.l_name,
table_openers.Quality,
CONCAT_WS('-',
table_openers.esp,
table_openers.acc,
table_openers.group) as 't1aid',
table_groups.aid,
table_groups.group_name
FROM
lance_mailstats.table_openers
LEFT JOIN
lance_mailstats.table_groups ON table_groups.aid = t1aid;
I get results for mail, f_name, l_name, Quality and t1aid, but the aid and group_name columns of the second table return null.
I feel like you can't use an alias in the ON clause.
Try doing
LEFT JOIN
lance_mailstats.table_groups ON table_groups.aid = CONCAT_WS('-',
table_openers.esp,
table_openers.acc,
table_openers.group);
"You can use the alias in GROUP BY, ORDER BY, or HAVING clauses to refer to the column" (from dev.mysql.com/doc/refman/5.0/en/problems-with-alias.html).
And "The conditional_expr used with ON is any conditional expression of the form that can be used in a WHERE clause" (from dev.mysql.com/doc/refman/5.1/en/join.html).
So as a logical inference you're not allowed to use aliases in ON clauses.
try to use a subquery..
it goes like this.........
ex.
SELECT
tbl1.mail, tbl1.f_name, tbl1.l_name,tbl1.Quality, tbl1.t1aid,table_groups.aid,
table_groups.group_name
FROM
(SELECT
table_openers.mail,
table_openers.f_name,
table_openers.l_name,
table_openers.Quality,
CONCAT_WS('-',
table_openers.esp,
table_openers.acc,
table_openers.group) as 't1aid',
FROM
lance_mailstats.table_openers )tbl1
LEFT JOIN
lance_mailstats.table_groups ON table_groups.aid = tbl1.t1aid;

(mysql) how to add wildcard on where condition when joining with another table?

i have one query that need some changes, and i don't get any clue to do this :
this is my query :
select * from user_data a
left join user_group b
on (a.role like b.role)
actually role value in userdata is (varchar)'staff'
and role value in group is (varchar)'staff;security;finance'
so i don't get result what i expected ..
i imagine the query should be similar to this :
select * from user_data a
left join user_group b
on (b.role like a.role+";%") // using wildcard
and i still don't know the right query using wildcard to this case
any one can help?
You can use CONCAT:
select * from user_data a
left join user_group b
on (b.role like CONCAT(a.role,";%")) // using wildcard
Note - does b.role only have to match a.role at the beginning? what if it was security;staff;finance? You could do CONCAT('%',a.role,'%').
You could do CONCAT('%','a.role','%') to handle matching a.role at any position, but only if you can be sure that you won't have nested roles.
For example: if b.role is staff and a.role is finance;gardenstaff;security, then this row will be returned from the query even though the role is gardenstaff and not staff.
As an alternative, you can use RLIKE instead of LIKE. This is basically a regular-expressions verson of LIKE.
In particular, the regex [[:<:]]staff[[:>:]] will match the whole word staff. The [[:<:]] and [[:>:]] stand for word boundaries, which stop you from matching the staff in gardenstaff.
So, your query could be:
select * from user_data a
left join user_group b
on (b.role RLIKE CONCAT('[[:<:]]',a.role,'[[:>:]]'))
And this would work for b.role being anywhere in the semicolon-separated a.role.

indexes in mysql SELECT AS or using Views

I'm in over my head with a big mysql query (mysql 5.0), and i'm hoping somebody here can help.
Earlier I asked how to get distinct values from a joined query
mysql count only for distinct values in joined query
The response I got worked (using a subquery with join as)
select *
from media m
inner join
( select uid
from users_tbl
limit 0,30) map
on map.uid = m.uid
inner join users_tbl u
on u.uid = m.uid
unfortunately, my query has grown more unruly, and though I have it running, joining into a derived table is taking too long because there is no indexes available to the derived query.
my query now looks like this
SELECT mdate.bid, mdate.fid, mdate.date, mdate.time, mdate.title, mdate.name,
mdate.address, mdate.rank, mdate.city, mdate.state, mdate.lat, mdate.`long`,
ext.link,
ext.source, ext.pre, meta, mdate.img
FROM ext
RIGHT OUTER JOIN (
SELECT media.bid,
media.date, media.time, media.title, users.name, users.img, users.rank, media.address,
media.city, media.state, media.lat, media.`long`,
GROUP_CONCAT(tags.tagname SEPARATOR ' | ') AS meta
FROM media
JOIN users ON media.bid = users.bid
LEFT JOIN tags ON users.bid=tags.bid
WHERE `long` BETWEEN -122.52224684058 AND -121.79760915942
AND lat BETWEEN 37.07500915942 AND 37.79964684058
AND date = '2009-02-23'
GROUP BY media.bid, media.date
ORDER BY media.date, users.rank DESC
LIMIT 0, 30
) mdate ON (mdate.bid = ext.bid AND mdate.date = ext.date)
phew!
SO, as you can see, if I understand my problem correctly, i have two derivative tables without indexes (and i don't deny that I may have screwed up the Join statements somehow, but I kept messing with different types, is this ended up giving me the result I wanted).
What's the best way to create a query similar to this which will allow me to take advantage of the indexes?
Dare I say, I actually have one more table to add into the mix at a later date.
Currently, my query is taking .8 seconds to complete, but I'm sure if I could take advantage of the indexes, this could be significantly faster.
First, check for indices on ext(bid, date), users(bid) and tags(bid), you should really have them.
It seems, though, that it's LONG and LAT that cause you most problems. You should try keeping your LONG and LAT as a (coordinate POINT), create a SPATIAL INDEX on this column and query like that:
WHERE MBRContains(#MySquare, coordinate)
If you can't change your schema for some reason, you can try creating additional indices that include date as a first field:
CREATE INDEX ix_date_long ON media (date, `long`)
CREATE INDEX ix_date_lat ON media (date, lat)
These indices will be more efficient for you query, as you use exact search on date combined with a ranged search on axes.
Starting fresh:
Question - why are you grouping by both media.bid and media.date? Can a bid have records for more than one date?
Here's a simpler version to try:
SELECT
mdate.bid,
mdate.fid,
mdate.date,
mdate.time,
mdate.title,
mdate.name,
mdate.address,
mdate.rank,
mdate.city,
mdate.state,
mdate.lat,
mdate.`long`,
ext.link,
ext.source,
ext.pre,
meta,
mdate.img,
( SELECT GROUP_CONCAT(tags.tagname SEPARATOR ' | ')
FROM tags
WHERE ext.bid = tags.bid
ORDER BY tags.bid GROUP BY tags.bid
) AS meta
FROM
ext
LEFT JOIN
media ON ext.bid = media.bid AND ext.date = media.date
JOIN
users ON ext.bid = users.bid
WHERE
`long` BETWEEN -122.52224684058 AND -121.79760915942
AND lat BETWEEN 37.07500915942 AND 37.79964684058
AND ext.date = '2009-02-23'
AND users.userid IN
(
SELECT userid FROM users ORDER BY rank DESC LIMIT 30
)
ORDER BY
media.date,
users.rank DESC
LIMIT 0, 30
You might want to compare your perforamnces against using a temp table for each selection, and joining those tables together.
create table #whatever
create table #whatever2
insert into #whatever select...
insert into #whatever2 select...
select from #whatever join #whatever 2
....
drop table #whatever
drop table #whatever2
If your system has enough memory to hold full tables this might work out much faster. It depends on how big your database is.