What does "expr" do in MySQL's COUNT(expr) function? - mysql

Consider below:
SELECT COUNT(*) FROM table;
SELECT COUNT(1) FROM table;
SELECT COUNT(-2) FROM table;
SELECT COUNT(135392) FROM table;
SELECT COUNT(field) FROM table;
SELECT COUNT(field1 + field2) FROM table;
I am not clear on what expr actually does, or what it can be used for because all of the above SQL statements return the same result. Example below:
+-----------+
| count(..) |
+-----------+
| 54542 |
+-----------+
MySQL's manual (https://dev.mysql.com/doc/refman/8.0/en/counting-rows.html) does not go into very much detail on the expr part, other than using the * symbol

COUNT(<expr>) counts the number of rows where <expr> evaluates to a non-NULL values.
Generally, it is not needed with an expression and would only be used on a single, NULL-able column -- or column that could be NULL due to an outer join.

COUNT(*) will count all rows
COUNT(expr) will count the row if expr is NOT NULL
So COUNT(expr) could be less than COUNT(*) if expr contains NULL values:
SELECT COUNT(*), COUNT(1), COUNT(col)
FROM (
SELECT 'a' UNION ALL
SELECT 'b' UNION ALL
SELECT NULL
) AS t(col)
-- 3 3 2

Expr is short for expression, itself short for “some valid block of sql that results in a single value for this row when evaluated”
It could be a constant, a column, the result of a function call, variable assignment, case statement etc
—equivalent
COUNT(*)
COUNT(1)
COUNT(‘a’)
—count only males. If the group is 1000 in number and 600 are female, this returns 400
COUNT(case when gender = ‘m’ then ‘a’ else null end)
In supplement to the other answers, the <expr> may optionally start with the word DISTINCT in which case only unique occurrences of the referenced entity/expression/function result are counted
—in a set of 1000 animals, returns 1000
COUNT(gender)
—in a set of 1000 animals, 600 female, returns 2 (only values M and F exist in the group)
COUNT(distinct gender)

Related

MySQL filter results from a SubSelect using AND or HAVING

I have a query that looks basically like this:
Select t.id,
(
Select GROUP_CONCAT(CONCAT_WS(' ', td.id) SEPARATOR ',') as list
From Table t2
) as id_list // this will add a comma delimited list
From Table t1
Where t1.status IS NULL
And id_list IN (3)
The query result is like this...
id|id_list
--------------------
1 |1
9 |1,3,12,10,15
This does not work as MySQL will not allow me to filter by id_list as an And conditional...
I have also tried this in place of the And conditional, but it does not work also...
Having id_list IN (3)
How can I filter these results based upon the id_list matching some parameter which I set, in this case 3.
I just want to return record id 9, not 1.
Thanks
To check for a value '3' in a comma separated list e.g. '2,3,5' we can use MySQL FIND_IN_SET function.
As a demonstration:
SELECT FIND_IN_SET('3','2,3,5') returns 2
SELECT FIND_IN_SET('3','7,11') returns 0
So for the query in the question, given id_list is a comma separated list, we could do
HAVING FIND_IN_SET('3',id_list)
or equivalently
HAVING FIND_IN_SET('3',id_list) > 0
Note that the IN comparison operator does not work like the FIND_IN_SET function.
The IN is equivalent to equality comparison of individual values. For example,
SELECT 3 IN (2,3,5)
is equivalent to
SELECT 3 = 2 OR 3 = 3 OR 3 = 5
As another example:
SELECT 3 IN ('2,3,5')
is equivalent to
SELECT 3 = '2,3,5'
Just a guess:
SELECT t.id,
(
SELECT GROUP_CONCAT(CONCAT_WS(' ', td.id) SEPARATOR ',') as list
FROM Table t2
) as id_list
FROM Table t1
WHERE t1.status IS NULL
AND FIND_IN_SET(3, id_list) > 0

How to include NULL values in aggregate function COUNT() in MySQL?

The aggregate query groups by attribute A3 and then performs a COUNT(A4) but it doesn't consider the NULL values in the attribute A4.
For a regular count, don't include the column name:
count(*)
For count distinct, just add the extra value back in:
count(distinct a4) + (case when count(a4) <> count(*) then 1 else 0 end)
This can be simplified in MySQL to:
count(distinct a4) + (count(a4) <> count(*))
Or, if you know there is value that won't exist in the column:
count(distinct coalesce(a4, ' <NULL>'))

Mysql how to sum and display as single row

I have a vote mysql table and users (user column) can vote y or n. (option column)
My table structure is like below:
| id | option | user | 
| 1 | y | jack | 
| 2 | n | jack | 
| 3 | n | michi| 
| 4 | n | michi| 
What I would like to do is, select distinct user and count option and display it in a single row like below:
| y | n |
| 1 | 2 |
I tried GROUP_CONCAT() and SUM but without luck. Can you please help me to get this sql working?
Thanks.
Group functions like GROUP_CONCAT(), SUM() and COUNT() need a GROUP BY statement to know which rows to combine.
In your query, you want to use COUNT().
Try this:
SELECT `option`, COUNT(DISTINCT `user`) AS users
FROM `table`
GROUP BY `option`
DEMO: http://sqlfiddle.com/#!9/705a9d/3
This will show you one row per option. If you want both options across one row, that's a bit trickier. You'll need to use subqueries for each option.
SELECT (
SELECT COUNT(DISTINCT `user`)
FROM `table`
WHERE `option` = 'y'
) AS y, (
SELECT COUNT(DISTINCT `user`)
FROM `table`
WHERE `option` = 'n'
) AS n
DEMO: http://sqlfiddle.com/#!9/705a9d/4
NOTE: You can use COUNT() without GROUP BY. That will make the query combine all found rows together.

MySQL - how to select query values which doesn't have a specific data

I'm having some troubles (thinking about it) doing a select from this table.
tb_details:
id status det_id
1 5 22
2 1 22
3 0 22
4 5 25
5 1 25
6 5 27
7 1 27
8 5 32
9 1 32
10 0 32
How can i make a select query to show just the det_id values which doesn't have a 0 in the table, maybe something like this:
det_id
25
27
One approach (out of several workable approaches) is to use an anti-join pattern.
SELECT t.det_id
FROM this_table t
LEFT
JOIN ( SELECT r.det_id
FROM this_table r
WHERE r.status = 0
GROUP BY r.det_id
) s
ON s.det_id = t.det_id
WHERE s.det_id IS NULL
GROUP BY t.det_id
Let's unpack that a bit.
The inline view (aliased as s) returns a distinct list of det_id values for which a status=0 row does exist in this_table.
The LEFT JOIN operation returns all det_id values from this_table t, along with the matching det_id from s. If a match is not found, the "left outerness" of the join means that all rows from t will be returned, whether a match is found or not.
The "trick" is the predicate in the WHERE clause, testing whether the value of the column returned from s is NULL or not. The predicate effectively excludes any rows from t which had a matching row found in s.
So, all that remains to return is rows from t that didn't have a match in s.
We add a GROUP BY t.det_id (or we could add the DISTINCT keyword), to return a list of distinct det_id values.
This isn't the only approach. You could also use a NOT EXISTS predicate ...
SELECT t.det_id
FROM this_table t
WHERE NOT EXISTS
( SELECT 1
FROM this_table r
WHERE r.det_id = t.det_id
AND r.status = 0
)
GROUP BY t.det_id
(This differs slightly, in that a row with a NULL value for det_id could be returned, where the previous query would not return it. That first query could be tweaked to make it return the same result as this.)
You could also use a NOT IN, taking care that the subquery does not return any NULL values for det_id.
SELECT t.det_id
FROM this_table t
WHERE t.det_id NOT IN
( SELECT r.det_id
FROM this_table r
WHERE r.status = 0
AND r.det_id IS NOT NULL
GROUP BY r.det_id
)
GROUP BY t.det_id
There are several statements what will return the specified resultset.
Another and simpler way of doing this is:-
SELECT DISTINCT det_id FROM TB
WHERE det_id NOT IN
(SELECT det_id RFOM TB WHERE status = 0);
If you want to only have the det_id where no other colum is 0 you should write
SELECT det_id FROM TABLE WHERE
Col1 <> 0
AND
Col2 <> 0
and so on...
If you want only 1 result per type add
GROUP BY det_id
at the end of the query.

Is there a simpler way to find MODE(S) of some values in MySQL

MODE is the value that occurs the MOST times in the data, there can be ONE MODE or MANY MODES
here's some values in two tables (sqlFiddle)
create table t100(id int auto_increment primary key, value int);
create table t200(id int auto_increment primary key, value int);
insert into t100(value) values (1),
(2),(2),(2),
(3),(3),
(4);
insert into t200(value) values (1),
(2),(2),(2),
(3),(3),
(4),(4),(4);
right now, to get the MODE(S) returned as comma separated list, I run the below query for table t100
SELECT GROUP_CONCAT(value) as modes,occurs
FROM
(SELECT value,occurs FROM
(SELECT value,count(*) as occurs
FROM
T100
GROUP BY value)T1,
(SELECT max(occurs) as maxoccurs FROM
(SELECT value,count(*) as occurs
FROM
T100
GROUP BY value)T2
)T3
WHERE T1.occurs = T3.maxoccurs)T4
GROUP BY occurs;
and the below query for table t200 (same query just with table name changed) I have 2 tables in this example because to show that it works for cases where there's 1 MODE and where there are multiple MODES.
SELECT GROUP_CONCAT(value) as modes,occurs
FROM
(SELECT value,occurs FROM
(SELECT value,count(*) as occurs
FROM
T200
GROUP BY value)T1,
(SELECT max(occurs) as maxoccurs FROM
(SELECT value,count(*) as occurs
FROM
T200
GROUP BY value)T2
)T3
WHERE T1.occurs = T3.maxoccurs)T4
GROUP BY occurs;
My question is "Is there a simpler way?"
I was thinking like using HAVING count(*) = max(count(*)) or something similar to get rid of the extra join but couldn't get HAVING to return the result i wanted.
UPDATED:
as suggested by #zneak, I can simplify T3 like below:
SELECT GROUP_CONCAT(value) as modes,occurs
FROM
(SELECT value,occurs FROM
(SELECT value,count(*) as occurs
FROM
T200
GROUP BY value)T1,
(SELECT count(*) as maxoccurs
FROM
T200
GROUP BY value
ORDER BY count(*) DESC
LIMIT 1
)T3
WHERE T1.occurs = T3.maxoccurs)T4
GROUP BY occurs;
Now is there a way to get ride of T3 altogether?
I tried this but it returns no rows for some reason
SELECT value,occurs FROM
(SELECT value,count(*) as occurs
FROM t200
GROUP BY `value`)T1
HAVING occurs=max(occurs)
basically I am wondering if there's a way to do it such that I only need to specify t100 or t200 once.
UPDATED: i found a way to specify t100 or t200 only once by adding a variable to set my own maxoccurs like below
SELECT GROUP_CONCAT(CASE WHEN occurs=#maxoccurs THEN value ELSE NULL END) as modes
FROM
(SELECT value,occurs,#maxoccurs:=GREATEST(#maxoccurs,occurs) as maxoccurs
FROM (SELECT value,count(*) as occurs
FROM t200
GROUP BY `value`)T1,(SELECT #maxoccurs:=0)mo
)T2
You are very close with the last query. The following finds one mode:
SELECT value, occurs
FROM (SELECT value,count(*) as occurs
FROM t200
GROUP BY `value`
LIMIT 1
) T1
I think your question was about multiple modes, though:
SELECT value, occurs
FROM (SELECT value, count(*) as occurs
FROM t200
GROUP BY `value`
) T1
WHERE occurs = (select max(occurs)
from (select `value`, count(*) as occurs
from t200
group by `value`
) t
);
EDIT:
This is much easier in almost any other database. MySQL supports neither with nor window/analytic functions.
Your query (shown below) does not do what you think it is doing:
SELECT value, occurs
FROM (SELECT value, count(*) as occurs
FROM t200
GROUP BY `value`
) T1
HAVING occurs = max(occurs) ;
The final having clause refers to the variable occurs but does use max(occurs). Because of the use of max(occurs) this is an aggregation query that returns one row, summarizing all rows from the subquery.
The variable occurs is not using for grouping. So, what value does MySQL use? It uses an arbitrary value from one of the rows in the subquery. This arbitrary value might match, or it might not. But, the value only comes from one row. There is no iteration over it.
I realize this is a very old question but in looking for the best way to find the MODE in a MySQL table, I came up with this:
SELECT [column name], count(*) as [ccount] FROM [table] WHERE [field] = [item] GROUP BY [column name] ORDER BY [ccount] DESC LIMIT 1 ;
In my actual situation, I had a log with recorded events in it. I wanted to know during which period (1, 2 or 3 as recorded in my log) the specific event occurred the most number of times. (Eg, the MODE of "period" column of the table for that specific event
My table looked like this (abridged):
EVENT_TYPE | PERIOD
-------------------------
1 | 3
1 | 3
1 | 3
1 | 2
2 | 1
2 | 1
2 | 1
2 | 3
Using the query:
SELECT event_type, period, count(*) as pcount FROM proto_log WHERE event_type = 1 GROUP BY period ORDER BY pcount DESC LIMIT 1 ;
I get the result:
> EVENT_TYPE | PERIOD | PCOUNT
> --------------------------------------
1 | 3 | 3
Using this result, the period column ($result['period'] for example) should contain the MODE for that query and of course pcount contains the actual count.
If you wanted to get multiple modes, I suppse you could keep adding other criteria to your WHERE clause using ORs:
SELECT event_type, period, count(*) as pcount FROM proto_log WHERE event_type = 1 ***OR event_type = 2*** GROUP BY period ORDER BY pcount DESC LIMIT 2 ;
The multiple ORs should give you the additional results and the LIMIT increase will add the additional MODES to the results. (Otherwise it will still only show the top 1 result)
Results:
EVENT_TYPE | PERIOD | PCOUNT
--------------------------------------
1 | 3 | 3
2 | 1 | 3
I am not 100% sure this is doing exactly what I think it is doing, or if it will work in all situations, so please let me know if I am on or off track here.