MySQL difficult query - mysql

i delete my previous question to make it easier to understand.
I'm developing a forum with this database schema:
ID:INT->PRIMARY-KEY->AUTO_INCREMENT
IDTOPIC:INT (0 if it is the "father" topic OR father's ID if it's a reply)
IDUSER:INT (ID of user who posted)
CONTENT:MEDIUMTEXT
DATE:TIMESTAMP
I need to make a query ordered by date, where to get only last thread reply (not if "i am the last user who replied", IDUSER<>$userid) or father thread topic if there are no replies.
Even topic or reply results, i need to print first thread IDTOPIC/IDUSER
My ideal results should be like this:
ID:IDTOPIC:IDUSER:CONTENT:DATE:IDLASTREPLY:IDLASTUSERREPLY:LASTCONTENTREPLY:LASTDATEREPLY
there is no problem if the last four fields are NULL.
I need the fastest query possible.
Please help me!

SELECT fa.ID, fa.IDTOPIC, fa.IDUSER, fa.CONTENT, fa.DATE,
re.ID as IDLASTREPLY,
re.IDUSER as IDLASTUSERREPLY,
re.CONTENT as LASTCONTENTREPLY,
re.DATE as LASTDATEREPLY,
CASE WHEN (re.DATE is NULL) THEN fa.DATE ELSE re.DATE END as LASTUPDATE
FROM post fa
LEFT JOIN post re ON re.ID =
(SELECT ID FROM post WHERE IDTOPIC = fa.ID ORDER BY DATE DESC LIMIT 1)
WHERE fa.IDTOPIC = 0
ORDER BY LASTUPDATE DESC
and ORDER as you like.
But it's not so good performance. I think you could improve your table structure.
EDIT: add LASTUPDATE for ORDER

Unfortunately, this select is not so good after some posts...
Today, with a total of about 900 records on the table, when i'm filtering threads by my user id (only 45 distinct threads!), MySQL needs 0.30 sec (at the begin, there were needed only 0.04/0.05 sec... the deterioration i think is proportional to how many threads i subscribed)
This could means that when i will have 300 distinct threads subscribed i should wait about 2 seconds for this query... it's too much.
The strange thing is if i "limit 0,10" this query or get full query, the execution speed does not change! This is why i think it's not good... because i suppose it have to select the whole data even if i limit.
No way to solve. This is how i setup query. There is a new notications table, because i want to use it as notification query (to show ONLY replies not made by $USERID)
NOTIFICATIONS TABLE FIELDS: id,idcontent,userid
SELECT
CASE WHEN (re.id is NULL) THEN fa.id ELSE re.id END AS id,
fa.id as idtopic,
CASE WHEN (re.userid is NULL) THEN fa.userid ELSE re.userid END AS userid,
CASE WHEN (re.content is NULL) THEN fa.content ELSE re.content END AS content,
n.notify,
u.id as reuserid, u.name, u.surname, u.photo,
CASE WHEN (re.date is NULL) THEN unix_timestamp(fa.date) ELSE unix_timestamp(re.date) END AS LASTUPDATE
FROM notifications AS n
LEFT JOIN post AS fa
ON fa.id = n.idcontent
LEFT JOIN post AS re
ON re.id=(SELECT ID FROM post WHERE IDTOPIC = fa.ID AND userid <> $USERID ORDER BY DATE DESC LIMIT 1)
LEFT JOIN users AS u
ON u.id = ( CASE WHEN (re.userid is NULL) THEN fa.userid ELSE re.userid END )
WHERE
n.userid = $USERID
AND NOT (fa.userid = $USERID AND re.userid = $USERID)

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

Doctrine2 ORM slow query optimization

I'm struggling to make a query efficient enough. I'm using Doctrine2 ORM (the query is build with QueryBuilder) and part of my query is running very slow - takes about 4s with table of 5000 rows.
This is the relevant part of db schema:
TABLE user
id (primary)
... (plenty of rows, not relevant to the query)
TABLE slot
id (primary)
user_id (foreign for user)
date (datetime)
And this is how my query looks like (it's the basic version, there's a lot of filters to be applied, but these work like fine for now)
SELECT
u.id AS uid,
COUNT(DISTINCT s_order.id) AS sclr_1,
COUNT(DISTINCT s_filter.id) AS sclr_2
FROM
user u
LEFT JOIN slot s_order ON (s_order.user_id = u.id)
LEFT JOIN slot s_filter ON (s_filter.user_id = u.id)
WHERE
(
(
(
s_order.date BETWEEN ?
AND ?
)
AND (
s_filter.date BETWEEN ?
AND ?
)
)
AND (u.deleted_at IS NULL)
)
AND u.userType IN ('2')
GROUP BY
u.id
HAVING
sclr_2 > 0
ORDER BY
sclr_1 DESC
LIMIT
12
Let me explain what I'm trying to achieve here:
I need to filter users who has any slots between 1 week ago and 1 week ahead, then order them by count of slots available between now and 1 week ahead. The part of query causing issues is LEFT JOIN of s_filter and I'm wondering whether perhaps there's a way to improve the performance of that query?
Any help appreciated really, even if it's only plain SQL I'll try to convert it to DQL myself!
#UPDATE
Just an additional info that I forgot, the LIMIT in query is for pagination purposes!
#UPDATE 2
After a while of tweaking the query I figured out that I can use JOIN for filtering instead of LEFT JOIN + COUNT, so my query does look like that now:
SELECT
u.id AS uid, COUNT(DISTINCT s_order.id) AS ordinal
FROM
langu_user u
LEFT JOIN
slot s_order ON (s_order.user_id = u.id) AND s_order.date BETWEEN '2017-02-03 14:03:22' AND '2017-02-10 14:03:22'
JOIN
slot s_filter ON (s_filter.user_id = u.id) AND s_filter.date BETWEEN '2017-01-27 14:03:22' AND '2017-02-10 14:03:22'
WHERE
u.deleted_at IS NULL
AND u.userType IN ('2')
GROUP BY u.id
ORDER BY ordinal DESC
LIMIT 12
And it went down from 4.1-4.3s to 3.6~

Select * as ALLIDS from table where user ID NOT IN (another table) while also counting how many times they occur in another table

That's possibly the worst title ever composed on StackOverflow but please hear me out:
I have a Tinder style database design:
USERS
ID, Name, etc, etc.. Not important
SWIPES
SwiperID, RecipientID, didLike
Now i'm attempting to "pull-in" a set of potential swipes for user X, which means i need to 1: SELECT ALL USER IDS THAT USER X HAS NOT SWIPED FOR, my attempt:
SELECT * FROM users WHERE ID NOT IN ( SELECT recipientID FROM swipes WHERE swiperID = ? ) AND ID != ? LIMIT 0 , 30
Which works, however my problem comes with number 2:
I need to then take these newly found, un-swiped users and COUNT how many times each of them have been "Swiped/Not Swiped" in the past and combine it with their other user data to return i.e.
COUNT(didLike) WHERE recipientID = ? AND didLike = true
But beyond doing a For loop in code that then performs that query over and over potentially hundreds of times for each ID found, how can i combine these queries?! I have been toying with queries for far too long.
Try something like this...
Join the user table with the swipe count, then do your filter.
SELECT users.*, r.likes
FROM users
INNER JOIN (
select recipientId, sum(case when didlike=true then 1 else 0 end) as likes
from swipes
group by recipientId ) r on r.recipientId = users.ID
WHERE ID NOT IN ( SELECT recipientID FROM swipes WHERE swiperID = ? ) AND ID != ? LIMIT 0 , 30
I haven't tested this at all, but it should put you in the right direction at least.

Single SQL to retrieve different information from different tables

I have this query which retrives 10 ( $limited ) queries from MySQL ,
"SELECT content.loc,content.id,content.title,
voting_count.up,voting_count.down
FROM
content,voting_count
WHERE names.id = voting_count.unique_content_id
ORDER BY content.id DESC $limit"
This query did great for posts that were allready in database and had votes , however new posts won't show.
Vote row is "inserted" first time someone votes on post. I guess that the reason why they won't be listed as there is no unique_content_id to connect to.
If i change query into this :
"SELECT content.loc,content.id,content.title
FROM
content
ORDER BY content.id DESC $limit"
it works , but i can't access voting_count.up & voting_count.down rows.
How could i access both information in single query ? Is it doable ?
If some data might not exist in one of the tables, instead of using INNER JOIN you should use LEFT JOIN:
SELECT content.loc,content.id,content.title,
-- USE function COALSESCE will show 0 if there are no
-- related records in table voting_count
COALESCE(voting_count.up, 0) as votes_up,
COALSESCE(voting_count.down, 0) as voted_down
FROM content LEFT JOIN voting_count
ON content.id = voting_count.unique_content_id
ORDER BY content.id DESC
As someone else above mentioned, what is names.id? However, perhaps the following might be of use assuming the join should have been from content.id to voting_count.unique_content_id:
$sql="select
c.`loc`,c.`id`, c.`title`,
case
when v.`up` is null then
0
else
v.`up`
end as 'up',
case
when v.`down` is null then
0
else
v.`down`
end as 'down'
from `content` c
left outer join `voting_count` v on v.`unique_content_id`=c.`id`
order by c.`id` desc {$limit}";

SQL query in WHERE condition

Is it good if i write query like this:- (see query in where condition)
SELECT distinct(id) "idea_id"
FROM ideas
WHERE deleted_by_user = 0 AND moderation_flag = 1 AND
user_id in (select id
from users
where confirm like "yes")
ORDER BY time_of_creation DESC
let me know if there is some issue in this query :
thanx in advance..
You can wirte this query in two ways:
SELECT DISTINCT(i.id) "idea_id"
FROM ideas i
INNER JOIN users u ON i.user_id = u.id
WHERE i.deleted_by_user = 0 AND i.moderation_flag = 1 AND u.confirm = 'yes'
ORDER BY i.time_of_creation DESC;
And
SELECT DISTINCT(i.id) "idea_id"
FROM ideas i
WHERE i.deleted_by_user = 0 AND i.moderation_flag = 1 AND
EXISTS (SELECT * FROM users u WHERE i.user_id = u.id AND u.confirm = 'yes')
ORDER BY i.time_of_creation DESC;
SELECT distinct a.ID idea_id
FROM ideas a
INNER JOIN users b
ON a.user_id = b.id
WHERE a.deleted_by_user = 0 AND
a.moderation_flag = 1
b.confirm = 'YES'
ORDER BY time_of_creation DESC
To answer your question - there are no problems with using subqueries.
On the other hand, you have (at least) three different things to think about when writing a query in one way or another:
How efficient will the data base run my query? (If the data base is small, this may not matter at all)
How easy is this to formulate and write? - which often connects to
How easy is this to understand for someone else who reads my code? (and I may myself count as "somebody else" if I look into code I've written a year ago...)
If you have a database of a size where efficiency counts, the best way to select how to formulate a query is normally to write it in different ways and test it on the data base. (but often the query optimizer in the data base is so good, it does not matter)
SELECT distinct i.id "idea_id"
FROM ideas i join users u
on i.user_id=u.id and u.confirm ='yes'
WHERE i.deleted_by_user = 0
AND i.moderation_flag = 1
ORDER BY i.time_of_creation DESC