Mysql performance/optimization help - mysql

So this was a small site that got extremely popular very fast and now and im having major problems with the below sql query.
I understand that my DB design is not great. I have text field for subjects and programs witch contains a serialized array and i search it using like.
the below query takes about a minute.
SELECT p.*, e.institution
FROM cv_personal p
LEFT JOIN cv_education e
ON p.id = e.user_id
LEFT JOIN cv_literacy l
ON p.id = l.user_id
WHERE 1 = 1
AND (e.qualification LIKE '%php%' OR e.subjects LIKE '%php%' OR l.programs LIKE '%php%')
GROUP BY p.id
ORDER BY p.created_on DESC

What an EXPLAIN show ?
I think you can add conditions to a join to reduce number of records which are used :
SELECT p.*, e.institution
FROM cv_personal p
LEFT JOIN cv_education e
ON (e.qualification LIKE '%php%' OR e.subjects LIKE '%php%') AND p.id = e.user_id
LEFT JOIN cv_literacy l
ON l.programs LIKE '%php%' AND p.id = l.user_id
ORDER BY p.created_on DESC
And why do you use GROUP BY ?

Related

sql query is behaving strange

I have a query
select c.CommentId
,c.CommentText
, c.CommenterId
, c.CommentDate
, u.first_name
, u.last_name
, i.ImageName
, i.Format
from comment c
join users u
on c.CommenterId = u.user_id
join user_profile_image i
on u.user_id = i.UserId
where PostId = 76
order
by CommentDate desc
limit 10
This query returns empty results when i.ImageName field is empty in the table. I want to return the row if the ImageName field is emty. How should I do this?
JOIN defaults to INNER JOIN for MySQL - try changing
join user_profile_image i
to
LEFT join user_profile_image i
The accepted answer here has a good visual explanation: Difference in MySQL JOIN vs LEFT JOIN
To include the rows when the ImageName field is empty, use LEFT JOIN, like this:
SELECT c.CommentId,c.CommentText, c.CommenterId, c.CommentDate, u.first_name,
u.last_name,i.ImageName,i.Format
FROM comment c
INNER JOIN users u ON c.CommenterId=u.user_id
LEFT JOIN user_profile_image i ON u.user_id=i.UserId
WHERE PostId = 76
ORDER BY CommentDate DESC
LIMIT 10;
The issue isn't exactly that i.ImageName is empty. The issue is that there is no image associated with the user. The join doesn't find an image, and without a match, the user isn't returned.
The solution is to use left join. My inclination is to write the query entirely with left join:
select c.CommentId, c.CommentText, c.CommenterId, c.CommentDate,
u.first_name, u.last_name,
i.ImageName, i.Format
from comment c left join
users u
on c.CommenterId = u.user_id left join
user_profile_image i
on u.user_id = i.UserId
where PostId = 76
order by c.CommentDate desc
limit 10;
Note: This assumes that PostId is in the comment table, which seems reasonable given the table names.

Count matched words from IN operator

i have this little mysql query :
select t.title FROM title t
inner join movie_keyword mk on mk.movie_id = t.id
inner join keyword k on k.id = mk.keyword_id
where k.keyword IN (
select k.keyword
FROM title t
inner join movie_keyword mk on mk.movie_id = t.id
inner join keyword k on k.id = mk.keyword_id
where t.id = 166282
)
LIMIT 15
as you can see it will return all titles from title that have at least one the same keyword that have movie with id 166282.
Now i have problem, because i want also count how many keywords was matched in IN operator(let's say i want to see only titles that have 3 or more the same keywords), i tried something with aggregate functions, but everything failed, so i came here with my problem. Maybe somebody can give me some advice, or code example.
I'm not also sure, if this "subquery way" is good, so if there are some better options how i should solve my problem, I am open to any suggestions or tips.
Thank you!
#Edit
So after some problems, i have one more. This is my current query :
SELECT s.title,s.vote,s.rating,count(dk.key) as keywordCnt, count(dg.name) as genreCnt
FROM series s
INNER JOIN series_has_genre shg ON shg.series_id = s.id
INNER JOIN dict_genre dg ON dg.id = shg.dict_genre_id
INNER JOIN series_has_keyword shk ON shk.series_id = s.id
INNER JOIN dict_keyword dk ON dk.id = shk.dict_keyword_id
WHERE dk.key IN (
SELECT dki.key FROM series si
INNER JOIN series_has_keyword shki ON shki.series_id = si.id
INNER JOIN dict_keyword dki ON dki.id = shki.dict_keyword_id
WHERE si.title LIKE 'The Wire'
)
and dg.name IN (
SELECT dgo.name FROM series so
INNER JOIN series_has_genre shgo ON shgo.series_id = so.id
INNER JOIN dict_genre dgo ON dgo.id = shgo.dict_genre_id
WHERE so.title LIKE 'The Wire'
)
and s.production_year > 2000
GROUP BY s.title
ORDER BY s.vote DESC, keywordCnt DESC ,s.rating DESC, genreCnt DESC
LIMIT 5
Problem is, it is very, very, very slow. Any tips what i should change, to run it faster ?
Will this work for you:
select t.title, count(k.keyword) as keywordCount FROM title t
inner join movie_keyword mk on mk.movie_id = t.id
inner join keyword k on k.id = mk.keyword_id
where k.keyword IN (
select ki.keyword
FROM title ti
inner join movie_keyword mki on mki.movie_id = ti.id
inner join keyword ki on ki.id = mki.keyword_id
where ti.id = 166282
) group by t.title
LIMIT 15
Note that I have changed the table names inside the nested query to avoid confusion.

GROUP BY and ORDER BY issues

I have the following query:
SELECT DISTINCT (
s.styleTitle
), COUNT(p.id) AS `PictureCount`
FROM `style` s
LEFT JOIN `instagram_picture_style` ps ON s.id = ps.style_id
LEFT JOIN `instagram_shop_picture` p ON ps.picture_id = p.id
LEFT JOIN `instagram_picture_category` c ON c.picture_id = p.id
LEFT JOIN `instagram_second_level_category` sl ON c.second_level_category_id = sl.id
WHERE sl.id =25
GROUP BY p.id
ORDER BY PictureCount
however this query gives me:
I basically wanted the list to be ordered by the style that has the most pictures in it. What did I do wrong? Why is it giving me 1 on all of the styles, I am pretty sure it has more pictures for that style
ORDER BY doesn't have underscores. But equally important, you are using DISTINCT in a way where you seem to think that it is a function. It is not. It is a modifies on the SELECT and it applies to all columns.
You should group by the same column you have in the distinct. Something like this:
SELECT s.styleTitle, COUNT(p.id) AS `PictureCount`
FROM `style` s
LEFT JOIN `instagram_picture_style` ps ON s.id = ps.style_id
LEFT JOIN `instagram_shop_picture` p ON ps.picture_id = p.id
LEFT JOIN `instagram_picture_category` c ON c.picture_id = p.id
LEFT JOIN `instagram_second_level_category` sl ON c.second_level_category_id = sl.id
WHERE sl.id = 25
GROUP BY s.styleTitle
ORDER BY PictureCount DESC;
In fact, you almost never need distinct with group by. If you are using, you need to think why it would be necessary.

Howto rewrite MySQL NOT IN query using a join?

i use a sql query like this to get some results i need:
SELECT
*
FROM
pictures p
WHERE
p.id NOT IN
(
SELECT
picture_id
FROM
guesses g
WHERE
g.user_id = XXX
)
AND
p.user_id != XXX
;
Relation is as follows: A user has many pictures and a picture belongs to one user. A user has many guesses and a guess belongs to one picture. The tricky part is that a user is only allowed one guess for the same picture.
XXX = $user_id
I guess that there is a way to rewrite this sub-select using a left join but i can't get it working.
Can anyone help?
Anja
Because it is a NOT IN condition you should use a LEFT OUTER JOIN. This is the direct translation to left outer join of your query:
SELECT
distinct p.*
FROM
pictures p
LEFT OUTER JOIN
guesses g ON g.picture_id = p.id and g.user_id = XXX
WHERE
p.user_id != XXX
and g.user_id is null
;
Okay, here we go. I guess that is the correct answer:
SELECT DISTINCT
p.*
FROM
pictures p
LEFT OUTER JOIN
guesses g
ON
g.picture_id = p.id and g.user_id = 1
WHERE
g.user_id is null
and p.user_id != 1
;

Sql Count on many to many

I have three tables
post
id | statement | date
features
id | feature
post_feature (many to many table between Post and Feature)
post_id | feature_id
I want to fire a query that will give me count of different distinct features and its respective features for the posts that are in given date period. I have just started learning SQL and I am not able to crack this one.
I tried the following one but not getting correct results.
SELECT f.feature, count(f.feature)
FROM post_feature l
JOIN features f ON (l.featureid = f.id AND l.featureid IN (
select post.id from post where post.date > 'some_date'))
GROUP BY f.feature
You can try like this:
SELECT f.feature, count(f.feature)
FROM post_feature l
JOIN features f ON l.featureid = f.id
JOIN post p ON l.post_id =p.id
WHERE p.date > 'some_date'
GROUP BY f.feature
select f.feature, count(*)
from post_feature l inner join features f on l.feature_id = f.id
inner join post p on l.post_id = p.id
where p.date > 'some_date'
group by f.feature
Your SQL is quite creative. However, your join in the IN clause is on the wrong columns. It should be on postid to postid.
Although that fixes the query, here is a better way to write it:
SELECT f.feature, count(f.feature)
FROM post p join
post_feature pf
on p.id = pf.postid join
feature f
on pf.featureid = f.id
where post.date > 'some_date'
GROUP BY f.feature
This joins all the tables, and then summarizes by the information you want to know.
Try
SELECT f.feature, count(DISTINCT f.feature)
FROM post_feature l
JOIN features f ON (l.featureid = f.id AND l.featureid IN (
select post.id from post where post.date > 'some_date'))
GROUP BY f.feature