Count null from joined table in MySQL - mysql

I need a count of NULL from 2 tables that are joined in MySQL. Sample data like this:
datefield FROM TABLE calendar (contain dates from start to end of this year)
-----------
TABLE value (data stored)
+------------+-------+
| date | keter |
+------------+-------+
| 2021-08-01 | 11 |
| 2021-08-04 | 0 |
| 2021-08-07 | 20 |
| 2021-08-08 | 15 |
| 2021-08-11 | 0 |
+------------+-------+
I am using the following query to combine and display data from calendar and value tables.
SELECT datefield,keter FROM calendar
LEFT JOIN kehadiran ON datefield=tgl AND id_kar IN ('110101')
WHERE datefield BETWEEN '2021-08-01' AND '2021-08-15' GROUP BY datefield;
result :
+------------+-------+
| datefield | keter |
+------------+-------+
| 2021-08-01 | 11 |
| 2021-08-02 | NULL |
| 2021-08-03 | NULL |
| 2021-08-04 | 0 |
| 2021-08-05 | NULL |
| 2021-08-06 | NULL |
| 2021-08-07 | 20 |
| 2021-08-08 | 15 |
| 2021-08-09 | NULL |
| 2021-08-10 | NULL |
| 2021-08-11 | 0 |
| 2021-08-12 | NULL |
| 2021-08-13 | NULL |
| 2021-08-14 | NULL |
| 2021-08-15 | NULL |
+------------+-------+
I use query based on this question (3 table join counting nulls), I didn't get the result I wanted. The query is this :
SELECT SUM(k.keter) FROM kehadiran k
LEFT OUTER JOIN calendar c ON c.datefield = k.keter AND id_kar IN ('110101')
WHERE datefield BETWEEN '2021-08-01' AND '2021-08-12' AND k.keter is NULL;
result:
+--------------+
| SUM(k.keter) |
+--------------+
| NULL |
+--------------+
the result i wanted :
+--------------+
| SUM(k.keter) |
+--------------+
| 10 |
+--------------+
How should I count NULL from the joined table as mentioned above?

You swapped the tables in your last query which is incorrect. Use the query that worked and use COUNT(*) with WHERE right_table.any_notnull_column IS NULL:
SELECT COUNT(*)
FROM calendar
LEFT JOIN kehadiran k ON datefield=tgl AND id_kar IN ('110101')
WHERE datefield BETWEEN '2021-08-01' AND '2021-08-15'
AND k.keter is NULL

You sum up NULL in your query. And a Sum of NULL is NULL. You should just replace SUM(k.keter) with COUNT(k.keter)
See for a small example

To count NULLs, you can use:
SUM(k.keter IS NULL)
Or:
COUNT(*) - COUNT(k.keter)

Related

MySQLl key-value store ordering with specific condition

I have the following structure:
+----------+--------+---------------------+
| id| gr_id| name | value |
+----------+--------+---------------------+
| 1 | 11 | name | Burro |
| 2 | 11 | submit | 2019/05/10 |
| 3 | 11 | date | 2019/05/17 |
| 4 | 12 | name | Ajax |
| 5 | 12 | submit | 2019/05/10 |
| 6 | 12 | date | 2019/05/18 |
+----------+--------+---------------------+
I have to order it by the date(if the name is date), from highest to lowest date, also it has to keep the groups (gr_id) without mixing the elments.
The desired result would look like this:
+----------+--------+---------------------+
| id| gr_id| name | value |
+----------+--------+---------------------+
| 4 | 12 | name | Ajax |
| 5 | 12 | submit | 2019/05/10 |
| 6 | 12 | date | 2019/05/18 |
| 1 | 11 | name | Burro |
| 2 | 11 | submit | 2019/05/10 |
| 3 | 11 | date | 2019/05/17 |
+----------+--------+---------------------+
How can i implement this?
You'll have to associate the group ordering criteria with all the elements of the group. You can do it through a subquery, or a join.
Subquery version:
SELECT t.*
FROM (SELECT gr_id, value as `date` FROM t WHERE `name` = 'date') AS grpOrder
INNER JOIN t ON grpOrder.gr_id = t.gr_id
ORDER BY grpOrder.`date`
, CASE `name`
WHEN 'name' THEN 1
WHEN 'submit' THEN 2
WHEN 'date' THEN 3
ELSE 4
END
Join version:
SELECT t1.*
FROM t AS t1
INNER JOIN AS t2 ON t1.gr_id = t2.gr_id AND t2.`name` = 'date'
ORDER BY t2.value
, CASE t1.`name`
WHEN 'name' THEN 1
WHEN 'submit' THEN 2
WHEN 'date' THEN 3
ELSE 4
END

How to select the highest value for a given month?

+-------------------------------------------------+-----------------+---------------------+
| landing_page | all_impressions | dates |
+-------------------------------------------------+-----------------+---------------------+
| https://www.example.co.uk/url-1 | 53977 | 2018-08-19 13:59:40 |
| https://www.example.co.uk/url-1 | 610 | 2018-09-19 13:59:40 |
| https://www.example.co.uk/url-1 | 555 | 2018-10-19 13:59:40 |
| https://www.example.co.uk/url-1 | 23 | 2018-11-19 13:59:40 |
| https://www.example.co.uk/ | 1000 | 2018-06-19 13:59:40 |
| https://www.example.co.uk/ | 2 | 2018-07-19 13:59:40 |
| https://www.example.co.uk/ | 4 | 2018-08-19 13:59:40 |
| https://www.example.co.uk/ | 1563 | 2018-09-19 13:59:40 |
| https://www.example.co.uk/ | 1 | 2018-10-19 13:59:40 |
| https://www.example.co.uk/ | 9812 | 2018-11-19 13:59:40 |
+-------------------------------------------------+-----------------+---------------------+
With the above database table, I only want to select the landing_page if the impression count is the max for the current date - For example, from this, the select would return https://www.example.co.uk/ only as the current month it's all_impressions value is it's highest for November (https://www.example.co.uk/url-1  would not be selected as it's highest value was in August)
How might I do this with SQL?
index info:
mysql> show indexes from landing_pages_client_v3;
+-------------------------+------------+--------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |
+-------------------------+------------+--------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| landing_pages_client_v3 | 0 | PRIMARY | 1 | id | A | 24279939 | NULL | NULL | | BTREE | | |
| landing_pages_client_v3 | 1 | profile_id | 1 | profile_id | A | 17 | NULL | NULL | YES | BTREE | | |
| landing_pages_client_v3 | 1 | profile_id | 2 | dates | A | 17 | NULL | NULL | | BTREE | | |
| landing_pages_client_v3 | 1 | profile_id_2 | 1 | profile_id | A | 17 | NULL | NULL | YES | BTREE | | |
| landing_pages_client_v3 | 1 | profile_id_2 | 2 | lp_id | A | 6069984 | NULL | NULL | YES | BTREE | | |
+-------------------------+------------+--------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
In a Derived Table, get the maximum value of all_impressions for every landing_page. Join back to the main table to get the row corresponding to maximum all_impressions value.
We will eventually consider that row only if it belongs to Current Month. For sargability, we will not use functions on the dates column. Instead, we will determine the first day of the current month and next month. We will consider those dates which fall within this range. You can check details of the datetime functions here: https://dev.mysql.com/doc/refman/8.0/en/date-and-time-functions.html
For performance, you may also need to add the following composite index: (landing_page, all_impressions, dates). (I am not sure about which order these columns should be in. Maybe some benchmarking/trial is needed.
SELECT
t.*
FROM
your_table AS t
JOIN
(
SELECT
landing_page,
MAX(all_impressions) AS max_all_impressions
FROM your_table
GROUP BY landing_page
) AS dt
ON dt.landing_page = t.landing_page AND
dt.max_all_impressions = t.all_impressions
WHERE
t.dates >= ((LAST_DAY(CURDATE()) + INTERVAL 1 DAY) - INTERVAL 1 MONTH) AND
t.dates < (LAST_DAY(CURDATE()) + INTERVAL 1 DAY)
You can try like this way to select the landing_page url and maximum value of the all_impressions column. To do that you've to use WHERE clause to check that your dates column value is the same month and year as the CURRENT_DATE number. SEE Date and Time Functions
SELECT landing_page,MAX(all_impressions)
FROM your_table_name_goes_here
WHERE MONTH(dates) = MONTH(CURRENT_DATE())
AND YEAR(dates) = YEAR(CURRENT_DATE())
OR
SELECT landing_page
FROM your_table_name_goes_here
WHERE MONTH(dates) = MONTH(CURRENT_DATE())
AND YEAR(dates) = YEAR(CURRENT_DATE())
ORDER BY all_impressions DESC LIMIT 1
In mysql. you can do like this.
SELECT landing_page,MAX(all_impressions) AS max_count
FROM your_table_name_goes_here
WHERE MONTH(dates) = MONTH(NOW()) AND YEAR(dates) = YEAR(NOW())
GROUP BY landing_page ORDER BY max_count DESC LIMIT 1

How can I treat with NULL as minimum value?

I have a table like this:
// notifications
+----+-----------+-------+---------+---------+------+
| id | score | type | post_id | user_id | seen |
+----+-----------+-------+---------+---------+------+
| 1 | 15 | 1 | 2342 | 342 | 1 |
| 2 | 5 | 1 | 2342 | 342 | 1 |
| 3 | NULL | 2 | 5342 | 342 | 1 |
| 4 | -10 | 1 | 2342 | 342 | NULL |
| 5 | 5 | 1 | 2342 | 342 | NULL |
| 6 | NULL | 2 | 8342 | 342 | NULL |
| 7 | -2 | 1 | 2342 | 342 | NULL |
+----+-----------+-------+---------+---------+------+
-- type: 1 means "it is a vote", 2 means "it is a comment (without score)"
Here is my query:
SELECT SUM(score), type, post_id, seen
FROM notifications
WHERE user_id = 342
GROUP BY type, post_id
ORDER BY (seen IS NULL) desc
As you see, there is SUM() function, Also both type and post_id columns are in the GROUP BY statement. Well now I'm talking about seen column. I don't want to put it into GROUP BY statement. So I have to use either MAX() or MIN() for it. Right?
Actually I need to select NULL as seen column (in query above) if there is even one row which has seen = NULL. My current query selects 1 as seen's value, even when I use MIN(seen). So why 1 is minimum when there is NULL?
Also I want to order rows so that all SEEN = NULL be in the top of list. How can I do that?
Expected result:
// notifications
+-----------+-------+---------+------+
| score | type | post_id | seen |
+-----------+-------+---------+------+
| 13 | 1 | 2342 | NULL |
| NULL | 2 | 8342 | NULL |
| NULL | 2 | 5342 | 1 |
+-----------+-------+---------+------+
You could do this
case when sum(seen is null) > 0
then null
else min(seen)
end
You could use the following query:
SELECT SUM(score), type, post_id, min(IFNULL(seen, 0)) as seen
FROM notifications
WHERE user_id = 342
GROUP BY type, post_id
ORDER BY seen desc

Removing duplicate rows if value from 1 row exists in another row SQL

I'm having an issue removing all the rows that have a certain value in them and then removing the other rows that have the same value as an already removed rows column.
Here is an example of what I have right now:
SELECT Race.intRaceID, Register.intRegID, Member.intMemberID
FROM Race
LEFT JOIN Register ON Race.intRaceID=Register.intRaceID
LEFT JOIN Member ON Register.intMemberID=Member.intMemberID
which gives me:
+------------+-----------+-------------+
| intRaceID | intRegID | intMemberID |
+------------+-----------+-------------+
| 100 | 10 | 1 |
| 100 | 40 | 2 |
| 200 | NULL | NULL |
| 300 | 30 | 2 |
| 400 | 20 | 4 |
| 500 | NULL | NULL |
+------------+-----------+-------------+
So, what I'm attempting to do is remove a particular intMemberID (keeping the NULLs) and all of the intRaceID's they're associated with.
I added
WHERE Member.intMemberID <> 2 OR Member.intMemberID IS NULL
Giving the result:
+------------+-----------+-------------+
| intRaceID | intRegID | intMemberID |
+------------+-----------+-------------+
| 100 | 10 | 1 |
| 200 | NULL | NULL |
| 400 | 20 | 4 |
| 500 | NULL | NULL |
+------------+-----------+-------------+
but that will not remove all intRaceIDs associated with the intMemberID.
Any help would be greatly appreciated
The table I'm trying to show is this:
+------------+-----------+-------------+
| intRaceID | intRegID | intMemberID |
+------------+-----------+-------------+
| 200 | NULL | NULL |
| 400 | 20 | 4 |
| 500 | NULL | NULL |
+------------+-----------+-------------+
I think you have to write the WHERE clause like in the following query:
SELECT Race.intRaceID, Register.intRegID, Member.intMemberID
FROM Race
LEFT JOIN Register ON Race.intRaceID = Register.intRaceID
LEFT JOIN Member ON Register.intMemberID = Member.intMemberID
WHERE Race.intRaceID NOT IN (
SELECT Race.intRaceID
FROM Race
INNER JOIN Register ON Race.intRaceID = Register.intRaceID
INNER JOIN Member ON Register.intMemberID = Member.intMemberID
WHERE Member.intMemberID = 2)
ORDER BY intRaceID;
This way you exclude all records for which intRaceID is related to intMemberID with a value of 2.
Demo here

SQL reduce number of columns in inner query

I have a query:
select
count(*), paymentOptionId
from
payments
where
id in (select min(reportDate), id
from payments
where userId in (select distinct userId
from payments
where paymentOptionId in (46,47,48,49,50,51,52,53,54,55,56))
group by userId)
group by
paymentOptionId;
The problem place is "select min(reportDate), id", this query must return 1 column result, but I can't realize how to do it while I need to group min.
The data set looks like
+----+--------+--------+-----------+---------------------+--------+----------+-----------------+
| id | userId | amount | userLevel | reportDate | buffId | bankQuot | paymentOptionId |
+----+--------+--------+-----------+---------------------+--------+----------+-----------------+
| 9 | 12012 | 5 | 5 | 2014-02-10 23:07:57 | NULL | NULL | 2 |
| 10 | 12191 | 5 | 6 | 2014-02-10 23:52:12 | NULL | NULL | 2 |
| 11 | 12295 | 5 | 6 | 2014-02-11 00:12:04 | NULL | NULL | 2 |
| 12 | 12295 | 5 | 6 | 2014-02-11 00:12:42 | NULL | NULL | 2 |
| 13 | 12256 | 5 | 6 | 2014-02-11 00:26:25 | NULL | NULL | 2 |
| 14 | 12256 | 5 | 6 | 2014-02-11 00:26:35 | NULL | NULL | 2 |
| 16 | 12510 | 5 | 5 | 2014-02-11 00:42:58 | NULL | NULL | 2 |
| 17 | 12510 | 5 | 5 | 2014-02-11 00:43:08 | NULL | NULL | 2 |
| 18 | 12510 | 18 | 5 | 2014-02-11 00:45:16 | NULL | NULL | 3 |
| 19 | 12510 | 5 | 6 | 2014-02-11 01:00:10 | NULL | NULL | 2 |
+----+--------+--------+-----------+---------------------+--------+----------+-----------------+
select count(*), paymentOptionId
from
(select userId, min(reportdate), paymentOptionId
from payments as t1
group by userId, paymentOptionId) as t2
group by paymentOptionId
Fiddle
It first gets the minimum report date (so the first entry) for every user, for every type (so there are two records for a user who has 2 types) and then counts them grouping by type (aka paymentOptionId).
By the way, you can of course cut the attributes chosen in select in from clause, they are only there so you can copy-paste it and see the results it is giving step by step.
You seem to want to report on various payment options and their counts for the earliest ReportDate for each user.
If so, here is an alternative approach
select p.paymentOptionId, count(*)
from payments p
where paymentOptionId in (46,47,48,49,50,51,52,53,54,55,56) and
not exists (select 1
from payments p2
where p2.userId = p.userId and
p2.ReportDate < p.ReportDate
)
group by paymentOptionId;
This isn't exactly the same as your query, because this will only report on the list of payment types, whereas you might want the first payment type for anyone who has ever had one of these types. I'm not sure which you want, though.