MySQL Query Optimization, Primary Key Index is not used? - mysql

We have lot of question like this, but each query is unique so this question arise. I have following query
Select * from tblretailerusers where (company_id=169 or company_id in (select id from tblretailercompany where multi_ret_id='169')) and ( id in (Select contact_person_1 from tbllocations where status=1 and (retailer_comp_id=169 or retailer_comp_id in (select id from tblretailercompany where multi_ret_id='169'))) OR id in(Select contact_person_2 from tbllocations where status=1 and (retailer_comp_id=169 or retailer_comp_id in (select id from tblretailercompany where multi_ret_id='169'))) ) and (last_login is not null )
It has three tables involve, Retailer, their Location and their User. Standard User information. Each retailer can have child Retailer, so Retailer table has Parent Retailer ID. Currently each table has about 6K records, and all table has Primary key as Auto increment and as I know they are indexed as well. in User Table Email field is indexed.
Now, this query take < 1 sec which is fine to have, but now client want to find user whose' email ids start with specific letter, like a and b. As soon as I add that to query, it starts taking about 50-60 seconds. I create Index on Email field which is not unique, and new query looks like
Select * from tblretailerusers where (company_id=169 or company_id in (select id from tblretailercompany where multi_ret_id='169')) and ( id in (Select contact_person_1 from tbllocations where status=1 and (retailer_comp_id=169 or retailer_comp_id in (select id from tblretailercompany where multi_ret_id='169'))) OR id in(Select contact_person_2 from tbllocations where status=1 and (retailer_comp_id=169 or retailer_comp_id in (select id from tblretailercompany where multi_ret_id='169'))) ) and (last_login is not null ) and email REGEXP '^A|^B'
I try to use Explain, in both version of query and the interesting fact I notice is that in Primary table row it do not show any value for possible_key where as we are using Primary key id search in User table as well as I have Index on Email field too. Here is Explain of query:
I try to recreate index, current I have index that use ID, CompanyID and Email in one index other than primary key in user table. I also create Index on Company Tables, but nothing speeds it up. User Table is MyISAM.
My another question is, how can I skip Sub Query for Child Company Search, as you can see it was used thrice in above query.
EDIT: The reason I am using the REGEXP in my query is that when I try the Like 'A%' it was even slow with that option.
Edit I just test with last_login is null instead of last_login is not null, and results take less than 5 seconds. Aren't Null or Not Null similar?

Instead of using the RegEx:
and email REGEXP '^A|^B'
...try a simple LIKE:
and (email like 'A%' or email like 'B%')
Regular Expressions are a bit of heavy for this rather small comparison. Like will probably be far faster. Also, I wouldn't expect the optimiser to try to decode what the regexp is trying to do. With a Like, however, it knows and will probably use the index you've set up.

Related

SQL select latest message of every conversation received AND sent

This question SQL select only rows with max value on a column doesn't solve my problem although it has been marked as duplicate.
It assumes my columns from_id and to_id are primary keys, when they don't have such constraint (see code provided bellow). If they were primary keys, I couldn't store my messages in the same table. As a result the SQL query of this answer prints all duplicates multiple times, which is not what I want. Please see expected behaviour bellow.
Expected behaviour : I need to select the latest message from all conversations, regardless of whether the user is only sender, recipient, or both. Each conversation/thread should only be displayed once.
Example : when querying this table, my SQL statement should only output msg3 and msg4, ignoring all the previous messages John and Alice exchanged.
Here is the closest query I could write. Problem is this query only selects conversations where user received a message. I'm stuck adding conversations where user is only sender (he didn't get any reply) to the selection.
SELECT * FROM messages where `to_id` = '1' GROUP BY `from_id` ORDER BY `send_date` ASC
Here are users and messages tables:
CREATE TABLE users (
id INT(11) AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(128) NOT NULL
);
CREATE TABLE messages (
id INT(11) AUTO_INCREMENT PRIMARY KEY,
to_id INT(11) NOT NULL, //recipient id to match to current user id
from_id INT(11) NOT NULL, //sender id to match to current user id
send_date DATETIME DEFAULT CURRENT_TIMESTAMP,
content TEXT
);
Question: How can I do this using a single SQL query ? Or should I change my data structure using three tables instead of one ?
I would first get the ids. You can do this using least() and greatest():
select least(m.to_id, m.from_id) as id1,
greatest(m.to_id, m.from_id) as id2, max(m.id) as max_id
from messages m
group by id1, id2;
You can then get the complete information about the message by joining back:
select m.*
from messages m
where m.id in (select max(m.id) as max_id
from messages m
group by least(m.to_id, m.from_id), greatest(m.to_id, m.from_id)
);
Note: In older versions of MySQL, putting the subquery in the from clause and using join is much more efficient.

Enhancing performance of SQL query

I am running a query on three tables messages, message_recipients and users.
Table structure of messages table:
id int pk
message_id int
message text
user_id int
...
Index for this table is on user_id, message_id and id.
Table structure of message_recipients table:
id int pk
message_id int
read_date datetime
user_id int
...
Index is on id, message_id and user_id.
Table structure of users table:
id int pk
display_name varchar
...
Index is on id.
I am running the following query against these tables:
SELECT
m.*,
if(m.user_id = 0, 'Campus Manager', u.display_name) AS name,
mr.read_date,
IF(m1.message_id > 0 and m1.user_id=1, true, false) as replied
FROM
messages m
JOIN
message_recipients mr
ON
mr.message_id = m.id
LEFT JOIN
users u
ON
u.UID = m.user_id
LEFT JOIN
messages m1
ON
m1.message_id = m.id
WHERE
mr.user_id = 1
AND
m.published = 1
GROUP BY
mr.message_id
ORDER BY
m.created DESC
EXPLAIN returns the following data for this query:
UPDATE
As suggested by #e4c5, I added new composite index on (published,user_id,created) and now the explain query shows this:
How can this query be optimized by adding required indexes (if any) as it is taking lot of time?
GROUP BY needs to list all the non-aggregated columns. I suspect that would be a mess. Why do you need GROUP BY at all?
Why are you linking messages.id to messages_id? Is this a hierarchical table, but the column names aren't like 'parent_id'?
"Index is on id, message_id and user_id" -- is that one composite index or 3 single-column indexes? (It makes a big difference.) It would be better to show us SHOW CREATE TABLE instead of ambiguously paraphrasing.
Is user_id=1 prolific? That is, are you expecting thousands of rows? Is this query only a problem for him?
Using LEFT JOIN implies that m1.message_id could be NULL, yet the reference to it seems to ignore that possibility.
If this is a single table that contains a message thread -- both the main info about the thread and the individual responses, then I suggest it is a bad design. (I made this mistake once upon a time.) I think it iis better to have a table with one row per thread and another table with one row per comment. 1 thread : many comments. So there would be a thread_id in the comment table.
I was able to bring down the query time from 3 seconds to 0.1 second by adding a new index to messages and message_recipients table and changing the database engine of messages table to MyISAM from InnoDB.
Composite index composite added on these columns with respective order on messages table - published, user_id, created
Composite index message_id_2 added on two columns on message_recipients table - message_id, user_id
EXPLAIN Query now shows

How Mysql indexes works in case of JOIN?

Suppose I have two tables patient, person
Mysql query is like below.
select fname , lname
from patient p
left join per on (per.person_id=p.person_id)
where p.account_id=2 and (per.fname like 'will%' OR per.lname like 'will%' ).
In case of this query how mysql will use index created on (p.account_id,p.person_id)
person_id is a foreign key from person table in patient table. .
I suspect you do not want LEFT. With LEFT JOIN, you are asking for account #2 whether or not he is named 'will'.
SELECT fname, lname
FROM patient p
JOIN per ON per.person_id = p.person_id
WHERE p.account_id = 2
AND (per.fname LIKE 'will% OR per.lname LIKE 'will%')
will find the full name of account #2 if it is a 'will', else return nothing.
You have not said what indexes you have, so we cannot explain your existing indexes. Please provide SHOW CREATE TABLE for each table.
For either version of the query, these indexes are the only useful ones:
p: INDEX(account_id) -- if it is not already the PRIMARY KEY
per: INDEX(person_id) -- again, if it is not already the PRIMARY KEY.
A PRIMARY KEY is a UNIQUE index.
The first index (or PK) would be a quick lookup to find the row(s) with account_id=2. The second would make the join work well. No index is useful for "will" because of the OR.
The query will look at patient first, then per, using "Nested Loop Join".
Please also provide EXPLAIN SELECT ..., so we can discuss the things I am guessing about.

Select rows from a table not matched in another table

So I have the following:
A lookup table that has two columns and looks like this for example:
userid moduleid
4 4
I also have a users table that has a primary key userid which the lookup table references. The user table has a few users lets say, and looks like this:
userid
1
2
3
4
In this example, it show that the user with ID 4 has a match with module ID 4. The others are not matched to any moduleid.
I need a query that gets me data from the users table WHERE the moduleid is not 4. In my application, I know the module but I don't know the user. So the query should return the other userids apart from 4, because 4 is already matched with module ID 4.
Is this possible to do?
I think I understand your question correctly. You can use a sub-query to cross-check the data between both tables using the NOT IN() function.
The following will select all userid records from the user_tbl table that do not exist in the lookup_tbl table:
SELECT `userid`
FROM `user_tbl`
WHERE `userid` NOT IN (
SELECT DISTINCT(`userid`) FROM `lookup_tbl` WHERE moduleid = 4
)
There are several ways to do this, one pretty intuitive way (in my opinion) is the use an in predicate to exclude the users with moduleid 4 in the lookup table:
SELECT * FROM Users WHERE UserID NOT IN (SELECT UserID FROM Lookup WHERE ModuleID = 4)
There are other ways, with possibly better performance (using a correlated not exists query or a join for instance).
One other option is to use a LEFT JOIN so that you can get the values from both tables, even when there is not a match. Then, pick the rows where there is no userid value from the lookup table.
SELECT u.userid
FROM usersTable u
LEFT JOIN lookupTable lt ON u.userid = lt.userid
WHERE lt.userid IS NULL
Are you looking for a query like this?
select userid from yourtablename where moduleid<>4

Multiple "where" -s from one table into one view

I have a table called "users" with 4 fields: ID, UNAME, NAME, SHOW_NAME.
I wish to put this data into one view so that if SHOW_NAME is not set, "UNAME" should be selected as "NAME", otherwise "NAME".
My current query:
SELECT id AS id, uname AS name
FROM users
WHERE show_name != 1
UNION
SELECT id AS id, name AS name
FROM users
WHERE show_name = 1
This generally works, but it does seem to lose the primary key (NaviCat telling me "users_view does not have a primary key...") - which I think is bad.
Is there a better way?
That should be fine. I'm not sure why it's complaining about the loss of a primary key.
I will offer one piece of advice. When you know that there can be no duplicates in your union (such as the two parts being when x = 1 and when x != 1), you should use union all.
The union clause will attempt to remove duplicates which, in this case, is a waste of time.
If you want more targeted assistance, it's probably best if you post the details of the view and the underlying table. Views themselves don't tend to have primary keys or indexes, relying instead on the underlying tables.
So this may well be a problem with your "NaviCat" product (whatever that is) expecting to see a primary key (in other words, it's not built very well for views).
If i am understanding your question correctly, you should be able to just use a CASE statement like below for your logic
SELECT
CASE WHEN SHOW_NAME ==1 THEN NAME ELSE UNAME END
FROM users
This can likely be better written as the following:
SELECT id AS id, IF(show_name == 1, name, uname) AS name
FROM users