MySQL Count multiple columns at once or separate transactions - mysql

I have a daily cron that counts the number of non-null and non-empty string rows for a specific column, such as:
SELECT count(first_name) FROM users WHERE first_name IS NOT NULL and first_name IS != ''
SELECT count(last_name) FROM users WHERE last_name IS NOT NULL and first_name IS != ''
Would the performance be improved if I did this in one MySQL call?

Use a CASE condition for 1 query
SELECT SUM(CASE WHEN first_name IS NOT NULL and first_name != '' THEN 1 ELSE 0 END),
SUM(CASE WHEN last_name IS NOT NULL and last_name != '' THEN 1 ELSE 0 END) FROM users

I believe that this query is enough:
SELECT COUNT(DISTINCT first_name), COUNT(DISTINCT last_name) FROM users
As other developers pointed this COUNT will count even '' empty value.
IMHO that is acceptable. If not you can:
SELECT COUNT(DISTINCT first_name)-1, COUNT(DISTINCT last_name)-1 FROM users
It is not exactly what you need, but if you are sure that you have those empty value that will work :
If you want clear query that return correct COUNT you should better use #Mihai query:
SELECT SUM(CASE WHEN first_name IS NOT NULL and first_name != '' THEN 1 ELSE 0 END),
SUM(CASE WHEN last_name IS NOT NULL and last_name != '' THEN 1 ELSE 0 END) FROM user
But this one can hit the performance a lot on big numbers of records.

Related

Find duplicates in mySQL but with a specific status

I have an orders table and I need to find the duplicates - orders which have the same dd_number and dd_code, BUT one of the orders must have the status of "checkout_completed".
The following is my current SQL which correctly finds duplicate orders which have the same dd_number and dd_code, however I cant work out how to check for the status:
SELECT
tbOrders.id, tbOrders.dd_number, tbOrders.dd_code, tbOrders.status, COUNT(*) AS totalDupes
FROM
tbOrders
WHERE
dd_number IS NOT NULL
AND
dd_number != ''
AND
dd_number != '000000'
AND
dd_number != '00000000'
GROUP BY
dd_number, dd_code
HAVING totalDupes > 1
Adding a WHERE clause of status = 'checkout_complete' does not work as it requires both orders to have the same status. Any help would be greatly appreciated!
SELECT
tbOrders.id, tbOrders.dd_number, tbOrders.dd_code, tbOrders.status, COUNT(*) AS totalDupes,
SUM(CASE WHEN status='checkout_complete' THEN 1 ELSE 0 END) as status_count
FROM
tbOrders
WHERE
dd_number IS NOT NULL
AND
dd_number != ''
AND
dd_number != '000000'
AND
dd_number != '00000000'
GROUP BY
dd_number, dd_code
HAVING totalDupes > 1 and status_count=1
You can introduce one more aggregate field counting records with the status
Mmm, multiple way of doing it. I'd suggest using the HAVING clause with conditional aggregation :
HAVING totalDupes > 1
AND MAX(CASE WHEN status = 'checkout_complete' THEN 1 ELSE 0 END) = 1
Though your query seems wrong. If ID is unique, then a random ID will be choosen , same goes for STATUS . All the columns should either be in the GROUP BY or wrapped with an aggregation function. Note that only MySQL older versions accept your syntax. In any other RDBMS this will throw an error.

Reduce the number of queries

I have seperate queries but i need to reduce the no so put all in one
select count(applicant_id) as registered from student_application where filter_status=0 AND
select count(applicant_id) as filer_select from student_application where filter_status=1 AND
select count(applicant_id) as filter_reject from student_application where filter_status=2
but this shows some errors
Use CASE expression.
Query
select
count(case when filter_status = 0 then applicant_id else null end) as registered,
count(case when filter_status = 1 then applicant_id else null end) as filer_select,
count(case when filter_status = 2 then applicant_id else null end) as filer_reject
from student_application;
SQL Fiddle
You could also use group_by, with the where clause if you're looking for a subset rather than all possible values of filter_status:
SELECT filter_status, COUNT(*)
FROM student_application
WHERE filter_status in (0, 1, 2)
GROUP BY filter_status;

MySql, Postgres, Oracle and SQLServer ignoring IS NOT NULL filter

While I was preparing an answer to one of our fellows here on SO I've encounter an odd situation, at least to me. The original question is here: Pivot Table Omitting Rows that Have Null values
I've modified the query to use max instead of group_concat in order to show the "problem" in all databases.
SELECT
id,
max(case when colID = 1 then value else '' end) AS fn,
max(case when colID = 2 then value else '' end) AS ln,
max(case when colID = 3 then value else '' end) AS jt
FROM tbl
GROUP BY id
The result of this query is this:
ID FN LN JT
1 Sampo Kallinen Office Manager
2 Jakko Salovaara Vice President
3 (null) Foo No First Name
The user asks to filter the row with id 3 because the field value is null.
When it seems pretty obvious that only it needs to do was to add a WHERE value IS NOT NULL constraint on that query to achieve what the user expect. It won't work.
So I start to test it on the other databases to see what happens (Queries with the WHERE CLAUSE)
SELECT
id,
max(case when colID = 1 then value else '' end) AS fn,
max(case when colID = 2 then value else '' end) AS ln,
max(case when colID = 3 then value else '' end) AS jt
FROM tbl
WHERE value is not null
GROUP BY id
Mysql: http://sqlfiddle.com/#!2/78395/1
Postgres: http://sqlfiddle.com/#!15/78395/1
SQLServer: http://sqlfiddle.com/#!6/78395/1
Oracle: http://sqlfiddle.com/#!4/78395/1
For my surprise the result was the same, none worked.
Then I tried a different version of the same query:
SELECT * FROM (
SELECT
id,
max(case when colID = 1 then value else '' end) AS fn,
max(case when colID = 2 then value else '' end) AS ln,
max(case when colID = 3 then value else '' end) AS jt
FROM tbl
GROUP BY id
) T
WHERE fn IS NOT NULL
AND ln IS NOT NULL
AND jt IS NOT NULL
Oracle: http://sqlfiddle.com/#!4/78395/2 WORKED
MySql: http://sqlfiddle.com/#!2/78395/2
Postgres: http://sqlfiddle.com/#!15/78395/2
SQLServer: http://sqlfiddle.com/#!6/78395/2
The only way I could make it work on all databases was with this query:
SELECT
id,
max(case when colID = 1 then value else '' end) AS fn,
max(case when colID = 2 then value else '' end) AS ln,
max(case when colID = 3 then value else '' end) AS jt
FROM tbl
WHERE NOT EXISTS (SELECT * FROM tbl b WHERE tbl.id=b.id AND value IS NULL)
GROUP BY id
So I ask:
What is happening here that except for that specific case on Oracle all other DBs seem to ignore the IS NOT NULL filter?
To omit the row from the result if any of the source rows for the same id has value IS NULL, a solution in Postgres would be to use the aggregate function every() or (synonym for historical reasons) bool_and() in the HAVING clause:
SELECT id
, max(case when colID = 1 then value else '' end) AS fn
, max(case when colID = 2 then value else '' end) AS ln
, max(case when colID = 3 then value else '' end) AS jt
FROM tbl
GROUP BY id
HAVING every(value IS NOT NULL);
SQL Fiddle.
Explain
Your attempt with a WHERE clause would just eliminate one source row for id = 3 in your example (the one with colID = 1), leaving two more for the same id. So we still get a row for id = 3 in the result after aggregating.
But since we have no row with colID = 1, we get an empty string (note: not a NULL value!) for fn in the result for id = 3.
A faster solution in Postgres would be to use crosstab(). Details:
PostgreSQL Crosstab Query
Other RDBMS
While EVERY is defined in the SQL:2008 standard, many RDBMS do not support it, presumably because some of them have shady implementations of the boolean type. (Not dropping any names like "MySQL" or "Oracle" ...). You can probably substitute everywhere (including Postgres) with:
SELECT id
, max(case when colID = 1 then value else '' end) AS fn
, max(case when colID = 2 then value else '' end) AS ln
, max(case when colID = 3 then value else '' end) AS jt
FROM tbl
GROUP BY id
HAVING count(*) = count(value);
Because count() doesn't count NULL values. In MySQL there is also bit_and().
More under this related question:
Is there any equivalent to Postgresql EVERY aggregate function on other RDBMS?
It works in Oracle because Oracle handles NULL incorrectly in that NULL and '' are the same. The other databases don't do this because it is wrong. NULL is unknown, versus '' which is just a blank, empty string.
So if your where clause said something like WHERE (fn IS NOT NULL or fn <> '') you would probably get further.
I think this is a case where a HAVING clause will do what you need.
SELECT id, max ... (same stuff as before)
FROM tbl
GROUP by id
HAVING fn IS NOT NULL
AND ln IS NOT NULL
AND jt IS NOT NULL

Group by null and not null values

I have a Table containing facebook Ids of my users and I have to make a report on who is using facebook or not.
For the facebook user, the datarow contains a number else it contains null
My result have to be like that :
NbUsers Facebook
1000 no
500 yes
I don't know how to construct my query!
Any help will be helpfull!
Thanks a lot!!
you can do it easily by case statement :
SELECT SUM(CASE WHEN FACEBOOKID IS NULL THEN 1 ELSE 0 END) AS NbUsers , 'NO' AS FACEBOOK
FROM USERTABLE
UNION ALL
SELECT SUM(CASE WHEN FACEBOOKID IS NULL THEN 0 ELSE 1 END) AS NbUsers , 'YES' AS FACEBOOK
FROM USERTABLE

get count of two table fields in one query

I am trying to get the count of females and males in the gender field of a table.
Is there a way to get the count of each in one query?
Something like:
select * from table count(where gender = 'm') as total_males, count(where gender = 'f') as total_females;
or will it require two queries?
select count(*) from table where gender = 'm';
select count(*) from table where gender = 'f';
This is basically a PIVOT. MySQL does not have a pivot so you can use an aggregate function with a CASE statement to perform this:
select
sum(case when gender = 'm' then 1 else 0 end) Total_Male,
sum(case when gender = 'f' then 1 else 0 end) Total_Female
from yourtable
See SQL Fiddle with Demo
Or using COUNT:
select
count(case when gender = 'm' then 1 else null end) Total_Male,
count(case when gender = 'f' then 1 else null end) Total_Female
from yourtable;
See SQL Fiddle with Demo
Something like this will work:
SELECT SUM(IF(t.gender='m',1,0)) AS total_males
, SUM(IF(t.gender='f',1,0)) AS total_females
FROM mytable t
The "trick" here is that we are using a conditional test to return either a 0 or a 1 for each row, and then adding up the 0's and 1's. To make this a little more clear, I am using the SUM aggregate function rather than COUNT, although COUNT could be used just as easily, though we'd need to return a NULL in place of the zero.
SELECT COUNT(IF(t.gender='m',1,NULL)) AS total_males
, COUNT(IF(t.gender='f',1,NULL)) AS total_females
FROM mytable t
Consider that the two expressions in the SELECT list of this query:
SELECT COUNT(1)
, SUM(1)
FROM mytable t
Will return the same value.
If you want to avoid the MySQL IF function, this can also be done using the ANSI SQL CASE expression:
SELECT SUM( CASE WHEN t.gender = 'm' THEN 1 ELSE 0 END )) AS total_males
, SUM( CASE WHEN t.gender = 'f' THEN 1 ELSE 0 END )) AS total_females
FROM mytable t
select sum(case when gender='m' then 1 else null end) as total_males, sum(case when gender='f' then 1 else null end) as total_females from ...
Should work just fine!
If your only issue is to avoid two queries, you can always write two queries as subselects of one query.
Select (select 1 from dual) as one, (select 2 from dual) as two from dual
This would work for your scenario, too.