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

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

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';

Two separate joins on the same query

I have a query to find duplicates in a table:
SELECT sofferenze.id_soff, sofferenze.Descrizione
FROM sofferenze
INNER JOIN (
SELECT Descrizione
FROM sofferenze
GROUP BY Descrizione
HAVING count( id ) >1
)dup ON sofferenze.Descrizione = dup.Descrizione
ORDER BY Descrizione ASC
It works like a charm and gives me all the duplicated rows.
I also have another query that starting from sofferenze.id_soff will give me another value in another table:
SELECT cod_fisc
FROM anagrafiche
JOIN `rischiatura` ON anagrafiche.id_ndg = rischiatura.id_ndg
WHERE id_ogg = 'SF000000012'
AND id_ruolo = 'RU010000002'
Actually this second query is run for each row returned by the first query replacing in this line WHERE id_ogg='SF000000012' the value 'SF000000012' with the value sofferenze.id_soff that is returned by the first query.
This code is not efficient because it runs several times the second query.
Are there any option that I can merge the two queries?
Why not combine it into a sub-query?
SELECT cod_fisc
FROM anagrafiche
JOIN `rischiatura` ON anagrafiche.id_ndg = rischiatura.id_ndg
WHERE id_ogg IN (SELECT x.id_soff
FROM sofferenze x
WHERE x.Descrizione IN (
SELECT ix.Descrizione
FROM sofferenze ix
GROUP BY ix.Descrizione
HAVING count( * ) >1
))
AND id_ruolo = 'RU010000002'

How to cope up with "your subquery return more than one row"

Below is my query,
select * from user_details
where
user_id in (
select sender_id from pending_friend_request_table
where receiver_id=10
)
Now I'm getting an obvious error that 'your subquery returns more than 1 row'.
My question is, is there any way through which I can make the above query to work by using some MySQL keywords/functions like range, limit, exists etc or through some other methods.
Better to go with JOINS
SELECT * FROM user_details AS u
LEFT JOIN pending_friend_request_table AS p
ON (u.user_id = p.sender_id)
WHERE p.receiver_id = 10;

Optimize query: Select in Select

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

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.