MYSQL - COUNT() NULL Values - mysql

This has been racking my head. I've scoured the internet (including this place) and can't find a solution. So as a last resort I was hoping the good people of this forum might be able to help me out.
I have two tables:
TableA
Order_detailsID
OrderID
TitleID
Return_date
TableB
TitleID
Title_name
Quantity_in_stock
And would like to run a query that shows the remaining 'Quantity_in_stock'.
If the 'Return_date' is set to NULL then it means the item is currently out -- so I have been trying to use the count() function for the NULL values and subtract it from the 'Quantity_in_stock'.
This is the script I have so far:
DELIMITER //
CREATE PROCEDURE InStock()
BEGIN
Select TableB.TitleID,
TableB.Title_name,
TableB.Quantity_in_stock AS 'Total_Stock',
COUNT(TableA.return_date IS NULL) AS 'Rented_Out',
TableB.Quantity_in_stock - COUNT(TableA.return_date IS NULL) AS 'Remaining Stock'
From TableB
LEFT JOIN TableA
ON TableA.TitleID = TableB.TitleID
GROUP BY TableB.TitleID;
END//
This works if there is one of more of the TitleIDs at NULL, however if there are no values at NULL, then the Count() is still returning a value of 1 when it should be 0.
What am I doing wrong?

Instead of:
COUNT(TableA.return_date IS NULL)
use this:
SUM(CASE
WHEN TableA.TitleID IS NULL THEN 0
WHEN TableA.return_date IS NOT NULL THEN 0
ELSE 1
END)
The problem with the TableA.return_date IS NULL predicate is that it's true in two completely different situations:
When there is no matching record in TableA
When there is a matching record but TableA.return_date value of this exact record is NULL.
Using the CASE expression you can differentiate between these two cases.

I will like to mention a simple concept here, just keep counting the rows when that particular column is null.
select count(*) from table_name where column_name is null

Related

Why would a Select from Tabke1 where FIeldA not in (Select FieldA from Table2) fail if I manually confirm it should work?

I ran
Select Term from TableA where Term not in (Select Name from TableB)
successfully. Yet when I tried another table
Select Term from TableA where Term not in (Select Name from TableC)
It returns a NULL set. I manuallyt confirmed there are literaly 1000s of records in TableA not in TableB and that the fields are correct.
Is there some other consideration I am not taking into account/understanding about what seems to be a simply query?
Update: Interestingly enough this alternative query worked:
Select Term from TableA where not exist (Select Name from TableB where TableA.Term=TableC.Name)
So while I solved the problem at hand I am still curious why the first query worked for both tables but only the second worked on TableC
For a simple example, consider the following query:
SELECT *
FROM Table
WHERE ID NOT IN (1, 2, NULL);
This is semantically equivalent to:
SELECT *
FROM Table
WHERE ID <> 1 AND ID <> 2 AND ID <> NULL;
The issue is with the last predicate (ID <> NULL). Nothing can be equal to null (not even null) and nothing can be not equal to null. The predicate returns neither true nor false, it returns NULL.
e.g.
ID
ID <> 1
ID <> 2
ID <> NULL
1
FALSE
TRUE
NULL
2
TRUE
FALSE
NULL
3
TRUE
TRUE
NULL
Since you need 3 "TRUE"s to satisfy the AND condition, no rows are returned, since none resolve to true for all 3 predicates.
You can get your NOT IN to work by excluding nulls, e.g.
SELECT Term
FROM TableA
WHERE Term NOT IN (SELECT Name FROM TableC WHERE Name IS NOT NULL);
But for this reason, and the potential for name clashes I always use NOT EXISTS.

If value exists in other table, return another value from that table

I currently have the following SQL query:
SELECT video_calls.initiated_user_id AS user_id,
(CASE
WHEN EXISTS
(SELECT *
FROM patients
WHERE patients.id = video_calls.initiated_user_id)
THEN 'patient'
ELSE (CASE
WHEN EXISTS
(SELECT *
FROM backend_users
WHERE backend_users.id = video_calls.initiated_user_id)
THEN "%%backend%%"
ELSE "unknown"
END)
END) AS user_type
FROM video_calls
WHERE id='7f350a98-93d3-4d21-80a8-6cda3e47a4c0'
UNION
SELECT user_id,
user_type
FROM channel_joins
WHERE channel_id='7f350a98-93d3-4d21-80a8-6cda3e47a4c0'
In the line, where it currently says THEN "%%backend%%" I'd like to return the column backend_users.backend_type instead, for the corresponding row where the value video_calls.initiated_user_id has been found. I suppose I need to work with a JOIN here, but I currently can't figure out where exactly.
You are already using a correlated subquery. You can use that to get the value:
ELSE (SELECT COALESCE(MAX(bu.backend_type), 'unknown')
FROM backend_users bu
WHERE bu.id = video_calls.initiated_user_id
)
Note the use of MAX(). This ensures that exactly one value is returned. If no rows match, the MAX() returns NULL, so 'unknown' is returned.
This has one slight nuance from your pseudo-code. If the matching row is NULL, then this returns 'unknown' rather than NULL. If that is an issue, the logic in the subquery can be tweaked.

How do I search for a value in a column and also get count of the column in a single SQL query

I have a table of following structure in MySQL 5.7:
CREATE TABLE `post_like` (
`post_title_id` varchar(255) NOT NULL,
`user_name` varchar(50) NOT NULL,
PRIMARY KEY (`post_title_id`,`user_name`),
) ENGINE=InnoDB;
and I have the following data
post_title_id user_name
new-story mani27
new-story manipal
some-story manipal
I am trying to get the count of likes for a particular story and also if a particular user has liked the story in a function likeStatus(user_name, post_title_id)
Suppose likeStatus(mani27, new-story) would result in:
count status
2 1
I am using the following query right now and it works right:
SELECT COUNT(user_name) AS count,
COUNT(CASE WHEN `user_name` = ? THEN 1 ELSE null END) as status
FROM post_like WHERE post_title_id = ?
But this would execute the case function on all the rows in the table rather than searching the indexed column user_name.
I could use two different queries to get count and status of username liking a post, but that would take much more time than this. So, is there an optimised way to get this output?
I didn't check the query but this should give you an idea. Try Group By
SELECT COUNT(user_name) AS count,
COUNT(CASE WHEN `user_name` = ? THEN 1 ELSE null END) as status
FROM post_like GROUP BY post_title_id HAVING post_title_id=?
But this would execute the case function on all the rows in the table
rather than searching the indexed column user_name
When you group by basing on post_title_id= and then applying count functions on them, number of row searches for username can be reduced to rows in that group
Add your condition inside CASE not in WHERE, then make sure you use DISTINCT to avoid duplicates:
SELECT COUNT(DISTINCT user_name) AS count,
COUNT(CASE WHEN `user_name` = ? AND post_title_id = ? THEN 1 ELSE null END) as status
FROM post_like
You don't have to scan all the records to see if a user liked the post. Just use a subquery on the select list. That should use the primary key index.
Something like this should work
SELECT COUNT(*),
(SELECT COUNT(*) FROM post_like WHERE postid=? AND userid=?)
WHERE postid=?

MySQL - tell if column _all_ has same value

I'm trying to write a query like
if (select count(*) from Users where fkId=5000 and status='r') =
(select count(*) from Users where fkId=5000) then ..
in just one query.
What this means is, if all the rows that have fkId=5000 also have status=r, then do something.
There can be any number of rows with fkId=5000, and any fraction of those rows could have status=r, status=k, status=l, status=a etc. I'm interested in the case where ALL the rows that have fkId=5000 also have status=r (and not any other status).
The way I'm doing it now is
how many rows with id=5000 and status = 'r'?
how many rows with id=5000?
are those numbers equal? then ..
I'm trying to figure out how to rewrite this query using only 1 query, instead of 2. Keyword ALL didn't seem to be able to write such a query (<> ALL is equivalent to NOT IN). I tried a couple of GROUP BY formulations but could not get the correct result to appear.
The most efficient way to do this is:
if not exists (select 1
from users
where fkid = 5000 and (status <> 'r' or status is null)
)
This will stop the query at the first non-matching row.
I suggest you to check for any rows with status not equal to 'r'
SELECT count(*)>0 FROM Users WHERE fkId = 5000 AND status != 'r'
In the following case, if the number 1 is "true" (which it is) then you'll get Yes back, and if not you'll get No back:
SELECT IF(1, 'Yes', 'No') AS yesorno
(Go ahead -- try it!)
In your case however, the following would be more appropriate:
SELECT IF (
(SELECT COUNT(*) FROM Users WHERE fkId=5000 AND status IN('r') AND status NOT IN('1', 'a', 'k')) = (SELECT COUNT(*) FROM Users WHERE fkId=5000),
'They are equal.',
'They are not equal.'
)
AS are_they_equal
By adding AS, you can manipulate the name of the "column" that's returned to you.
Hope that helps... Also, see this page if you'd like more info.
:)
EASY!
Simply join back to the same table. Here is the complete code for testing:
CREATE TABLE Users(id int NOT NULL AUTO_INCREMENT, fkID int NOT NULL, status char(1), PRIMARY KEY (id));
INSERT Users (fkID, status) VALUES (5000, 'r');
INSERT Users (fkID, status) VALUES (5000, 'r');
INSERT Users (fkID, status) VALUES (5000, 'r');
-- The next query produces "0" to indicate no miss-matches
SELECT COUNT(*) FROM Users u1 LEFT JOIN Users u2 ON u1.id=u2.id AND u2.status='r' WHERE u1.fkID=5000 AND u2.id IS NULL;
-- now change one record to create a miss-match
UPDATE Users SET status='l' WHERE id=3 ;
-- The next query produces "1" to indicate 1 miss-match
SELECT COUNT(*) FROM Users u1 LEFT JOIN Users u2 ON u1.id=u2.id AND u2.status='r' WHERE u1.fkID=5000 AND u2.id IS NULL;
DROP TABLE Users;
So all you need to test for in the result is that it's 0 (zero) meaning everything has fkID=5000 also has status='r'
If you properly index your table then joining back to the same table is not an issue and certainly beats having to do a 2nd query.
Besides the NOT EXISTS version - which should be the most efficient as it does no counting at all and exits as soon as it finds a value that doesn't match the conditions, there is one more way, that will work if status is not nullable and will be efficient if there is an index on (fkId, status):
IF EXISTS
( SELECT 1
FROM Users
WHERE fkId = 5000
HAVING MIN(status) = 'r'
AND MAX(status) = 'r'
)
There is one difference though. The above will show false if there are no rows at all with fkId=5000, while the NOT EXISTS version will show true - which is probably what you want anyway.

Why Mysql Query Count is different?

I have a Customer table, my client want to not physically delete any record from this table so I use a TINYINT field "IsDeleted" to keep track of deleted customers.
Now i m in a situation where i need to exclude Deleted Customers but when i tired following Query it gives me less number of records
select count(*) from customer where IsDeleted <> 1; (Count = 1477)
then the following
select count(*) from customer where (IsDeleted = 0 or IsDeleted is null); (Count = 1552)
why the above query counts are different?
why "NULL" value is not counted in " IsDeleted <> 1" check?
Please suggest.
Like Duniyadnd and triclosan point out this is caused by the column type for IsDeleted.
Change the query in the right panel so you can see the difference between using int and varchar column types. sqlfiddle.com/#!2/7bf0a/5
You cannot correct use comparing operations for NULL-values. Consider to change type of IsDeleted to enum('N','Y') with Not NULL option.
Null is not an int. As soon as you did <> 1, that means it would only look at ints. Null ideally means something that does not exist (that's why a lot of people use it instead of 0 in case you do store 0s in the table).
If you only have null, 0 and 1 values in the isDeleted column, you would probably find a difference between the two queries (1522-1477) to be the total number of nulls in your table (75).