How to select count(distinct) with additional where clause in MySQL? - mysql

I have a query that looks like this:
SELECT clicks.offer_id, count(distinct clicks.transaction_id) as unique_clicks
FROM clicks
WHERE date > '2014-12-01 17:43:30' and offer_id = 1;
This query counts the number of unique clicks by their transaction ID for an offer.
Now, I want to add the unique number of clicks for mobile and desktop users. Along the lines of:
SELECT clicks.offer_id, count(distinct clicks.transaction) as unique_clicks,
sum(case when clicks.mobile = 1 then 1 else 0 end) as mobile_unique_clicks,
sum(case when clicks.mobile = 0 then 1 else 0 end) as desktop_unique_clicks
FROM clicks
WHERE date > '2014-12-01 17:43:30' and offer_id = 1;
The problem with this is that it won't count unique transaction IDs. Is there a way to do this with one query? And what kind of covering indexes would I need to keep this efficient?

SELECT clicks.offer_id, count(distinct clicks.transaction_id) as unique_clicks,
count(DISTINCT case when clicks.mobile = 1 then clicks.transaction_id else null end) as mobile_unique_clicks,
count(DISTINCT case when clicks.mobile = 0 then clicks.transaction_id else null end) as desktop_unique_clicks
FROM clicks
WHERE date > '2014-12-01 17:43:30' and offer_id = 1;
You can use COUNT(DISTINCT ...)

something like this?
SELECT clicks.offer_id,
count(distinct clicks.transaction_id) as unique_clicks,
sub.mobile_unique_clicks,
count(distinct clicks.transaction_id) - sub.mobile_unique_clicks as desktop_unique_clicks
FROM clicks
JOIN
(
SELECT clicks.offer_id, count(distinct clicks.transaction_id) as mobile_unique_clicks,
FROM clicks
WHERE date > '2014-12-01 17:43:30' and offer_id = 1 AND clicks.mobile = 1
) sub
WHERE date > '2014-12-01 17:43:30' and offer_id = 1;

If I'm understanding your question correctly, you can move your query to a subquery and use distinct:
select offer_id,
count(transaction) unique_clicks,
count(case when mobile = 1 then 1 end) mobile_unique_clicks,
count(case when mobile = 0 then 1 end) desktop_unique_clicks
from (
select distinct offer_id, transaction, mobile
from clicks
where date > '2014-12-01 17:43:30' and offer_id = 1
) t
group by offer_id

Related

MySQL - join/group concat returning too many items

I've got a simple query which brings up wins, draws and losses in a head-to-head matches table.
SELECT
SUM(CASE WHEN score_w > score_m THEN 1 ELSE 0 END) AS wins_w,
SUM(CASE WHEN score_m > score_w THEN 1 ELSE 0 END) AS wins_m,
SUM(CASE WHEN score_w = score_m THEN 1 ELSE 0 END) AS draws
FROM 6dos7me3xn8
All is well. I get a single row, with the data I need as columns.
Now I want to also pull out a group concat'd list of the most recent three match dates. I tried:
SELECT
SUM(CASE WHEN mn.score_w, 0) > mn.score_m THEN 1 ELSE 0 END) AS wins_w,
SUM(CASE WHEN mn.score_m > mn.score_w THEN 1 ELSE 0 END) AS wins_m,
SUM(CASE WHEN mn.score_w = mn.score_m THEN 1 ELSE 0 END) AS draws,
GROUP_CONCAT(jn.date) AS recent
FROM 6dos7me3xn8 mn
JOIN (SELECT date FROM 6dos7me3xn8 ORDER BY date DESC LIMIT 3) jn
...but the LIMIT is having no effect, seemingly - I get all of the dates group concat'd, not just 3.
I also tried removing the JOIN and replacing the GROUP_CONCAT with
GROUP_CONCAT((SELECT date FROM 6dos7me3xn8 ORDER BY date DESC LIMIT 3)) AS recent
...but that errors with 'Subquery returns more than 1 row.'
I'm sure it's something simple, but what am I doing wrong?
If you are running MySQL 8.0, you can do this with window functions:
select
sum(score_w > score_m) as wins_w,
sum(score_m > score_w) as wins_m,
sum(score_w = score_m) as draws,
group_concat(case when rn <= 3 then date end) as recent
from (
select t.*, row_number() over(order by date desc) rn
from `6dos7me3xn8` t
) t
The subquery ranks records by descending date; we can then use that information in the outer query. Note that you don't need the case expressions: MySQL evaluates true/false conditions as 1/0 in numeric contet.
In earlier versions, the simpler approach is probably a row-limiting subquery:
select
sum(score_w > score_m) as wins_w,
sum(score_m > score_w) as wins_m,
sum(score_w = score_m) as draws,
(select group_concat(date) from (select date from `6dos7me3xn8` order by date desc limit 3) t) as recent
from `6dos7me3xn8`
You are doing a cross join. TO get the most recent three dates, you can use:
FROM (SELECT mn.*, DENSE_RANK() OVER (ORDER BY date desc) as seqnum
FROM 6dos7me3xn8 mn
) mn
WHERE seqnum <= 3
You have no ON clause to specify the relationship between the subquery and the table you're joining it to. So you get a full cross product.
You also need ORDER BY to make it return the 3 most recent dates, not any 3 dates.
SELECT
SUM(CASE WHEN mn.score_w, 0) > mn.score_m THEN 1 ELSE 0 END) AS wins_w,
SUM(CASE WHEN mn.score_m > mn.score_w THEN 1 ELSE 0 END) AS wins_m,
SUM(CASE WHEN mn.score_w = mn.score_m THEN 1 ELSE 0 END) AS draws,
GROUP_CONCAT(jn.date) AS recent
FROM 6dos7me3xn8 mn
JOIN (
SELECT DISTINCT date
FROM 6dos7me3xn8
ORDER BY date DESC
LIMIT 3
) jn ON jn.date = mn.date

Count different values in a column, while doing total and group by different column values

We have a table with data from different nodes and one of the column will have status report as "compliant or non-compliant", sample data as below
I want to filter the table in such a way that if any of the checks on a node shows non compliant, it should be flagged as non-compliant and rest as compliant. Using below query i am able to do it
SELECT COUNT(*) AS total_nodes,
SUM(fully_compliant = 0) AS Non_compliant_nodes,
SUM(fully_compliant = 1) AS compliant_nodes
FROM (
SELECT Node, CASE WHEN SUM(Status = 'Compliant') = COUNT(*) THEN 1 ELSE 0 END AS fully_compliant
FROM your_table GROUP BY Node
)
Now, i want to group and split the result by dept as below, how can i achieve this
I think you're looking for this:
select dept,
count(*) as total_nodes,
sum(case when non_compliant_chk = 0 then 1 else 0 end) as compliant_nodes,
sum(case when non_compliant_chk > 0 then 1 else 0 end) as non_compliant_nodes
from (
select dept,
node,
sum(case when 'Non-Compliant' then 1 else 0 end) as non_compliant_chk
from your_table
group by dept,
node
) v
group by dept;
With few modifications to what Brian suggested, I am able to get the desired result
select dept,
count(*) as total_nodes,
sum(case when non_compliant_chk = 0 then 1 else 0 end) as compliant_nodes,
sum(case when non_compliant_chk > 0 then 1 else 0 end) as non_compliant_nodes
from (
select dept,
node,
COUNT(CASE WHEN Compliance-Status = 'Non-Compliant' THEN 1 END) 'non_compliant_chk'
from table WHERE DOR >= DATE(NOW()) - INTERVAL 7 DAY
group by Dept,
Node
) v
group by Dept;

MySQL Group By Query X or not X

This should be easy but I have not found the answer. I have a query that is running a count and I want to group this within the query into those where State = 1 and State <>1 and have the sum for each group.
SELECT count(`id_job`) as count, `state`
FROM job_table
GROUP BY `state`;
You can query a boolean expression and group by it too:
SELECT state = 1, COUNT(*)
FROM job_table
GROUP BY state = 1
SELECT count(id_job) as count,state FROM job_table
GROUP BY case when state = 1 then 1 else 0
In MySQL, you can easily put these into columns:
select sum( state = 1 ) as state_1,
sum( state <> 1 ) as state_2
from job_table;
If state can be NULL, you want to be a bit careful. The second condition is safer as:
select sum( state = 1 ) as state_1,
sum( not state <=> 1 ) as state_2
from job_table;
Try this
SELECT SUM(CASE WHEN State=1 THEN 1 END) as count1,
SUM(CASE WHEN State<>1 THEN 1 END) as count2,
State
FROM job_table
GROUP BY state

Get both results with one query

I want to get results for likes by a specific user-id and all likes and dislikes of a specific page id.
My structure looks like this:
`pages`: (id, title)
`pages_likes`: (id, page_id, uid, status)
If the status is -1 it's a dislike of a specific page, if it's 1 it's a like.
So, to get all likes this is my query:
SELECT COUNT(id) FROM pages_likes WHERE status = '1'
But now I also want to get if user-id 3 for example likes this page with
SELECT COUNT(id) FROM pages_likes WHERE status = '1' AND uid='3'
How can I achieve both in one query? I guess there has to be changed something right after the SELECT statement?
If you want to do this in a single query, use conditional aggregation:
SELECT SUM(CASE WHEN uid = '3' THEN 1 ELSE 0 END) AS threeLikes,
SUM(CASE WHEN uid <> '3' THEN 1 ELSE 0 END) AS otherLikes
FROM pages_likes
WHERE status = '1'
Another option would be to use a UNION, cf. the answer given by #bernie
Update:
If you want page likes and dislikes in the same query, you can try:
SELECT SUM(CASE WHEN uid = '3' AND status = '1' THEN 1 ELSE 0 END) AS threeLikes,
SUM(CASE WHEN uid <> '3' AND status = '1' THEN 1 ELSE 0 END) AS otherLikes
SUM(CASE WHEN uid = '3' AND status = '0' THEN 1 ELSE 0 END) AS threeDislikes,
SUM(CASE WHEN uid <> '3' AND status = '0' THEN 1 ELSE 0 END) AS otherDisikes
FROM pages_likes
I think this would work:
SELECT '' user, COUNT(id) likes
FROM pages_likes
WHERE status = '1'
GROUP BY 1
UNION ALL
SELECT uid user, COUNT(id) likes
FROM pages_likes
WHERE status = '1' AND uid='3'
GROUP BY 1
I would do it this way:
SELECT COUNT(*) AS total_likes, SUM(uid=3) AS uid3_likes
FROM pages_likes
WHERE status=1 AND page_id=1234
Re your comment:
Here's an example of showing total likes, and total dislikes. It's similar to the answer from #TimBiegeleisen.
SELECT SUM(status=1) AS total_likes,
SUM(CASE WHEN uid=3 AND status=1 THEN 1 END) AS uid3_likes
SUM(CASE WHEN uid=3 AND status=-1 THEN 1 END) AS uid3_dislikes
FROM pages_likes
WHERE page_id=1234

Combing two queries while using group by

Having some trouble figuring out the logic to this. See the two queries below:
Query 1:
SELECT cId, crId, COUNT(EventType)
FROM Data
WHERE EventType='0' OR EventType='0p' OR EventType='n' OR EventType = 'np'
GROUP BY crId;
Query 2:
SELECT cId, crId, COUNT(EventType) AS Clicks
FROM Data
WHERE EventType='c'
GROUP BY crId;
Was just wondering if there was a way to make the column that I would get at the end of query 2 appear in query 1. Since the where statements are different, not really sure where to go, and any subquery that I've wrote just hasn't worked.
Thanks in advance
SELECT cId, crId,
SUM(CASE WHEN EventType='0' OR EventType='0p' OR EventType='n' OR EventType = 'np' THEN 1 ELSE 0 END) AS Count_1,
SUM(CASE WHEN EventType='c' THEN 1 ELSE 0 END) AS Count_2
FROM Data
WHERE EventType IN ('0','0p','n','np','c')
GROUP BY crId;
You can join the two, using the second as a correlated subquery.
SELECT
Data.cId,
Data.crId,
COUNT(EventType) AS event_type_count,
click_counts.Clicks
FROM
Data
/* Correlated subquery retrieves the Clicks (EventType 'c') per cId */
LEFT JOIN (
SELECT cId, crId, COUNT(EventType) AS Clicks
FROM Data
WHERE EventType='c'
GROUP BY crId
) AS click_count ON Data.cId = click_count.cId AND Data.crId = click_count.crId
/* OR chain replaced with IN() clause */
WHERE Data.EventType IN ('0','0p','n','np')
/* This GROUP BY should probably also include Data.cId... */
GROUP BY Data.crId;
You can do this all querying from the table once and using CASE statements.
SELECT cId, crId,
SUM(CASE WHEN EventType IN ('0', '0p', 'n', 'np') THEN 1 ELSE 0 END) as events,
SUM(CASE WHEN EventType = 'c' THEN 1 ELSE 0 END) as clicks
FROM Data
WHERE EventType IN ('0', '0p', 'n', 'np', 'c')
GROUP BY crId;
You want to use IN?
SELECT cId, crId, COUNT(EventType) as Clicks
FROM Data
WHERE EventType IN ('0','0p','n','np','c')
GROUP BY crId;
:) PUtting myself in right direction ;)
sqlfiddle demo
select id, crid,
count(case when type <> 'c'
then crid end) count_others,
count(case when type ='c'
then crid end) count_c
from tb
group by crid
;