Optimize query: Select in Select - mysql

Lets say i have a table to store the blogs (table name is blogs) of each user.
But also have a table to store the blogs from other users that the user liked (table name is likedBlogs), yes?
so to retrieve them i just do:
-Select user's blogs.
-Add the blogs he liked.
$q = mysql_query("
SELECT id
FROM blogs
WHERE (iduser = $id)
OR id IN
(SELECT idblog
FROM likedBlogs
WHERE iduser='$id')
AND (id > 0)
ORDER BY id DESC
LIMIT 20
") or die(mysql_error());
Can i do this better? (how would you qualify in performance this query?)
Thanks

I believe you can better refactor it usign EXISTS instead of IN.
IN requires the entire result set be returned and then begin search for a value, but EXISTS check row by row and interrupt inner query when first occurence is found.
SELECT id
FROM blogs
WHERE (iduser = $id)
OR EXISTS
(SELECT idblog
FROM likedBlogs
WHERE iduser='$id' AND idblog = id)
AND (id > 0)
ORDER BY id
DESC LIMIT 20
See Optimizing IN/=ANY Subqueries
very useful optimization is to “inform” the subquery that the only
rows of interest are those where the inner expression inner_expr is
equal to outer_expr. This is done by pushing down an appropriate
equality into the subquery's WHERE clause. That is, the comparison is
converted to this: EXISTS (SELECT 1 FROM ... WHERE subquery_where AND
outer_expr=inner_expr)

Usually you would use join instead of nested select's.
select blogs.id
from blogs inner join likedBlogs on (blogs.iduser = likedBlogs.iduser and blogs.id = likedBlogs.idblog)
where blogs.iduser = $id
and blogs.id > 0
order by blogs.id desc limit 20
upd first time I didn't read task statement correctly
select blogs.id
from blogs left join likedBlogs on (blogs.id = likedBlogs.idblog)
where blogs.iduser = $id or likedBlogs.iduser = $id;

Your query looks fine to me. For performance you should make sure you have indexes on the id columns in your database tables.

I think you might be better served by a union
SELECT id
FROM blogs
WHERE iduser = $id AND id > 0
UNION
SELECT idblog AS id
FROM likedBlogs
WHERE iduser='$id' AND idblog > 0

Related

Select rows with similar value in one column

I have a table called trades and has a field session id. The table has specific rows with a similar session id. This reason some rows have a similar session id is that when a trade is placed, it takes an existing session id.
I now want to select rows with similar sessions ids and do something with it.
This is my first query that lists all the rows
SELECT * FROM trades
where trade_session_status="DONE" AND
trade_profit_worker_status="UNDONE"
I have tried this query as well
SELECT * FROM trades
where trade_session_status="DONE" AND
trade_profit_worker_status="UNDONE"
order BY(session_id)
I have looked at the distinct queries and came up with this query
SELECT DISTINCT session_id,id
FROM trades
WHERE trade_session_status="DONE" AND
trade_profit_worker_status="UNDONE"
ORDER BY session_id
The #2 and #3 queries all return the same number of rows. My question is,will the #2 and #3 queries always return the rows with distinct session_id without leaving any rows out?.
Sounds to me that you could use an EXISTS for this.
SELECT *
FROM trades t
WHERE trade_session_status = 'DONE'
AND trade_profit_worker_status = 'UNDONE'
AND EXISTS
(
SELECT 1
FROM trades d
WHERE d.trade_session_status = 'DONE'
AND d.trade_profit_worker_status = 'UNDONE'
AND d.session_id = t.session_id
AND d.id <> t.id
);
Note that the criteria for trade_session_status & trade_profit_worker_status are also used in the query for the EXISTS. I don't know if that's needed for your purpose, so remove them if that's not what you expect. But you get the idea.
Another way is to inner join to a sub-query with the duplicate session_id's.
SELECT t.*
FROM trades t
JOIN
(
SELECT session_id
FROM trades
WHERE trade_session_status = 'DONE'
AND trade_profit_worker_status = 'UNDONE'
GROUP BY session_id
HAVING COUNT(*) > 1
) d ON d.session_id = t.session_id
WHERE t.trade_session_status = 'DONE'
AND t.trade_profit_worker_status = 'UNDONE';

How to join tables with union ? mysql

I have two tables:
history
business
I want to run this query :
SELECT name, talias.*
FROM
(SELECT business.bussName as name history.*
FROM history
INNER JOIN business on history.bussID = business.bussID
WHERE history.activity = 'Insert' OR history.activity = 'Update'
UNION
SELECT name as Null, history.*
FROM history
WHERE history.activity = 'Delete'
) as talias
WHERE 1
order by talias.date DESC
LIMIT $fetch,20
this query take 13 second , I think the problem is that Mysql join all the rows at history and business tables ! While it should join just 20 rows !
how could I fix that ?
If I understand you correctly you want all rows from history where the activity is deleted plus all those rows where the activity is 'Insert' or 'Update' and there is a corresponding row in the business table.
I don't know if that is going to be faster than your query - you will need to check the execution plan to verify this.
SELECT *
FROM history
where activity = 'Delete'
or ( activity in ('Insert','Update')
AND exists (select 1
from business
where history.bussID = business.bussID))
order by `date` DESC
LIMIT $fetch,20
Edit (after the question has changed)
If you do need columns from the business table, replacing the union with an outer join might improve performance.
But to be honest, I don't expect it. The MySQL optimizer isn't very smart and I wouldn't be surprised if the outer join was actually implemented using some kind of union. Again only you can test that by looking at the execution plan.
SELECT h.*,
b.bussName as name
FROM history
LEFT JOIN business b
ON h.bussID = b.bussID
AND h.activity in ('Insert','Update')
WHERE h.activity in ('Delete', 'Insert','Update')
ORDER BY h.`date` DESC
LIMIT $fetch,20
Btw: date is a horrible column name. First because it's a reserved word, second (and more important) because it doesn't document anything. Is that the "creation date"? The "deletion date"? A "due date"? Some other date?
Try this:
SELECT h.*
FROM history AS h
WHERE (h.activity IN ('Insert', 'Update')
AND EXISTS (SELECT * FROM business AS b WHERE b.bussID = h.bussID))
OR h.activity = 'Delete'
ORDER BY h.date DESC
LIMIT $fetch, 20
For the ORDER BY and LIMIT to be efficient, make sure you have an index on history.date.

Improving Efficiency of my SQL

I have a MySQL table of LIKES (likeID,userID,objectID,likeDate) and I would like to be able to count all the 'likes' that have been made after the user in question.
Typically I would get the date:
SELECT likeDate FROM LIKES WHERE userID = <logged in user's ID>
and then find all dates and count the row returned (or use mysql COUNT) like this:
SELECT * FROM LIKES WHERE likeDate > <given date>
However, I'm sure there is a way to do this in one query rather than making two calls to the database. Can anyone help?
Thanks
Feed the result of the first query directly into the second one:
SELECT COUNT(*)
FROM LIKES
WHERE likeDate > (
SELECT max(likeDate)
FROM LIKES
WHERE userID = <logged in user's ID>
)
However note that you need to add the use of max() in your first query.
This query should be the fastest possible way to get your answer. To ensure maximum performance, add indexes on both userID and likeDate:
create index likes_userId on likes(userID);
create index likes_likeDate on likes(likeDate);
SELECT l1.likeDate,
(SELECT COUNT(1) FROM LIKES l2 WHERE l2.likeDate > l1.likeDate) AS likesAfter
FROM LIKES l1
WHERE userID = ?
GROUP BY l1.likeDate
Or as a join,
SELECT l1.likeDate, COUNT(1)
FROM LIKES l1
LEFT OUTER JOIN LIKES l2 ON l2.likeDate > l1.likeDate
WHERE userID = ?
GROUP BY l1.likeDate
SELECT * FROM LIKES WHERE likeDate >
IFNULL((SELECT max(likeDate) FROM LIKES WHERE userID = <logged in user's ID>
adn objectId=<question's Id>),0)
and objectId=<question's Id>

Mysql select distinct rows in one table based on rows of another

I have two tables(Friends and News_Feed). I need to retrieve the most recent rows(highest id) from the News_Feed table. The catch is that I only want to select rows in the News_Feed table that pertain to either of the two columns in the Friends table. I also need to order by News_Feed.id, so creating two queries(such as first selecting my friends, and then looping into a News_Feed query) will not work. Tables are set up as follows:
-Friends-
id
user
user_friending
News_Feed
id
pertaining_user
action
orig_comment
My current, unworking query, is...
$result = mysql_query("SELECT * FROM News_Feed WHERE pertaining_user=(SELECT user FROM Friends WHERE user_friending='37' AND is_confirmed='1' UNION SELECT user_friending FROM Friends WHERE user='37' AND is_confirmed='1') AND orig_comment='0' ORDER BY id DESC")or die(mysql_error());
while($row_news = mysql_fetch_array($result)){
This returns subquery returns more than one row error, which I understand. There must be way though to do this.
Use exists:
select
*
from
News_Feed f
where
exists (
select
1
from
friends u
where
(u.user = f.pertaining_user and u.user_friending = '37')
or (u.user_friending = f.pertaining_user and u.user = '37')
and u.is_confirmed = 1
)
order by
f.id desc
This will be much faster than trying to do an in with a union. It does a semi-join and throws out invalid rows immediately.
You are probably looking for the IN keyword.
SELECT *
FROM News_Feed
WHERE pertaining_user IN
(
SELECT user
FROM Friends
WHERE user_friending='37'
AND is_confirmed='1'
UNION
SELECT user_friending
FROM Friends
WHERE user='37'
AND is_confirmed='1')
AND orig_comment='0'
ORDER BY id DESC

Way to find rows with a same ID and only keep one row

I have a working query that will return some results(records) from my database, like:
123|John Williams|IL|M|06/01/2011|ACTIVE
124|Mary Haque|NY|F|TERMINATED|06/30/2011
124|Mary Haque|NY|F|07/01/2011|ACTIVE
125|Alex Chan|VA|07/01/2011|ACTIVE
126|Rob Kreb|VA|TERMINATED|06/20/2011
As you can see, the result is simply a list of customer records, while the last two fields indicate whether the member is active or terminated and associated active/terminated date.
The complication now is, as you can see for member with ID 124 (Mary Haque), she has two records, and for this kind of two-record customer, I only want to keep the row where the member is active while totally ignore her terminated history. So for example, the desired output for the above should be:
123|John Williams|IL|M|06/01/2011|ACTIVE
124|Mary Haque|NY|F|07/01/2011|ACTIVE
125|Alex Chan|VA|07/01/2011|ACTIVE
126|Rob Kreb|VA|TERMINATED|06/20/2011
as you can see, now Mary Haque only has her active information on the result. The above result is generate by a SQL "Select" query, but I couldn't simply append a "WHERE status=ACTIVE" to this query because I still want to keep the members that only has ONE record like Rob Kreb above even though he is terminated. I only want the filtering for TERMINATED member record to take place when a certain member has two records.
FYI, my current query looks like this:
SELECT * FROM customer_change WHERE CUSTOMER_LOGIN NOT IN(SELECT CUSTOMER_LOGIN FROM customer_full WHERE CUSTOMER_LOGIN IS NOT NULL)
UNION
SELECT * FROM customer_change WHERE CUSTOMER_POINTS=0 AND CUSTOMER_LOGIN NOT IN(SELECT CUSTOMER_LOGIN FROM customer_full WHERE CUSTOMER_POINTS=0 AND CUSTOMER_LOGIN IS NOT NULL)
Thanks for the help in advance!
colX and colY are the last 2 columns of the query:
SELECT *
FROM (your_UNION_query) AS p
WHERE NOT ( colX = 'TERMINATED'
AND EXISTS
( SELECT *
FROM (your_UNION_query) AS q
WHERE q.id = p.id
AND q.colY = 'ACTIVE'
)
)
Something like this will do the trick:
DELETE
FROM tablename
WHERE tablename.status = 'TERMINATED'
AND tablename.id IN(SELECT
id
FROM (SELECT
t.id
FROM tablename t
GROUP BY t.id
HAVING COUNT(t.id) > 1) AS T1)
Assuming that id is the field refering to 124,125, etc.