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

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.

Related

Get non zero ids count without where clause

I wont to get non zero funnel_id count. i get funnel_id count but is also show count of funnel_id is zero and here we do not add where clause here i also get page_count in this query.
SELECT `smart_projects`.project_id, `smart_projects`.business_id, `smart_projects`.title,
`page_pages`.`funnel_id` as `funnel_id`, count(distinct(page_pages.page_id) )as page_count, count(distinct(page_pages.funnel_id) )as funnel_count
FROM `smart_projects`
LEFT JOIN `page_pages` ON `smart_projects`.`project_id` = `page_pages`.`project_id`
WHERE smart_projects.status != 0
AND `smart_projects`.`business_id` = 'cd9412774edb11e9'
AND `smart_projects`.`created_date` BETWEEN 1558031400 AND 1558722600
GROUP BY `smart_projects`.`project_id`
ORDER BY `funnel_count` ASC
LIMIT 10
page_pages table is :
smart_projects table is :
result is :-
Expected Result is :
SELECT `smart_projects`.project_id, `smart_projects`.business_id, `smart_projects`.title,
`page_pages`.`funnel_id` as `funnel_id`, count(distinct(page_pages.page_id) )as page_count, count(distinct (CASE WHEN page_pages.funnel_id != 0 then page_pages.funnel_id ELSE NULL END ) ) as funnel_count
FROM `smart_projects`
LEFT JOIN `page_pages` ON `smart_projects`.`project_id` = `page_pages`.`project_id`
WHERE smart_projects.status != 0
AND `smart_projects`.`business_id` = 'cd9412774edb11e9'
GROUP BY `smart_projects`.`project_id`
ORDER BY `title` DESC
If you want to filter out zero values, using a having clause:
SELECT sp.project_id, sp.business_id, sp.title,
count(distinct pp.page_id ) as page_count,
count(distinct pp.funnel_id ) as funnel_count
FROM `smart_projects` sp LEFT JOIN
`page_pages` pp
ON sp.`project_id` = pp.`project_id`
WHERE sp.status <> 0 AND
sp.`business_id` = 'cd9412774edb11e9' AND
sp.`created_date` BETWEEN 1558031400 AND 1558722600
GROUP BY sp.`project_id`
HAVING funnel_count > 0
ORDER BY `funnel_count` ASC
LIMIT 10;
Notes:
Table aliases make the query easier to write and to read.
DISTINCT is not a function, it is a keyword. The following expression does not need parentheses.
funnel_id is not appropriate in the SELECT, because it is the argument to an aggregation function.

Group and order by a column but donot include that column in results

I've been trying to figure out how I can modify this query so that the result set does not include the numHits. I want the same results in the same order, just not have the numHits included.
SELECT
`newel_inventoryKeywordIdDictionaryId`.`inventoryId`
,COUNT(`newel_inventoryKeywordIdDictionaryId`.`inventoryId`) as numHits
FROM
`newel_inventoryKeywordIdDictionaryId`
, `newel_inventoryDictionary`
WHERE
`newel_inventoryKeywordIdDictionaryId`.`dicId` = `newel_inventoryDictionary`.`dicId`
AND (
`newel_inventoryDictionary`.`word` = 'alabaster' OR `newel_inventoryDictionary`.`word` = 'chess'
)
GROUP BY inventoryId
ORDER BY numHits DESC;
sample results:
inventoryId, numHits
6928, 2
6929, 2
6924, 2
6925, 2
13772, 2
6926, 2
18203, 1
6931, 1
13863, 1
18402, 1
Desired Results:
inventoryId
6928
6929
6924
6925
13772
6926
18203
6931
13863
18402
Move the column from SELECT clause to ORDER BY clause:
SELECT
`newel_inventoryKeywordIdDictionaryId`.`inventoryId`
FROM
`newel_inventoryKeywordIdDictionaryId`
, `newel_inventoryDictionary`
WHERE
`newel_inventoryKeywordIdDictionaryId`.`dicId` = `newel_inventoryDictionary`.`dicId`
AND (
`newel_inventoryDictionary`.`word` = 'alabaster' OR `newel_inventoryDictionary`.`word` = 'chess'
)
GROUP BY inventoryId
ORDER BY COUNT(`newel_inventoryKeywordIdDictionaryId`.`inventoryId`) DESC;
SELECT
`newel_inventoryKeywordIdDictionaryId`.`inventoryId`
FROM
`newel_inventoryKeywordIdDictionaryId`
, `newel_inventoryDictionary`
WHERE
`newel_inventoryKeywordIdDictionaryId`.`dicId` = `newel_inventoryDictionary`.`dicId`
AND (
`newel_inventoryDictionary`.`word` = 'alabaster' OR `newel_inventoryDictionary`.`word` = 'chess'
)
GROUP BY inventoryId
ORDER BY COUNT(`newel_inventoryKeywordIdDictionaryId`.`inventoryId`) DESC;
You just need to put the aggregation in the ORDER BY. However, you should also:
Use explicit join syntax. Never use commas in the from clause.
Use table aliases. They make queries easier to write and to read.
Use in instead of a bunch of or statements.
Here is an improved version of the query:
SELECT kdi.inventoryId
FROM newel_inventoryKeywordIdDictionaryId kdi JOIN
newel_inventoryDictionary id
ON kdi.dicId = id.dictId
WHERE id.word IN ('alabaster', 'chess')
GROUP BY kdi.inventoryId
ORDER BY COUNT(*) DESC;

Order by with mathematical formula in 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

MySQL HAVING not behaving properly

I have a query:
SELECT I.Id
, CAST(SUBSTRING_INDEX(GROUP_CONCAT(I.StatusId ORDER BY I.TransactionId DESC), ',', 1) AS UNSIGNED) AS StatusId
, SUM(I.RefundAmount) AS RefundAmount
FROM (
SELECT I.Id
, IT.Id AS TransactionId
, IT.StatusId
, IF(IT.TypeId = 2, IT.RefundAmount, 0) AS RefundAmount
FROM Items I
INNER JOIN ItemTransactions IT ON IT.ItemId = I.Id
WHERE I.Id = someValue
) I
GROUP BY I.Id
HAVING StatusId = 1 AND RefundAmount = 0
Where Items table has transaction records stored in ItemTransactions table. I've been using this type of query and works for me until this time, got some issues with the having clause.
The query works in SQL Editors but not working properly when used on stored procedures. (Don't get me wrong, I've been using this query for most of my stored procedures). Debugging line per line, found that there is an issue with having clause.
As a temporary fix, I changed the query to:
SELECT I.Id
, I.StatusId
, I.RefundAmount
FROM (
SELECT I.Id
, CAST(SUBSTRING_INDEX(GROUP_CONCAT(I.StatusId ORDER BY I.TransactionId DESC), ',', 1) AS UNSIGNED) AS StatusId
, SUM(I.RefundAmount) AS RefundAmount
FROM (
SELECT I.Id
, IT.Id AS TransactionId
, IT.StatusId
, IF(IT.TypeId = 2, IT.RefundAmount, 0) AS RefundAmount
FROM Items I
INNER JOIN ItemTransactions IT ON IT.ItemId = I.Id
WHERE I.Id = someValue
) I
GROUP BY I.Id
--HAVING StatusId = 1 AND RefundAmount = 0
) I
WHERE I.StatusId = 1 AND I.RefundAmount = 0
The query works fine. But I'd like to know if somebody has already encountered this, and found a fix. I'm using MySQL 5.0.
Thanks
WHERE clause is used to filter data on the common attributes and expressions. HAVING clause is used to filter data after groupping had been performed and it's arguments should be either the ones of the GROUP BY clause, or expressions containing aggregate functions.
It is also illegal to use the alias of the column in WHERE, GROUP BY or HAVING clauses, while it does works for the ORDER BY clause.
One option, as you've found, is to use a sub-query and then column references will work.
Another one is to duplicate the whole expression in the HAVING clause:
SELECT I.Id,
CAST(SUBSTRING_INDEX(GROUP_CONCAT(I.StatusId ORDER BY I.TransactionId DESC),
',', 1) AS UNSIGNED) AS StatusId,
SUM(I.RefundAmount) AS RefundAmount
FROM (
SELECT I.Id
, IT.Id AS TransactionId
, IT.StatusId
, IF(IT.TypeId = 2, IT.RefundAmount, 0) AS RefundAmount
FROM Items I
INNER JOIN ItemTransactions IT ON IT.ItemId = I.Id
WHERE I.Id = someValue
) I
GROUP BY I.Id
HAVING
CAST(SUBSTRING_INDEX(GROUP_CONCAT(I.StatusId ORDER BY I.TransactionId DESC),
',', 1) AS UNSIGNED) = 1
AND SUM(I.RefundAmount) = 0;
EDIT: A closer look leaded to this question Using column alias in WHERE clause of MySQL query produces an error and to the MySQL documentation, that outlines that it is possible to use column aliases in GROUP BY, HAVING and ORDER BY clauses if such aliases are quoted with backticks, like:
...
HAVING `StatusId` = 1 AND `RefundAmount` = 0

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.