Improving Efficiency of my SQL - mysql

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>

Related

SQL: How to merge two complex queries into one, where the second one needs data from the first one

The goal is to load a list of chats where the user sending the request is a member in. Some of the chats are group chats (more than two members) and there I want to show the profile pictures from the users who wrote the last three messages.
The first query to load meta data like the title and the timestamp of the chat is:
SELECT Chat_Users.ID_Chat, Chats.title, Chats.lastMessageAt
FROM Chat_Users
JOIN Chats ON Chats.ID = Chat_Users.ID_Chat
GROUP BY Chat_Users.ID_Chat
HAVING COUNT(Chat_Users.ID_Chat) = 2
AND MAX(Chat_Users.ID_User = $userID) > 0
ORDER BY Chats.lastMessageAt DESC
LIMIT 20
The query to load the last three profile pictures from one of the chats loaded with the query above is:
SELECT GROUP_CONCAT(innerTable.profilePictures SEPARATOR ', ') AS 'ppUrls',
innerTable.ID_Chat
FROM
(
SELECT Chat_Users.ID_Chat, Users.profilePictureUrl AS profilePictures
FROM Users
JOIN Chat_Users ON Chat_Users.ID_User = Users.ID
JOIN Chat_Messages ON Chat_Messages.ID_Chat = Chat_Users.ID_Chat
WHERE Chat_Users.ID_Chat = $chatID
ORDER BY Chat_Messages.timestamp DESC
LIMIT 3
) innerTable
GROUP BY innerTable.ID_Chat
Both are working separately but I want to merge them together so I don't have to run the second query in a loop due to performance reasons. Unfortunately I have no idea how this can be achieved because the second query needs the $chatID, which it only gets from the first query.
So to clarify the desired result: The list with the profile picture urls (second query) should be just another column in the result of the first query.
I hope it is explained in a reasonably understandable way. Any help would be much appreciated.
Edit: Sample data from the affected tables:
Table "Chats":
Table "Chat_Users":
Table "Chat_Messages":
Table "Users":
This fufils the brief, however it requires a view because MySQL 5.x doesn't support the WITH clause.
It's long and cluncky and I've tried to shorten it but this is as good as I can get, hopefully someone will pop up in the comments with a way to make it shorter!
The view:
CREATE VIEW last_interaction AS
SELECT
id_chat,
id_user,
MAX(timestamp) AS timestamp
FROM chat_messages
GROUP BY id_user, id_chat
The query:
SELECT
Chat_Users.ID_Chat,
Chats.title,
Chats.lastMessageAt,
urls.pps AS profilePictureUrls
FROM Chat_Users
JOIN Chats ON Chats.ID = Chat_Users.ID_Chat
JOIN (
SELECT
lo.id_chat,
GROUP_CONCAT(users.profilePictureUrl) AS pps
FROM last_interaction lo
JOIN users ON users.id = lo.id_user
WHERE (
SELECT COUNT(*) -- the amount of more recent interactions
FROM last_interaction li
WHERE (li.timestamp = lo.timestamp AND li.id_user > lo.id_user)
) < 3
GROUP BY id_chat
) urls ON urls.id_chat = Chats.id
GROUP BY Chat_Users.ID_Chat
HAVING COUNT(Chat_Users.ID_Chat) > 2
AND MAX(Chat_Users.ID_User = $userID)
ORDER BY Chats.lastMessageAt DESC
LIMIT 20

MySql Select data from SubQuery when master query has groupby clause

I have two tables in MySql db.
1) networks (NetworkId, NetworkType)
2) users (Id, NetworkId, IpAddress)
Using [NetworkId and IpAddress] defines unique users.
Now, I want to use group by clause on NetworkType and at the same time want to list count of all users as like below:
SELECT (SELECT Count(distinct IpAddress) FROM users
WHERE NetworkId in nr.NetworkId ) as UsersCount
FROM networks as nr
GROUP BY NetworkType;
But due to any reason I always gets zero users.
When I run following queries
SELECT GROUP_CONCATE(nr.NetworkId)
FROM networks as nr
GROUP BY NetworkType;
Then I am getting valid values with ',' separated.
Thanks in advance.
Updated per your new information about what you needed with the group by.
How about an upvote for my efforts at least.
Latest SQLFIDDLE
select
mysub.count,
nr.`networkid`
from
`networks` as nr,
(select
count(`ipaddress`) as count,
`networkid`
from
`users`
where
`networkid`
in (
select
`networkid`
from
`networks`
)
group by
`networkid`) as mysub
where nr.`networkid` = mysub.`networkid`
group by nr.`networkid`
Something like this perhaps:
select n.networktype, count(distinct u.ipaddress)
from networks n
join users u on n.networkid = u.networkid
group by n.networktype

How to combine 2 mysql queries

I have the following 2 queries.
Query 1 :
select distinct(thread_id) from records where client_name='MyClient'
Query 2 :
select max(thread_no) from records
where thread_id='loop_result_from_above_query' AND action='Reviewed'
Is it possible to combine them into a single query ?
The second query is run on every result of the first query.
Thank you.
See attached image of a small snippet of mysql records.
I need a single mysql query to output only records which have action="MyAction" as the latest records for a given set of thread_ids. In the sample data set : record with Sr: 7201
I hope this helps in helping me :)
SELECT client_name, thread_id, MAX(thread_no) max_thread
FROM records
WHERE action='Reviewed' AND client_name='MyClient'
GROUP BY client_name, thread_id
UPDATE 1
SELECT a.*
FROM records a
INNER JOIN
(
SELECT thread_id, max(sr) max_sr
FROM records
GROUP BY thread_id
) b ON a.thread_id = b.thread_id AND
a.sr = b.max_sr
WHERE a.action = 'MyAction'
You can use SELF JOIN, but it is not advisable and will impact your query performance. Please check below query for your reference
SELECT DISTINCT r1.thread_id, MAX(r2.thread_no) from records r1 LEFT JOIN records r2 ON r2.thread_id=r1.thread_id WHERE r1.client_name='MyClient' AND r2.action='Reviewed'
SELECT a.maxthreadid,
b.maxthreadno
FROM (SELECT DISTINCT( thread_id ) AS MaxThreadId
FROM records
WHERE client_name = 'MyClient') a
CROSS JOIN (SELECT Max(thread_no) AS MaxThreadNo
FROM records
WHERE thread_id = 'loop_result_from_above_query'
AND action = 'Reviewed') b
Try this.
SELECT *
FROM (SELECT Row_number()
OVER (
partition BY thread_id
ORDER BY thread_no) no,
Max(thread_no)
OVER(
partition BY thread_id ) Maxthread_no,
thread_id,
action,
client_name
FROM records
Where client_name = 'MyClient') AS T1
WHERE no = 1
AND action = 'Reviewed'

How can I pull out only records that have FAIL against them?

I'm sure this will be quite simple for some one clued up in SQL but I think it needs a sub query or something. I have a table which basically has a load of order numbers in it and a reply column from an XML API. Either FAIL or SUCCESS.
A brand new row is inserted into the DB after every request. So there may be 5 FAILS for one order number, and on the 6th attempt a record is inserted saying SUCCESS.
How can I put out order numbers that ONLY have a FAIL status next to them?
This will allow me to figure out what records need looking into that continuously fail in the API request.
Try this, by grouping your orders with primary key (order_id)
SELECT * FROM
(
SELECT GROUP_CONCAT(status) as status_combined, order_id
FROM orders
GROUP BY order_id
) AS order_tmp
WHERE status_combined NOT LIKE '%SUCCESS%'
Edit (As per asker comments)
SELECT * FROM
(
SELECT GROUP_CONCAT(status) as status_combined, order_id
FROM orders
JOIN certificates ON certificates.Ordernumber = orders.OrderNumber
GROUP BY order_id
) AS order_tmp
WHERE status_combined NOT LIKE '%SUCCESS%'
please make sure you need to join based on "Ordernumber" or "order_id"
Try this
select m.*
from Main m
join Transactiontable tt
on m.orderid = tt.orderid
group by tt.status , m.orderid
having count(case when tt.status = "failed") = count(tt.status)
You can use simple sql query using a where clause:
select *
from some_table
where Column_From_some_table_has_value = your_particular_value
thats enough.
You can have a look at How to use where clause in sql
Thanks
This is probably the cleanest way to do it:
select *
from mytable
where id in (
select id
from mytable
group by id
having sum(status = 'SUCCESS') = 0)
I'm not a fan of #Minesh's answer because it uses both an aggregate function and the LIKE operator. Both of those can cause performance issues since there won't be any indexes to help the query out with the difficult part of the work. The LIKE clause particularly is a lot of work for the database since it will need to scan every result.
I'm more familiar with SQL Server, but this should work well for you:
SELECT *
FROM Orders
WHERE OrderNumber NOT IN (
SELECT OrderNumber
FROM Orders
WHERE Status = 'SUCCESS')
AND OrderNumber NOT IN (
SELECT OrderNumber
FROM Certificates
WHERE OrderStatus = 'CANCELLED')

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