How to make this query run faster? - mysql

I have query like this:
SELECT
`om_chapter`.`manganame` as `link`,
(SELECT `manganame` FROM `om_manga` WHERE `Active` = '1' AND `om_manga`.`link` = `om_chapter`.`manganame` LIMIT 0,1) AS `manganame`,
(SELECT `cover` FROM `om_manga` WHERE `Active` = '1' AND `om_manga`.`link` = `om_chapter`.`manganame` LIMIT 0,1) AS `cover`,
(SELECT `othername` FROM `om_manga` WHERE `Active` = '1' AND `om_manga`.`link` = `om_chapter`.`manganame` LIMIT 0,1) AS `othername`
FROM `om_chapter`
WHERE
`Active` = '1' AND
(SELECT `Active` From `om_manga` WHERE `om_manga`.`link` = `om_chapter`.`manganame` LIMIT 0,1) AND
`id` IN ( SELECT MAX(`id`) FROM `om_chapter` WHERE `Active` = '1' GROUP BY `manganame` )
ORDER BY `id` DESC LIMIT 10
So how can I make this query faster?
Here are my tables:
om_chapter:
id | manganame | chapter | Active
-----------------------------------------
1 | naruto | 1 | 1
2 | naruto | 12 | 1
3 | naruto | 22 | 1
4 | bleach | 10 | 1
5 | bleach | 15 | 1
6 | gents | 1 | 1
7 | naruto | 21 | 1
om_manga:
id | othername | manganame | cover | Active
-----------------------------------------------------
1 | naruto | naruto | n.jpg | 1
2 | bleach | bleach | b.jpg | 1
4 | gents | gents | g.jpg | 1
First thing i want form this query is to give me 10 last rows form om_chapter by grouping manganame and ordering by id.. i try to use a simple query by using group or even distinct but none of them give me the right result...
In a simple query with group or distinct, the result is like this:
id | manganame | chapter | Active
-----------------------------------------
7 | prince | 21 | 1
5 | gent | 15 | 1
2 | naruto | 12 | 1
1 | bleach | 1 | 1
But i want this result:
id | manganame | chapter | Active
-----------------------------------------
9 | gents | 21 | 1
8 | bleach | 21 | 1
7 | prince | 21 | 1
6 | naruto | 1 | 1
So i use this:
WHERE
`Active` = '1' AND
(SELECT `Active` From `om_manga` WHERE `om_manga`.`link` = `om_chapter`.`manganame` LIMIT 0,1) AND
`id` IN ( SELECT MAX(`id`) FROM `om_chapter` WHERE `Active` = '1' GROUP BY `manganame` )
And i use sub select in where because i want Active's field in om_manga's table be 1..
For the reset of sub select, i actually didn`t try join, but i will..!

I might have misunderstood your intentions.. But here's one try:
SELECT c.`manganame` AS `link`
, m.`manganame`
, m.`cover`
, m.`othername`
FROM
`om_manga` m
INNER JOIN `om_chapter` c
ON m.`link` = c.`manganame`
INNER JOIN
( SELECT `manganame`, MAX(`id`) AS `maxid`
FROM `om_chapter`
WHERE `Active` = '1'
GROUP BY `manganame` ) mx
ON mx.`maxid` = c.`id`
ORDER BY c.`id` DESC LIMIT 10

I would introduce a foreign key contstrain to the om_chapter table to account for the link from a manga to its corresponding chapters.
This is how I would conceptualize the problem.
A manga can have many chapters. A chapter belongs to one manga.
Then I would alter the om_chapter table, to include a foreign key for the chapter to link to the manga.
ALTER TABLE om_Chapter (
ADD mangaID int references om_Manga (id)
)
And drop the manganame column as it is just redundant now
ALTER TABLE om_Chapter (
DROP COLUMN manganame
)
Your tables then could look like this.
om_manga:
id | othername | manganame | cover | Active
-----------------------------------------------------
1 | naruto | naruto | n.jpg | 1
2 | bleach | bleach | b.jpg | 1
4 | gents | gents | g.jpg | 1
om_chapter:
id | chapter | Active | mangaID
-----------------------------------------
1 | 1 | 1 | 1
2 | 12 | 1 | 1
3 | 22 | 1 | 1
4 | 10 | 1 | 2
5 | 15 | 1 | 2
6 | 1 | 1 | 4
Finally you could query the tables like so
SELECT TOP 10 m.Manganame as link,
m.Manganame,
m.cover,
m.othername,
FROM om_manga as m INNER JOIN
om_chapter as c ON m.ID = c.mangaID
WHERE m.active = 1 AND c.active = 1
ORDER BY m.ID DESC

Why not a simple join?
SELECT om_chapter.manganame, cover, othername
FROM om_chapter
JOIN om_manga ON om_chapter.manganame = om_manga=manganame
WHERE om_chapter.Active = 1 AND om_manga.Active = 1
unless I'm misreading your version.

Use a left outer join (and lose the sub-queries, and the back-quotes):
SELECT c.manganame AS link,
m.manganame AS manganame,
m.cover AS cover,
m.othername AS `othername
FROM om_chapter AS c
LEFT JOIN om_manga AS m
ON c.manganame = m.manganame
WHERE c.Active = '1'
AND c.id IN (SELECT MAX(o.id)
FROM om_chapter AS o
WHERE o.active = 1
GROUP BY o.manganame)
ORDER BY c.id DESC LIMIT 10
Were it my query, I'd probably select 'c.id AS id' too.

Related

MySQL - JOIN columns of table1 with AVG of two columns from table2 and table3

I am trying to combine columns of the following 3 tables.
pages:
*id* | *identifier* | reference | url | ...
-------------------------------------------------------
1 | 1 | page one | http://... | ...
2 | 3 | page two | ..... | ...
3 | 23 | page three | ..... | ...
4 | 25 | page four | ..... | ...
5 | 43 | page five | ..... | ...
comments:
page_id | *rating* | comment | is_approved | name | ...
-------------------------------------------------------
1 | 4 | bla bla | 1 | joe | ...
2 | 5 | more bla | 1 | jim | ...
2 | 3 | blub | 1 | jill | ...
3 | 1 | blubblub | 1 | jack | ...
4 | 2 | hey ho | 0 | jimbo| ...
5 | 4 | huhu | 1 | mike | ...
ratings:
page_id | *rating* | ip_address | ...
-----------------------------------
1 | 3 | ... | ...
1 | 2 | ... | ...
2 | 5 | ... | ...
3 | 4 | ... | ...
4 | 0 | ... | ...
5 | 2 | ... | ...
pages.id links to comments.page_id and ratings.page_id
More specifically, I'd like to get the average of comments.rating and ratings.rating AS 'star_total' and combine this new column with the corrosponding rows from "pages", so that I get a table structure like this: id, identifier, star_total.
This is what I have been dabbling with. I know its not how it should be. It's just a rough idea and as far as I got:
SELECT pages.id, pages.identifier, star_total
FROM pages LEFT JOIN
(
SELECT AVG(`rating`) FROM (
SELECT 'rating' FROM comments
WHERE `comments.is_approved = '1'
AND comments.rating != '0'
AND comments.page_id = ratings.page_id
UNION ALL
SELECT `rating`
FROM ratings
WHERE ratings.page_id = comments.page_id
) AS `star_total`
)
We could do something like this:
SELECT pages.id
, pages.identifier
, q.star_total
FROM pages
LEFT
JOIN ( SELECT t.page_id
, SUM(t.tot_rating)/SUM(t.cnt_rating) AS star_total
FROM ( SELECT c.page_id AS page_id
, SUM(c.rating) AS tot_rating
, COUNT(c.rating) AS cnt_rating
FROM comments c
WHERE c.is_approved = '1'
AND c.rating != '0'
GROUP BY c.page_id
UNION ALL
SELECT r.page_id -- AS page_id
, SUM(r.rating) -- AS tot_rating
, COUNT(r.rating) -- AS cnt_rating
FROM ratings r
GROUP BY r.page_id
) t
GROUP BY t.page_id
) q
ON q.page_id = pages.id
For large sets, those inline views (derived tables) are going to be expensive. A somewhat simpler approach to get an equivalent result, possibly exacerbating the performance issue with the inline view:
SELECT pages.id
, pages.identifier
, q.star_total
FROM pages
LEFT
JOIN ( SELECT t.page_id
, AVG(t.rating) AS star_total
FROM ( SELECT c.page_id AS page_id
, c.rating AS rating
FROM comments c
WHERE c.is_approved = '1'
AND c.rating != '0'
UNION ALL
SELECT r.page_id -- AS page_id
, r.rating -- AS rating
FROM ratings r
) t
GROUP BY t.page_id
) q
ON q.page_id = pages.id
I think you need to union the ratings data, then calculate the average per page, then join that result to the pages.
SELECT
p.id
, p.identifier
, u star_total
FROM pages AS p
LEFT JOIN (
SELECT
page_id
, AVG(rating) star_total
FROM (
SELECT
page_id
, rating
FROM comments
WHERE comments.is_approved = '1'
AND comments.rating != '0'
UNION ALL
SELECT
page_id
, rating
FROM ratings
) d
GROUP BY
page_id
) AS u ON p.id = u.id

Select the highest record of each relation

I need to get the most visited site of all users
I have the following tables:
page:
id link_page
1 link 1
2 link 2
3 Link 3
user:
id name
1 Joseph
2 Cris
3 Byram
pages_user:
user_id page_id
1 1
1 1
1 1
2 1
2 1
2 2
Should obtain the following:
user_id | most visited page id
1 1
2 1
So far I did something like this:
SELECT count(pu.page_id) as counter, pu.user_id
FROM page_user pu
INNER JOIN page p on p.id = pu.page_id
INNER JOIN users u on u.id = pu.user_id
GROUP BY pu.user_id, pu.page_id
ORDER BY counter DESC
But I return all the records.
As I can get the site most visited of all users?
Greetings from Chile.
You can try this:
SELECT
t.user_id,
t.page_id AS most_visited_page_id
FROM (
SELECT
pu.user_id,
pu.page_id,
COUNT(*) AS visitCount
FROM page_user pu
INNER JOIN page p ON p.id = pu.page_id
INNER JOIN user u ON u.id = pu.user_id
GROUP BY pu.user_id, pu.page_id
ORDER BY pu.user_id, visitCount DESC, rand() <-- In case of multiple
page with same count it will give random page or you can check
it with most recent visit with date if any -->
) AS t
GROUP BY t.user_id
Here is the sqlfiddle.
SELECT id, (SELECT page_id FROM page_user
WHERE user_id = id GROUP BY page_id
ORDER BY COUNT(page_id) DESC LIMIT 1) FROM USER
Try this query . You may modify to show all required fields
EDIT : To see the number of hits, use the following query.
SELECT id, (SELECT page_id FROM page_user
WHERE user_id = id GROUP BY page_id
ORDER BY COUNT(page_id) DESC LIMIT 1) page_id, (SELECT COUNT(page_id) cnt FROM page_user
WHERE user_id = id GROUP BY page_id
ORDER BY cnt DESC LIMIT 1) cnt FROM USER
Here is a way of doing it, consider the following
mysql> select * from user;
+------+--------+
| id | name |
+------+--------+
| 1 | Joseph |
| 2 | Cris |
| 3 | Byam |
+------+--------+
3 rows in set (0.00 sec)
mysql> select * from page;
+------+-----------+
| id | link_page |
+------+-----------+
| 1 | link 1 |
| 2 | link 2 |
| 3 | link 3 |
+------+-----------+
3 rows in set (0.00 sec)
+---------+---------+
| user_id | page_id |
+---------+---------+
| 1 | 1 |
| 1 | 1 |
| 1 | 1 |
| 2 | 1 |
| 2 | 1 |
| 2 | 2 |
| 3 | 2 |
| 3 | 2 |
| 3 | 2 |
| 3 | 1 |
| 3 | 3 |
| 3 | 3 |
+---------+---------+
12 rows in set (0.00 sec)
select
u.id as user_id,
p.id as `most visited page id`
from user u join (
select count(*) as cnt,user_id,page_id from pages_user group by user_id,page_id
)x on x.user_id = u.id
left join(
select count(*) as cnt,user_id,page_id from pages_user group by user_id,page_id
)y on x.user_id = y.user_id and y.cnt > x.cnt
join page p on p.id = x.page_id
where y.user_id is null ;
+---------+----------------------+
| user_id | most visited page id |
+---------+----------------------+
| 1 | 1 |
| 2 | 1 |
| 3 | 2 |
+---------+----------------------+

Select two items with maximum number of common values

I have the following table:
+----+-----------+-----------+
| id | teacherId | studentId |
+----+-----------+-----------+
| 1 | 1 | 4 |
| 2 | 1 | 2 |
| 3 | 1 | 1 |
| 4 | 1 | 3 |
| 5 | 2 | 2 |
| 6 | 2 | 1 |
| 7 | 2 | 3 |
| 8 | 3 | 9 |
| 9 | 3 | 6 |
| 10 | 1 | 6 |
+----+-----------+-----------+
I need a query to find two teacherId's with maximum number of common studentId's.
In this case teachers with teacherIds 1,2 have common students with studentIds 2, 1, 3, which is greater than 1,3 having common students 6.
Thanks in Advance!
[Edit]: After several hours I've had the following solution:
SELECT * FROM (
SELECT r1tid, r2tid, COUNT(r2tid) AS cnt
FROM (
SELECT r1.teacherId AS r1tid, r2.teacherId AS r2tid
FROM table r1
INNER JOIN table r2 ON r1.studentId=r2.studentId AND r1.teacherId!=r2.teacherId
ORDER BY r1tid
) t
GROUP BY r1tid, r2tid
ORDER BY cnt DESC
) t GROUP BY cnt ORDER BY cnt DESC LIMIT 1;
I was sure that there must exist more short and elegant solution, but I could not find it.
You would do this with a self-join. Assuming no duplicates in the table:
select t.teacherid, t2.teacherid, count(*) as NumStudentsInCommon
from table t join
table t2
on t.studentid = t2.studentid and
t.teacherid < t2.teacherid
group by t.teacherid, t2.teacherid
order by NumStudentsInCommon desc
limit 1;
If you had duplicates, you would just replace count(*) with count(distinct studentid), but count(distinct) requires a bit more work.
select t.teacherId, t2.teacherId, sum(t.studentId) as NumStudentsInCommon
from table1 t join
table1 t2
on t.studentId = t2.studentId and
t.teacherId < t2.teacherId
group by t.teacherId, t2.teacherId
order by NumStudentsInCommon desc

Order MySQL data by row value

I have two tables that looks like this:
Table: items
id | itemId
---|------
0 | 1
1 | 2
2 | 3
Table: item_specs
id | itemId | key | values
---|--------|---------------
0 | 1 | itemreceived | 2012-06-01
1 | 1 | modelyear | 1992
2 | 1 | model | 2
3 | 2 | itemreceived | 2012-06-05
4 | 2 | modelyear | 2003
5 | 2 | model | 1
6 | 3 | itemreceived | 2012-07-05
7 | 3 | modelyear | 2000
8 | 3 | model | 3
My current query looks like this:
SELECT items.*, item_specs.* FROM item_specs
INNER JOIN item_specs ON items.itemId = item_specs.itemId
WHERE itemId IN(1,2,3)
How can I order the result by a key value, for example: model?
The result I'm looking for is something like this: (if I order by model)
id | itemId | key | values
---|--------|---------------
3 | 2 | itemreceived | 2012-06-05
4 | 2 | modelyear | 2003
5 | 2 | model | 1
0 | 1 | itemreceived | 2012-06-01
1 | 1 | modelyear | 1992
2 | 1 | model | 2
6 | 3 | itemreceived | 2012-07-05
7 | 3 | modelyear | 2000
8 | 3 | model | 3
The content that is returned is ordered by the value that is that has the key model
You need the model number for every row. You can do that with a join:
SELECT items.*, item_specs.*
FROM item_specs
INNER JOIN item_specs ON items.itemId = item_specs.itemId
INNER JOIN item_specs aux ON (aux.key = 'model' and aux.itemID = item_specs.itemId)
WHERE item_specs.itemId IN(1,2,3)
ORDER BY aux.values/*this is the model*/, item_specs.id;
or with a subselect:
SELECT items.*,
item_specs.*,
(select aux.values
from item_specs aux
where aux.key = 'model' and aux.itemID = item_specs.itemId
) as model
FROM item_specs
INNER JOIN item_specs ON items.itemId = item_specs.itemId
WHERE item_specs.itemId IN(1,2,3)
ORDER BY model, item_specs.id;
SELECT * FROM `table` WHERE `key` = 'model' ORDER BY `values` ASC
You have to manually specify a table type/storage engine. That can't be seen in the structure you provided.
Read more here.
It seems you want to use an order by clause. This will order by the columns you need. You can also do sneaky things here, like insert a true/false value for what you order by first.
SELECT * FROM `table`
Order by (case When Key='model' then 0 else 1 end), values
See, for instance, http://blog.sqlauthority.com/2007/07/17/sql-server-case-statement-in-order-by-clause-order-by-using-variable/
SELECT * FROM `table`
WHERE `key` = 'model'
ORDER BY `values`;

Joining two tables without returning unwanted row

My table structure looks like this:
tbl.users tbl.issues
+--------+-----------+ +---------+------------+-----------+
| userid | real_name | | issueid | assignedid | creatorid |
+--------+-----------+ +---------+------------+-----------+
| 1 | test_1 | | 1 | 1 | 1 |
| 2 | test_2 | | 2 | 1 | 2 |
+--------+-----------+ +---------+------------+-----------+
Basically I want to write a query that will end in a results table looking like this:
(results table)
+---------+------------+---------------+-----------+--------------+
| issueid | assignedid | assigned_name | creatorid | creator_name |
+---------+------------+---------------+-----------+--------------+
| 1 | 1 | test_1 | 1 | test_1 |
| 2 | 1 | test_1 | 2 | test_2 |
+---------+------------+---------------+-----------+--------------+
My SQL looks like this at the moment:
SELECT
`issues`.`issueid`,
`issues`.`creatorid`,
`issues`.`assignedid`,
`users`.`real_name`
FROM `issues`
JOIN `users`
ON ( `users`.`userid` = `issues`.`creatorid` )
OR (`users`.`userid` = `issues`.`assignedid`)
ORDER BY `issueid` ASC
LIMIT 0 , 30
This returns something like this:
(results table)
+---------+------------+-----------+-----------+
| issueid | assignedid | creatorid | real_name |
+---------+------------+-----------+-----------+
| 1 | 1 | 1 | test_1 |
| 2 | 1 | 2 | test_1 |
| 2 | 1 | 2 | test_2 |
+---------+------------+-----------+-----------+
Can anyone help me get to the desired results table?
SELECT
IssueID,
AssignedID,
CreatorID,
AssignedUser.real_name AS AssignedName,
CreatorUser.real_name AS CreatorName
FROM Issues
LEFT JOIN Users AS AssignedUser
ON Issues.AssignedID = AssignedUser.UserID
LEFT JOIN Users AS CreatorUser
ON Issues.CreatorID = CreatorUser.UserID
ORDER BY `issueid` ASC
LIMIT 0, 30
On the general knowledge front, our illustrious site founder wrote a very nice blog article on this subject which I find myself referring to over and over again.
Visual Explanation of SQL Joins
Use this:
SELECT
`issues`.`issueid`,
`issues`.`creatorid`,
`creator`.`real_name`,
`issues`.`assignedid`,
`assigned`.`real_name`
FROM `issues` i
INNER JOIN `users` creator ON ( `creator`.`userid` = `issues`.`creatorid` )
INNER JOIN `users` assigned ON (`assigned`.`userid` = `issues`.`assignedid`)
ORDER BY `issueid` ASC
LIMIT 0 , 30
SELECT DISTINCT (i.issueid, i.creatorid, i.assignedid, u.real_name)
FROM issues i, users u
WHERE u.userid = i.creatorid OR u.userid = assignedid
ORDER BY i.issueid ASC
LIMIT 0 , 30
Not sure if the parenthesis are needed or not.
Does this work?
SELECT
i.issueid,
i.assignedid,
u1.real_name as assigned_name,
i.creatorid,
u2.real_name as creator_name
FROM users u1
INNER JOIN issues i ON u1.userid = i.assignedid
INNER JOIN users u2 ON u2.userid = i.creatorid
ORDER BY i.issueid
SELECT
i.issueid,
i.assignedid,
a.real_name,
i.creatorid,
c.real_name
FROM
issues i
INNER JOIN users c
ON c.userid = i.creatorid
INNER JOIN users a
ON a.userid = i.assignedid
ORDER BY
i.issueid ASC