Selection of user's vote column based on ID - mysql

In my public voting database, I have pages and pages_votes tables with structure below:-
Page
Page Votes
i am querying mysql database to get results for all pages with their total sum of positive and negative votes
positive vote is 1, and negative vote is 0.
my query is as below:-
SELECT pages.id, pages.title, COUNT(pv.page_id) AS `total_count`, SUM(pv.vote=1) AS `likes`, SUM(pv.vote=0) AS `dislikes`
FROM `pages`
LEFT JOIN `pages_votes` AS `pv` ON `pages`.`id` = `pv`.`page_id`
GROUP BY `pages`.`id`, `pages`.`title`, `pages`.`slug`, `pages`.`image`
ORDER BY `total_count` DESC;
Results look like this (no issue here):-
Now I want to include a new custom column in this result called 'my_vote', my vote will show me my votes (user_id = 3) as 1 or 0 for like/dislikes if i have voted, and NULL if i have not voted.
There is already a user_id in page_votes table recording which user voted. How do I use that to get votes of a specific user with say ID = 10?

I suggest writing your query with table aliases. Then the answer to your question is a CASE expression:
SELECT p.id, p.title, COUNT(pv.page_id) AS total_count,
SUM(pv.vote =1 ) AS likes, SUM(pv.vote = 0) AS dislikes,
SUM(CASE WHEN pv.vote = 1 AND p.user_id = 3 THEN 1
WHEN pv.vote = 0 AND p.user_id = 3 THEN 0
END) as user_3_vote
FROM pages p LEFT JOIN
pages_votes pv
ON p.id = pv.page_id
GROUP BY p.id, p.title
ORDER BY total_count DESC;
Note that this uses SUM(CASE . . .) rather than SUM( <boolean expression> ). This is important so you can get a NULL value when there is no vote.

will you consider a small change here in your query? to get specified output.
SELECT pages.id, pages.title, COUNT(pv.page_id) AS `total_count`,
SUM(case when pv.vote = 1 then 1 else 0 end) AS `likes`,
SUM(case when pv.vote = 0 then 1 else 0 end) AS `dislikes`
FROM `pages`
LEFT JOIN `pages_votes` AS `pv`
ON `pages`.`id` = `pv`.`page_id`
GROUP BY `pages`.`id`, `pages`.`title`, `pages`.`slug`, `pages`.`image`
ORDER BY `total_count` DESC;

Related

SQL query for an unpopular product

Trying to calculate the most unpopular product in MySQL 5.7, I run the following query:
SELECT
ClientOrderItem.ItemId AS ID_product,
countIf(Orders.ClientOrderStateID = 3) AS cnt
FROM
ClientOrderItem
LEFT JOIN Orders ON ClientOrderItem.ClientOrderID = Orders.id
LEFT JOIN AdditionalInfo ON ClientOrderItem.ClientOrderID = AdditionalInfo.ClientOrderID
WHERE
Orders.ClientOrderStateID = 3
AND (
AdditionalInfo.code = 'IsTestOrder'
AND AdditionalInfo.value = '0'
)
GROUP BY cnt
ORDER BY cnt DESC;
I get the following error:
SQL Error [1305] [42000]: FUNCTION ozon.countIf does not exist
Can you tell me what to replace the "countIf" function with?
Thanks.
Your question does not explain what you mean by "most unpopular". However, in MySQL, if you want to count the rows where Orders.ClientOrderStateID = 3, then you can use
sum(Orders.ClientOrderStateID = 3) AS cnt
The more standard syntax is:
sum(case when Orders.ClientOrderStateID = 3 then 1 else 0 end) as cnt
as mentioned in the comment mysql doesn;t support 'countif'
you can follow this syntax:
SELECT
ClientOrderItem.ItemId AS ID_product,
count(IF Orders.ClientOrderStateID = 3,1,NULL) AS cnt
FROM
...
OR
SELECT
ClientOrderItem.ItemId AS ID_product,
count(case when Orders.ClientOrderStateID = 3 then 1 end) AS cnt
FROM
...

MySQL - Joining same table twice, parent sum column value getting duplicated

I'm just 3 months of experience in MySQL. Here I'm trying to generate a report based on log and part table. When trying to join the "Part" table twice, the "Log" table Quantity gets doubled. Kindly let me know where I'm doing wrong.
Log Table
Part Table
Expected Report
Query Used
SELECT
report.*,
(SUM(report.quantity)) AS totalQuantity,
normalPart.price AS normalPrice,
premiumPart.price AS premiumPrice
FROM
log AS report
LEFT JOIN
(SELECT
*
FROM
part AS normalPart
WHERE
normalPart.type = 'normal'
GROUP BY normalPart.partNumber , normalPart.genId) AS normalPart ON report.partNumber = normalPart.partNumber
AND report.genId = normalPart.genId
AND normalPart.cat = report.fromCat
LEFT JOIN
(SELECT
*
FROM
part AS premiumPart
WHERE
premiumPart.type = 'premium'
GROUP BY premiumPart.partNumber , premiumPart.genId) AS premiumPart ON report.partNumber = premiumPart.partNumber
AND report.genId = premiumPart.genId
AND premiumPart.cat = report.toCat;
Query Result
This answers the original version of the question.
Aggregate before you join:
select l.*, p.normal_price, p.premium_price
from (select genid, partnumber, sum(quantity) as quantity
from log
group by genid, partnumber
) l left join
(select partnumber, genid,
max(case when type = 'normal' then price end) as normal_price,
max(case when type = 'premium' then price end) as premium_price
from part
group by partnumber, genid
) p
on p.partnumber = l.partnumber and p.genid = l.genid

How to do a subquery in group_concat

I have the following data model:
`title`
- id
- name
`version`
- id
- name
- title_id
`version_price`
- id
- version_id
- store
- price
And here is an example of the data:
`title`
- id=1, name=titanic
- id=2, name=avatar
`version`
- id=1, name="titanic (dubbed in spanish)", title_id=1
- id=2, name="avatar directors cut", title_id=2
- id=3, name="avatar theatrical", title_id=2
`version_price`
- id=1, version_id=1, store=itunes, price=$4.99
- id=1, version_id=1, store=google, price=$4.99
- id=1, version_id=2, store=itunes, price=$5.99
- id=1, version_id=3, store=itunes, price=$5.99
I want to construct a query that will give me all titles that have a version_price on iTunes but not on Google. How would I do this? Here is what I have so far:
select
title.id, title.name, group_concat(distinct store order by store)
from
version inner join title on version.title_id=title.id inner join version_price on version_price.version_id=version.id
group by
title_id
This gives me a group_concat which shows me what I have:
id name group_concat(distinct store order by store)
1 Titanic Google,iTunes
2 Avatar iTunes
But how would I construct a query to include whether the item is on Google (using a case statement or whatever's needed)
id name group_concat(distinct store order by store) on_google
1 Titanic Google,iTunes true
2 Avatar iTunes false
It would basically be doing a group_concat LIKE '%google%' instead of a normal where clause.
Here's a link for a SQL fiddle of the current query I have: http://sqlfiddle.com/#!9/e52b53/1/0
Use conditional aggregation to determine if the title is in a specified store.
select title.id, title.name, group_concat(distinct version_price.store order by store),
if(count(case when store = 'google' then 1 end) >= 1,'true','false') as on_google
from version
inner join title on version.title_id=title.id
inner join version_price on version_price.version_id=version.id
group by title.id, title.name
count(case when store = 'google' then 1 end) >= 1 counts all the rows for a given title after assigning 1 to the rows which have google in them. (Or else they would be assigned null and the count ignores nulls.) Thereafter, the if checks for the countand classifies a title if it has atleast one google store on it.
http://sqlfiddle.com/#!9/b8706/2
you can just:
SELECT
title.id,
title.name,
group_concat(distinct version_price.store),
MAX(IF(version_price.store='google',1,0)) on_google
FROM version
INNER JOIN title
ON version.title_id=title.id
INNER JOIN version_price
ON version_price.version_id=version.id
GROUP BY title_id;
and add HAVING to the query if need records to be filtered:
SELECT
title.id,
title.name,
group_concat(distinct version_price.store),
MAX(IF(version_price.store='google',1,0)) on_google
FROM version
INNER JOIN title
ON version.title_id=title.id
INNER JOIN version_price
ON version_price.version_id=version.id
GROUP BY title_id
HAVING on_google;
This will give you the number of version prices not on google, and the number on google. (COUNT does not count null values.)
SELECT t.id, t.name
, COUNT(DISTINCT vpNotG.id) > 0 AS onOtherThanGoogle
, COUNT(DISTINCT vpG.id) > 0 AS onGoogle
FROM title AS t
INNER JOIN version AS v ON t.id=v.title_id
LEFT JOIN version_price AS vpNotG
ON v.id=vpNotG.version_id
AND vpNotG.store <> 'Google'
LEFT JOIN version_price AS vpG
ON v.id=vpG.version_id
AND vpG.store = 'Google'
GROUP BY t.id
or for another solution similar to vkp's:
SELECT t.id, t.name
, COUNT(DISTINCT CASE WHEN store = 'Google' THEN vp.id ELSE NULL END) AS googlePriceCount
, COUNT(DISTINCT CASE WHEN store = 'iTunes' THEN vp.id ELSE NULL END) AS iTunesPriceCount
, COUNT(DISTINCT CASE WHEN store <> 'Google' THEN vp.id ELSE NULL END) AS nonGooglePriceCount
FROM title AS t
INNER JOIN version AS v ON t.id = v.title_id
INNER JOIN version_price AS vp ON v.id = vp.version_id
GROUP BY t.id
Note: The ELSE NULL can be omitted, because if no ELSE is provided it is implied; but I included for clarity.
I would do it like below
SELECT
*
FROM
title t
INNER JOIN
version v ON
v.title_id = t.id
CROSS APPLY (
SELECT
*
FROM
version_price vp
WHERE
vp.store <> 'google'
) c ON c.version_id == v.id
Syntax may be just a little off as I can't test it right now, but I believe this is the spirit of what you would want. Cross apply is also a very efficient join which is always helpful!
This might be the most inefficient of the above answers, but the following subquery would work, using a %like% condition:
select *, case when stores like '%google%' then 1 else 0 end on_google
from (select title.id, title.name, group_concat(distinct store order by store) stores
from version inner join title on version.title_id=title.id inner join version_price
on version_price.version_id=version.id group by title_id) x
id name stores on_google
1 Titanic Google,iTunes 1
2 Avatar iTunes 0

SQL Left Join Multiple Tables and count values conditionally in each table

I am having some trouble putting together a SQL statement properly because I don't have much experience SQL, especially aggregate functions. Safe to say I don't really know what I'm doing outside of the basic SQL structure. I can do regular joins, but not complex ones.
I have some tables: 'Survey', 'Questions', 'Session', 'ParentSurvey', and 'ParentSurveyQuestion'. Structurally, a survey can have questions, it can have users that started the survey (a session), and it can have a parent survey whose questions get imported into the current survey.
What I want to do is get information for a each survey in the Survey table; total questions it has, how many sessions have been started (conditionally, ones that have not finished), and the number of questions in the parents survey. The three joined tables can but do not have to contain any values, and if they don't then 0 should be returned by COUNT. The common field in three of the tables is a variation of 'survey_id'
Here is my SQL so far, I put the table structure below it.
SELECT
`kp_survey_id`,
COALESCE( q.cnt, 0 ) AS questionsAmount,
COALESCE( s.cnt, 0 ) AS sessionsAmount
COALESCE( p.cnt, 0 ) AS parentQAmount,
FROM `Survey`
LEFT JOIN <-- I'd like the count of questions for this survey
( SELECT COUNT(*) AS cnt
FROM Questions
GROUP BY kf_survey_id ) q
ON Survey.kp_survey_id = Questions.kf_survey_id
LEFT JOIN
( SELECT COUNT(*) AS cnt <-- I'd like the count of started sessions for this survey
FROM Session
WHERE session_status = 'started' <-- should this be Session.session_status?
GROUP BY kf_survey_id ) s
ON Survey.kp_survey_id = Session.kf_survey_id
LEFT JOIN
( SELECT COUNT(*) AS cnt <-- I'd like the count of questions in the parent survey with this survey id
FROM ParentSurvey
GROUP BY kp_parent_survey_id ) p
ON Survey.kf_parent_survey_id = ParentSurveyQuestion.kf_parent_survey_id
'kp' prefix means primary key, while 'kf' prefix means foreign key
Structure:
Survey: 'kp_survey_id' | 'kf_parent_survey_id'
Question: 'kp_question_id' | 'kf_survey_id'
Session: 'kp_session_id' | 'kf_survey_id' | 'session_status'
ParentSurvey: 'kp_parent_survey_id' | 'survey_name'
ParentSurveyQuestion: 'kp_parent_question_id' | 'kf_parent_survey_id'
There are also other columns in each table like 'name' or 'account_id', but i don't think they matter in this case
I'd like to know if I'm doing this correctly or if I'm missing something. I'm repurposing some code I found here on stackoverflow and modifying it to meet my needs, as I haven't seen conditional aggregation for more than three tables on this site.
My expected output is something like:
kp_survey_id | questionsAmount | sessionsAmount | parentQAmount
1 | 3 | 0 | 3
2 | 0 | 5 | 3
I think you were pretty close -- just need to fix your joins and include the survey id in the subqueries to use in those joins:
SELECT
`kp_survey_id`,
COALESCE( q.cnt, 0 ) AS questionsAmount,
COALESCE( s.cnt, 0 ) AS sessionsAmount
COALESCE( p.cnt, 0 ) AS parentQAmount,
FROM `Survey`
LEFT JOIN
( SELECT COUNT(*) cnt, kf_survey_id AS cnt
FROM Questions
GROUP BY kf_survey_id ) q
ON Survey.kp_survey_id = q.kf_survey_id
LEFT JOIN
( SELECT COUNT(*) cnt, kf_survey_id
FROM Session
WHERE session_status = 'started'
GROUP BY kf_survey_id ) s
ON Survey.kp_survey_id = s.kf_survey_id
LEFT JOIN
( SELECT COUNT(*) cnt, kp_parent_survey_id
FROM ParentSurvey
GROUP BY kp_parent_survey_id ) p
ON Survey.kf_parent_survey_id = p.kp_parent_survey_id
One thing you need to do is correct your joins. When you are joining to a subquery, you need to use the alias of the subquery. In your case you are using the alias of the table being used in the subquery.
Another thing you need to change is to include the field you wish to use in your JOIN in the subquery.
Make these changes and try running. Do you get an error or the desired results?
SELECT
`kp_survey_id`,
COALESCE( q.cnt, 0 ) AS questionsAmount,
COALESCE( s.cnt, 0 ) AS sessionsAmount
COALESCE( p.cnt, 0 ) AS parentQAmount,
FROM `Survey`
LEFT JOIN <-- I'd like the count of questions for this survey
( SELECT kf_survey_id, COUNT(*) AS cnt
FROM Questions
GROUP BY kf_survey_id ) q
ON Survey.kp_survey_id = q.kf_survey_id
LEFT JOIN
( SELECT kf_survey_id, COUNT(*) AS cnt <-- I'd like the count of started sessions for this survey
FROM Session
WHERE session_status = 'started' <-- should this be Session.session_status?
GROUP BY kf_survey_id ) s
ON Survey.kp_survey_id = s.kf_survey_id
LEFT JOIN
( SELECT kp_parent_survey_id, COUNT(*) AS cnt <-- I'd like the count of questions in the parent survey with this survey id
FROM ParentSurvey
GROUP BY kp_parent_survey_id ) p
ON Survey.kf_parent_survey_id = p.kf_parent_survey_id

How to select all this data without repeating subqueries?

Say I have a table called 'users', a table called 'posts' and a table called 'ratings', that stores the rating of each user to each post. If I wanted to select the posts, together with their thumbs up, thumbs down and 'average' ratings (rounded to the second decimal), I would do:
SELECT *,
(SELECT COUNT(*) FROM ratings WHERE thumb='up' AND post_id = posts.id) AS thumbs_up,
(SELECT COUNT(*) FROM ratings WHERE thumb='down' AND post_id = posts.id) AS thumbs_down,
ROUND((SELECT COUNT(*) FROM ratings WHERE thumb='up' AND post_id = posts.id) / (SELECT COUNT(*) FROM ratings WHERE post_id = posts.id), 2) AS average_rating
FROM posts;
But is there a way to obtain this same data without repeating the subqueries? I mean, ideally I would like to do:
SELECT *,
(SELECT COUNT(*) FROM ratings WHERE thumb='up' AND post_id = posts.id) AS thumbs_up,
(SELECT COUNT(*) FROM ratings WHERE thumb='down' AND post_id = posts.id) AS thumbs_down,
ROUND(thumbs_up / (thumbs_up + thumbs_down), 2) AS average_rating
FROM posts;
But MySQL does not allow this. What is next best thing? Or is there an even better way, by using JOINs or UNIONs?
SELECT
*,
ROUND(thumbs_up / (thumbs_up + thumbs_down), 2) AS average_rating
FROM (
SELECT
YourColumns...
SUM(CASE WHEN r.thumb = 'up' THEN 1 END) AS thumbs_up,
SUM(CASE WHEN r.thumb = 'down' THEN 1 END) AS thumbs_down
FROM
posts p LEFT JOIN
ratings r ON r.post_id = p.id
GROUP BY YoursColumns
) sub
Why not having in your "posts" table a column "ups" and "downs" where are summarized / counted the ups and downs ? you would end with something like this :
SELECT ups, downs, ROUND(ups / (ups + downs), 2) AS average_rating FROM posts where id = anId;
You could simply use variables inside a stored procedure. Code is concept and may need adjustment to fit mysql.
SET #thumbsup = 0;
SET #thumbsdown = 0;
SELECT #thumbsup = COUNT( * ) FROM ratings WHERE thumb = 'up' AND post_id = #someid;
SELECT #thumbsdown = COUNT( * ) FROM ratings WHERE thumb = 'down' AND post_id = #someid;
RETURN #thumbsup, #thumbsdown, ROUND( #thumbsup / ( thumbsup + thumbsdown ), 2 );