Order by with mathematical formula in mysql - mysql

I want to display users' posts on index page with most popular.
And I am calculating it with (likes+views-dislikes)/(today's date-posteddate)
Can I pass mathematical formula in order by clause in mysql?
EDIT:
ok here is the query which I am using right now:
SELECT Posts.PostId, Posts.PostTitle, Posts.TextContent,Posts.PostType, PostedAs, CONCAT(PostedDate,' ',PostedTime) AS Date,
COUNT(PostLikes.PostId) AS Likes, COUNT(PostViews.PostId) AS Views,
(CASE Posts.PostType
WHEN 'media'
THEN (SELECT MediaContent FROM PostsMedia WHERE PostsMedia.PostId = Posts.PostId GROUP BY PostsMedia.PostId)
ELSE
'non-media'
END
) AS MediaContent,
(CASE Posts.PostType
WHEN 'media'
THEN (SELECT MediaType FROM PostsMedia WHERE PostsMedia.PostId = Posts.PostId GROUP BY PostsMedia.PostId)
ELSE
'non-media'
END
) AS MediaType
FROM Posts, PostLikes, PostViews
WHERE Posts.PostId = PostLikes.PostId AND PostLikes.Liked = 1 AND Posts.Classification <> 'sales_related' AND (LOWER(Posts.PostType) != 'text' AND LOWER(Posts.PostType) != 'shout')
AND Posts.Filter<>'HOME' GROUP BY PostLikes.PostId, PostViews.PostId ORDER BY Likes DESC, Views DESC $limitClause

Yes. You didn't provide any real code, but to approximate it:
SELECT (likes+views-dislikes)/(today's date-posteddate) AS popularity
FROM thetable
ORDER BY popularity DESC
or
SELECT item1, item2
FROM thetable
ORDER BY (likes+views-dislikes)/(today's date-posteddate) DESC

Related

How to select main item row from table using CASE WHERE conditions?

I have an array of products and I need to select the main photo of each product. My photo table consists of columns: id, product_id, photoname, flag, order.
'flag' = 1 when is the main photo and 'order' is the ordering of photos. Sometimes the 'flag' and 'order'are 0 (not set), so I need to assume the main photo is its 'id' ASC.
In my MySQL code I have:
private function getProducts($idUser) {
$products = DB::connection('mysql2')->select("SELECT
a.*
, (CASE WHEN photos.flag = 0 THEN
(
SELECT photoname
FROM mpy_product_photos
WHERE flag = 1 AND product_id = a.id LIMIT 1
)
ELSE photos.photoname END
) AS photoname
FROM mpy_products AS `a`
LEFT JOIN mpy_product_photos AS `photos` ON (photos.product_id = a.id)
WHERE a.user_id = '$idUsuario'
GROUP BY a.id
ORDER BY a.date DESC");
return $products;
}
When 'flag' = 0 for all 'photoname' from the same 'product_id', the return is null.
I've tried adding the following, along with many things but always get a Syntax error.
, (CASE WHEN photos.flag = 0 THEN
(
SELECT photoname
FROM mpy_product_photos
WHERE flag = 1 AND product_id = a.id LIMIT 1
)
ELSE
(
SELECT photoname
FROM mpy_product_photos
WHERE product_id = a.id ORDER BY photos.id LIMIT 1 END
)
) AS photoname
So when I don't have 'flag' = 1, I need to select and order by 'order' ASC. If 'order' = 0 in all rows, I need to select by 'id' ASC.
I've also tried the following, and at least I get a 'photoname' randomly (I guess) selected instead of null when all 'flag' = 0.
, (CASE WHEN photos.flag = 0 THEN
(
SELECT photoname
FROM mpy_product_photos
WHERE product_id = a.id ORDER BY photos.flag DESC LIMIT 1
)
ELSE photos.photoname END
) AS photoname
How can I solve this?
UPDATE: You specifically ask for a solution with CASE WHEN. In the meantime, I provide alternatives.
For MySQL 5.5+
select
a.*,
(select
photoname
from my_product_photos
where product_id = a.id
order by flag desc, ord asc, id asc
limit 1) as photoname
from my_product a;
If you're using MySQL 8.0, you can achieve this with the RANK window function rather simply as well
SELECT
product.*,
photo.photoname
FROM my_product product
JOIN (
SELECT
*,
RANK() OVER(PARTITION BY product_id ORDER BY flag DESC, ord ASC, id ASC) AS r
FROM my_product_photos
) photo ON photo.product_id = product.id AND photo.r = 1;
The RANK Function, as it's name implies, ranks the rows according to the given "rules". In the query, the rules are: Rank those with the higher flag, the lower order and lower id of the same product_id. This is going to be as a column of alias r. Then, you can just INNER JOIN as usual. With r you can then filter out the ones "less likely to be what you want".
This is a DB Fiddle with sample data. This might not match what you have in your own DB, so adjust as necessary.

Using mysql to find common set between two lists

I have two queries in which I would like to find their common values. I'm trying to ultimately find out what percentage of users have visited both webpages.
SELECT DISTINCT user_id
FROM table
WHERE url ='y'
ORDER BY user_id;
SELECT DISTINCT user_id
FROM table
WHERE url ='z'
ORDER BY user_id;
I've tried a
NOT IN
and a
UNION
but haven't had much luck - though I could easily be doing it wrong. I'm new.
One method is to use conditional aggregation. To get information for each user:
select user_id,
sum(url = 'y') as y_visits,
sum(url = 'z') as z_visits
from t
group by user_id;
To get the list of users, add a having clause:
having y_visits >= 1 and z_visits >- 1
To get summary information:
select y_visitor, z_visitor, count(*)
from (select user_id,
max(url = 'y') as y_visitor,
max(url = 'z') as z_visitor
from t
group by user_id
) yz
group by y_visitor, z_visitor;
To get a simple percentage:
select avg(y_visitor = 1 and z_visitor = 1) as p_VisitedBothYandZ
from (select user_id,
max(url = 'y') as y_visitor,
max(url = 'z') as z_visitor
from t
group by url
) yz;

ORDER BY before GROUP BY

I have a page showing a list of persons with whom you have been chatting to. Currently it's showing the persons you have been chatting to, and the OLDEST message. However, it should show the NEWEST message. This is the query:
SELECT * FROM post
WHERE fk_user_to = '$userid'
GROUP BY fk_user_from
ORDER BY datotime DESC
and the table structure
post_id || fk_user_to || fk_user_from || message || datotime
This worked out:
SELECT * FROM
(SELECT * FROM post ORDER BY datotime DESC) as inv
WHERE fk_user_to = '$userid'
GROUP BY fk_user_from
ORDER BY datotime DESC
There is no simple solution with plain SQL I think. If your database system allows subqueries, you got do a subquery like
SELECT fk_user_from AS id, MAX(dateotime) AS newest_timestamp FROM post WHERE fk_user_to = '$userid'
and then select from post again using the two fields as key.
In MySQL that could be something like:
SELECT * FROM post P1
JOIN (
SELECT fk_user_from, MAX(dateotime) AS newest_dateotime
FROM post
WHERE fk_user_to = '$userid'
GROUP BY fk_user_from
) AS P2
ON P1.fk_user_from=P2.fk_user_from
AND P1.datotime =P2.newest_dateotime
WHERE P1.fk_user_to = '$userid'
It doesn't really work like that. You can never be sure what row GROUP BY selects, so putting an ORDER BY on it will have unpredictable output. Also you cannot put an order by BEFORE a GROUP BY.
Try this:
SELECT *,MAX(datotime) AS dt fk_user_from
FROM post
WHERE fk_user_to = '$userid'
GROUP BY fk_user_from
ORDER BY dt

MySQL sort grouped data

I have tried to program a inbox that display messages in the order they were received and then by if they have been read or not, it seemed to work for a while, but not it doesn't. It may have only worked under certain circumstances maybe..
Anyway here is my query;
SELECT `id`, `from_userid`, `read`, max(sent) AS sent
FROM (`who_messages`)
WHERE `to_userid` = '41'
GROUP BY `from_userid`
ORDER BY `read` ASC, `sent` DESC
I believe the problem is that the messages are being grouped in the wrong order.. as the inbox is always showing as read, when new messages exist. I get the right time of the new messages, but I am guessing this because I selected max(sent).
Is my logic wrong? or can I sort and then group as all my efforts have resulted in 'Every derived table must have its own alias'
Setup an SQL Fiddle - here's the best I came up with. Basically I do the ordering first in a sub-query then group them afterwards. That seemed to work with the (limited) test data I entered.
SELECT *
FROM (SELECT id, from_userid, is_read, sent
FROM who_messages
WHERE to_userid = 41
ORDER BY from_userid ASC, is_read ASC) m
GROUP BY m.from_userid
ORDER BY m.is_read ASC, m.sent DESC
See the fiddle to play around: http://sqlfiddle.com/#!2/4f63d/8
You are selecting non-grouping fields in a grouped query. It is not guaranteed which record of the group will be returned, and ORDER BY is processed after GROUP BY.
Try this:
SELECT m.*
FROM (
SELECT DISTINCT from_userid
FROM who_messages
WHERE to_userid = 41
) md
JOIN who_messages m
ON m.id =
(
SELECT mi.id
FROM who_message mi
WHERE (mi.to_userid, mi.from_userid) = (41, md.from_userid)
ORDER BY
mi.sent DESC, mi.id DESC
LIMIT 1
)
Create an index on who_message (to_userid, from_userid, sent, id) for this to work fast.
Update
The above query will return the record for the last message from any given user (including its read status). If you want to check that you have any unread messages from the user, use this:
SELECT m.*, md.all_read
FROM (
SELECT from_userid, MIN(read) AS all_read
FROM who_messages
WHERE to_userid = 41
GROUP BY
from_userid
) md
JOIN who_messages m
ON m.id =
(
SELECT mi.id
FROM who_message mi
WHERE (mi.to_userid, mi.from_userid) = (41, md.from_userid)
ORDER BY
mi.sent DESC, mi.id DESC
LIMIT 1
)
For this to work fast, create an index on who_message (to_userid, from_userid, read) (in addition to the previous index).
As Quassnoi said, you are using a GROUP BY query and ordering on 'read' which is not an aggregate function. Therefore you can't be certain of the value used by the MySQL engine (usually the last of the group but...)
I would suggest writing your query this way, as it doesn't involve any subquery and has some many other performance-friendly usage:
SELECT
from_userid,
COUNT(*) AS nb_messages,
SUM(NOT is_read) AS nb_unread_messages,
MAX(sent) AS last_sent
FROM who_messages
WHERE to_userid = 41
GROUP BY from_userid
ORDER BY nb_unread_messages DESC, last_sent DESC;
(I used Andy Jones' fiddle schema: http://sqlfiddle.com/#!2/4f63d/8.
By the way, many thanks Andy, this site is great !)
Hope this help !
"inbox that display messages in the order they were received and then by if they have been read or not ... however it is suppose to be the latest message" - assumes read is a nullable date/time column, and messages are stored in the order they are sent (newer have larger id than older - autoid)
SELECT wm.id, wm.from_userid, (wm.read IS NULL) as unread, wm.sent
FROM (SELECT MAX(id) AS id FROM who_messages WHERE to_userid = '41' GROUP BY from_userid) sub
INNER JOIN who_messages wm ON sub.id = wm.id
ORDER BY wm.sent DESC, wm.read

How do I get more than one column from a SELECT subquery?

Here is my problem :
I have 3 tables : account, account_event and account_subscription
account contains details like : company_name, email, phone, ...
account_event contains following events : incoming calls, outgoing calls, visit, mail
I use account_subscription in this query to retrieve the "prospects" accounts. If the account does not have a subscription, it is a prospect.
What I am using right now is the following query, which is working fine :
SELECT `account`.*,
(SELECT event_date
FROM clients.account_event cae
WHERE cae.account_id = account.id
AND cae.event_type = 'visit'
AND cae.event_done = 'Y'
ORDER BY event_date DESC
LIMIT 1) last_visit_date
FROM (`clients`.`account`)
WHERE (SELECT count(*)
FROM clients.account_subscription cas
WHERE cas.account_id = account.id) = 0
ORDER BY `last_visit_date` DESC
You can see that it returns the last_visit_date.
I would like to modify my query to return the last event details (last contact). I need the event_date AND the event_type.
So I tried the following query which is NOT working because apparently I can't get more than one column from my select subquery.
SELECT `account`.*,
(SELECT event_date last_contact_date, event_type last_contact_type
FROM clients.account_event cae
WHERE cae.account_id = account.id
AND cae.event_done = 'Y'
ORDER BY event_date DESC
LIMIT 1)
FROM (`clients`.`account`)
WHERE (SELECT count(*)
FROM clients.account_subscription cas
WHERE cas.account_id = account.id) = 0
ORDER BY `last_visit_date` DESC
I tried a lot of solutions around joins but my problem is that I need to get the last event for each account.
Any ideas?
Thank you in advance.
Jerome
Get a PRIMARY KEY in a subquery and join the actual table on it:
SELECT a.*, ae.*
FROM account a
JOIN account_event ae
ON ae.id =
(
SELECT id
FROM account_event aei
WHERE aei.account_id = a.id
AND aei.event_done = 'Y'
ORDER BY
event_date DESC
LIMIT 1
)
WHERE a.id NOT IN
(
SELECT account_id
FROM account_subscription
)
ORDER BY
last_visit_date DESC
Try moving the subquery to from part and alias it; it will look as just another table and you'll be able to extract more than one column from it.