Getting a Rank out of a Total - mysql

I have been doing this for quite some time:
SELECT COUNT(*) AS 'Rank' FROM Table
WHERE Condition = 'Condition' AND Score >= 'Score';
SELECT COUNT(*) AS 'Total' FROM Table
WHERE Condition = 'Condition';
Is there a more efficient way of getting both Rank and Total?

You can calculate both at the same time with one pass through the data.
SELECT COUNT(*) AS 'Total',
SUM(CASE WHEN Score >= 'Score' THEN 1 ELSE 0 END) AS `Rank`
FROM Table
WHERE Condition = 'Condition';

Related

MySQL InnoDB - GROUP BY on many items

I've got a table that has id, date, ad_id, ad_network, ad_event columns.
In my database there are millions of distinct ad_id each has a few events associated with them.
When I try to use GROUP BY on the ad_id to count each event it takes so long there is 503 error.
I need to count distinct AdClickThru and AdImpression so that I can calculate the CTR.
The problem is that one user can click many times, so I must count only one AdClickThru.
The query is below:
SELECT
`ad_network`,
`ad_id`,
SUM(DISTINCT CASE WHEN `ad_event` = "AdImpression" THEN 1 ELSE 0 END) as AdImpression,
SUM(DISTINCT CASE WHEN `ad_event` = "AdClickThru" THEN 1 ELSE 0 END) as AdClickThru
FROM `ads`
WHERE 1
AND `ad_event` IN ("AdImpression", "AdClickThru")
AND SUBSTR(`date`, 1, 7) = "2020-08"
GROUP BY `ad_id`
I have indexes on ad_id and ad_event + date but it does not help much.
How can I optimize this query?
The database will grow to billions of entries and more.
#edit
Forgot to mention that the code above is inner part of outer query:
SELECT
`ad_network`,
SUM(`AdImpression`) as cnt_AdImpression,
SUM(`AdClickThru`) as cnt_AdClickThru,
100 * SUM(`AdClickThru`) / SUM(`AdImpression`) as ctr
FROM (
SELECT
`ad_network`,
`ad_id`,
SUM(DISTINCT CASE WHEN `ad_event` = "AdImpression" THEN 1 ELSE 0 END) as AdImpression,
SUM(DISTINCT CASE WHEN `ad_event` = "AdClickThru" THEN 1 ELSE 0 END) as AdClickThru
FROM `ads`
WHERE 1
AND `ad_event` IN ("AdImpression", "AdClickThru")
AND SUBSTR(`date`, 1, 7) = "2020-08" -- better performance
GROUP BY `ad_id`
) a
GROUP BY `ad_network`
ORDER BY ctr DESC
The problem is that one user can click many times, so I must count only one AdClickThru.
Then use MAX(), not COUNT(DISTINCT). This gives the same result as your expression, and is much more efficient. I would also recommend rewriting the date filter so it is index-friendly:
SELECT
`ad_network`,
`ad_id`,
MAX(`ad_event` = 'AdImpression') as AdImpression,
MAX(`ad_event` = 'AdClickThru') as AdClickThru
FROM `ads`
WHERE 1
AND `ad_event` IN ('AdImpression', 'AdClickThru')
AND `date` >= '2020-08-01'
AND `date` < '2020-09-01'
GROUP BY `ad_id`
Notes:
the presence of ad_network in the select clause is hitching me: if there are several values per ad_id, it is undefined which will be picked. Either put this column in the group by clause as well, or use an aggregate function in the sélect clause (such as MAX(ad_network) - or if you are ok with an arbitrary value, then be explicit about it with any_value()
use single quotes for literal strings rather than double quotes (this is the SQL standard)
There is no need for 2 separate aggregations in the main query and the subquery.
You want to count the distinct ad_ids for each of the 2 cases:
SELECT ad_network,
COUNT(DISTINCT CASE WHEN ad_event = 'AdImpression' THEN ad_id END) AS cnt_AdImpression,
COUNT(DISTINCT CASE WHEN ad_event = 'AdClickThru' THEN ad_id END) AS cnt_AdClickThru,
100 *
COUNT(DISTINCT CASE WHEN ad_event = 'AdClickThru' THEN ad_id END) /
COUNT(DISTINCT CASE WHEN ad_event = 'AdImpression' THEN ad_id END) AS ctr
FROM ads
WHERE ad_event IN ('AdImpression', 'AdClickThru') AND SUBSTR(date, 1, 7) = '2020-08'
GROUP BY ad_network
ORDER BY ctr DESC
The problem here is that you have to repeat the expressions for cnt_AdImpression and cnt_AdClickThru.
You can calculate these expressions in a subquery:
SELECT ad_network, cnt_AdImpression, cnt_AdClickThru,
100 * cnt_AdClickThru / cnt_AdImpression AS ctr
FROM (
SELECT ad_network,
COUNT(DISTINCT CASE WHEN ad_event = 'AdImpression' THEN ad_id END) AS cnt_AdImpression,
COUNT(DISTINCT CASE WHEN ad_event = 'AdClickThru' THEN ad_id END) AS cnt_AdClickThru
FROM ads
WHERE ad_event IN ('AdImpression', 'AdClickThru') AND SUBSTR(date, 1, 7) = '2020-08'
GROUP BY ad_network
) t
ORDER BY ctr DESC

MySql GROUP BY Max Date

I have a table called votes with 4 columns: id, name, choice, date.
****id****name****vote******date***
****1*****sam*******A******01-01-17
****2*****sam*******B******01-05-30
****3*****jon*******A******01-01-19
My ultimate goal is to count up all the votes, but I only want to count 1 vote per person, and specifically each person's most recent vote.
In the example above, the result should be 1 vote for A, and 1 vote for B.
Here is what I currently have:
select name,
sum(case when uniques.choice = A then 1 else 0 end) votesA,
sum(case when uniques.choice = B then 1 else 0 end) votesB
FROM (
SELECT id, name, choice, max(date)
FROM votes
GROUP BY name
) uniques;
However, this doesn't work because the subquery is indeed selecting the max date, but it's not including the correct choice that is associated with that max date.
Don't think "group by" to get the most recent vote. Think of join or some other option. Here is one way:
SELECT v.name,
SUM(v.choice = 'A') as votesA,
SUM(v.choice = 'B') as votesB
FROM votes v
WHERE v.date = (SELECT MAX(v2.date) FROM votes v2 WHERE v2.name = v.name)
GROUP BY v.name;
Here is a SQL Fiddle.
Your answer are close but need to JOIN self
Subquery get Max date by name then JOIN self.
select
sum(case when T.vote = 'A' then 1 else 0 end) votesA,
sum(case when T.vote = 'B' then 1 else 0 end) votesB
FROM (
SELECT name,Max(date) as date
FROM T
GROUP BY name
) AS T1 INNER JOIN T ON T1.date = T.date
SQLFiddle
Try this
SELECT
choice,
COUNT(1)
FROM
votes v
INNER JOIN
(
SELECT
id,
max(date)
FROM
votes
GROUP BY
name
) tmp ON
v.id = tmp.id
GROUP BY
choice;
Something like this (if you really need count only last vote of person)
SELECT
sum(case when vote='A' then cnt else 0 end) voteA,
sum(case when vote='B' then cnt else 0 end) voteB
FROM
(SELECT vote,count(distinct name) cnt
FROM (
SELECT name,vote,date,max(date) over (partition by name) maxd
FROM votes
)
WHERE date=maxd
GROUP BY vote
)
PS. MySQL v 8
select
name,
sum( case when choice = 'A' then 1 else 0 end) voteA,
sum( case when choice = 'B' then 1 else 0 end) voteB
from
(
select id, name, choice
from votes
where date = (select max(date) from votes t2
where t2.name = votes.name )
) t
group by name
Or output just one row for the total counts of VoteA and VoteB:
select
sum( case when choice = 'A' then 1 else 0 end) voteA,
sum( case when choice = 'B' then 1 else 0 end) voteB
from
(
select id, name, choice
from votes
where date = (select max(date) from votes t2
where t2.name = votes.name )
) t
Based on #d-shish solution, and since introduction (in MySQL 5.7) of ONLY_FULL_GROUP_BY, the GROUP BY statement must be placed in subquery like this :
SELECT v.`name`,
SUM(v.`choice` = 'A') as `votesA`,
SUM(v.`choice` = 'B') as `votesB`
FROM `votes` v
WHERE (
SELECT MAX(v2.`date`)
FROM `votes` v2
WHERE v2.`name` = v.`name`
GROUP BY v.`name` # << after
) = v.`date`
# GROUP BY v.`name` << before
Otherwise, it won't work anymore !

Union two SQL Queries with one same column ( using Eloquent )

i have two queries which should be union (with laravel eloquent) but there is a duplicate column called group_date in both query and I should show one of them
SELECT
to_char(CREATE_UTC_DATETIME, 'yyyy-mm-dd') AS group_date,
COUNT(*) AS successful_transaction
FROM "REPORT_EVENTS"
WHERE "RESULT_CODE" = '0' AND "EVENT_TYPE" = 'BILL'
GROUP BY to_char(CREATE_UTC_DATETIME, 'yyyy-mm-dd')
ORDER BY "GROUP_DATE" DESC
SELECT
to_char(CREATE_UTC_DATETIME, 'yyyy-mm-dd') AS group_date,
COUNT(*) AS unsuccessful_transaction
FROM "REPORT_EVENTS"
WHERE "RESULT_CODE" = '1' AND "EVENT_TYPE" = 'BILL'
GROUP BY to_char(CREATE_UTC_DATETIME, 'yyyy-mm-dd')
ORDER BY "GROUP_DATE" DESC
You don't want a UNION here, but rather a single query which uses conditional aggregation:
SELECT
TO_CHAR(CREATE_UTC_DATETIME, 'yyyy-mm-dd') AS group_date,
SUM(CASE WHEN RESULT_CODE = '0' THEN 1 ELSE 0 END) AS successful_transaction,
SUM(CASE WHEN RESULT_CODE = '1' THEN 1 ELSE 0 END) AS unsuccessful_transaction
FROM "REPORT_EVENTS"
WHERE "EVENT_TYPE" = 'BILL'
GROUP BY TO_CHAR(CREATE_UTC_DATETIME, 'yyyy-mm-dd')
ORDER BY "GROUP_DATE" DESC
I am not giving any Eloquent/Laravel code here, but I am fairly certain that you would need a custom raw query to handle this. So, your actual PHP code would more or less just have the above query in its raw form.

Multiple select with single Group By query

I have these 3 fields in the table.
trans_date | transaction_type | client_id
What I need is a count of entries by transaction_types for each date. For example,
Date : 07/07/2015 total count : 6 transaction_type 1 count : 3 ,
transaction_type 2 count : 1, transaction_type 3 count : 2 etc....
And I need this for all the dates grouped by each date.
Here's my current query,
SELECT count(id) as total_count,
(select count(id) where transaction_type=1) as type1_count,
(select count(id) where transaction_type=2) as type2_count,
(select count(id) where transaction_type=3) as type3_count
FROM tblTransactions
where client_id=1
GROUP BY date(trans_date/1000, 'unixepoch')
This returns weird numbers that doesn't match. What am I doing wrong?
The reason that you are getting weird values is that your sub queries are not filtered by date so you will get the total count for each transaction type. What you need is a correlated subquery that will get a paremeter from outer query:
SELECT count(id) as total_count,
(select count(id) where transaction_type=1 and trans_date=t.trans_date) as type1_count,
(select count(id) where transaction_type=2 and trans_date=t.trans_date) as type2_count,
(select count(id) where transaction_type=3 and trans_date=t.trans_date) as type3_count
FROM tblTransactions t
where client_id=1
GROUP BY date(trans_date/1000, 'unixepoch')
You can use sum function instead of subqueries
select date(trans_date/1000, 'unixepoch') d,
sum(case when transaction_type = 1 then 1 else 0 end) type1_count,
sum(case when transaction_type = 2 then 1 else 0 end) type2_count,
sum(case when transaction_type = 3 then 1 else 0 end) type3_count
from tblTransactions
where client_id=1
group by d

SQL query not returning expect result

I wrote the following query to return some statistics about purchases made in the X amount of time. But for some reason every "COUNT" column return the total number of rows. Did I organize the query incorrectly?
SELECT COUNT(*) as countTotal, SUM(`cost`) as cost, COUNT(`paymentType` = 'credit') as count_credit, COUNT(`paymentType` = 'cash') as count_cash
FROM `purchase` WHERE `date` >= '2011-5-4'
update
I just decided to use sub-queries. This is what I ended up with.
SELECT
COUNT(*) as countTotal,
SUM(`cost`) as cost,
(SELECT COUNT(*) FROM `purchase` WHERE `paymentType` = 'credit') as count_credit,
(SELECT COUNT(*) FROM `purchase` WHERE `paymentType` = 'cash') as count_cash
FROM `purchase` WHERE `date` >= '2011-5-4'
update2
Used ypercubes answer below.
count does return the number of rows for the domain or group queried. Looks like you need to group by PaymentType to achieve what you are looking for.
SELECT PaymentType, COUNT(*) as countTotal, SUM(`cost`) as cost,
FROM `purchase`
WHERE `date` >= '2011-5-4'
Group by PaymentType
here is a reference
http://dev.mysql.com/doc/refman/5.0/en/group-by-functions.html
It doesn't look correct but changing COUNT() to SUM() works fine:
SELECT COUNT(*) AS countTotal
, SUM(cost) AS cost
, SUM(paymentType = 'credit') AS count_credit --- SUM does counting here
, SUM(paymentType = 'cash') AS count_cash --- and here
FROM purchase
WHERE `date` >= '2011-05-04'
Explanation: True == 1 and False == 0 for MySQL.
You need a GROUP BY clause after your WHERE clause