MySQL LEFT JOIN SUM doen not include 0 - mysql

Guess there are many varianat of this question, however this has a twist.
My primary table contains logged kilometers for certain dates for certain users:
Table km_run:
|entry|mnumber|dato |km | where 'dato' is the specific date. Formats are like:
|1 |3 |2013-01-01|5.7|
For a specific user ('mnumber') I want to calculate the sum in each week of a year. For this purpose I have made a 'dummy-table' just containing the week numbers from 1 to 53:
Table `week_list`:
|week|
|1 |
|2 |
etc..
This query gives the sum, however I cannot find a way to return a zero if there are no entries in 'km_run' for the specific week.
SELECT `week_list`.`week`, WEEKOFYEAR(`km_run`.`dato`), SUM(`km_run`.`km`)
FROM `week_list` LEFT JOIN `km_run` ON WEEKOFYEAR(`dato`) = `week_list`.`week`
WHERE `km_run`.`mnumber` = 3 AND `km_run`.`dato` >= '2013-01-01'
AND `km_run`.`dato` < '2014-01-01'
GROUP BY WEEKOFYEAR(`dato`)
I have tried to do COALESCE( SUM(km),0) and I have also tried to use the IFNULL function around the sum. Despite the left join, not all records from week_list are returned in the sql statement.
Here's the result:
week | WEEKOFYEAR(`km_run`.`dato`) | SUM(`km_run`.`km`)
1 | 1 | 58.4
3 | 3 | 50.7
4 | 4 | 39.2
As you can see, week two is skipped instead of returning a 0

Firstly JOIN works, creating such rows:
week=2 weekofyear=null mnumber=null sum=0 ...
Then, WHERE clause (for example, where mnumber=3) excludes rows with nulls.
You could try something like this:
SELECT week, SUM(km) FROM (
(SELECT km_run.km AS km, WEEKOFYEAR(km_run.dato) AS week
FROM km_run
WHERE mnumber = 3 AND km_run.dato >= '2013-01-01' AND km_run.dato < '2014-01-01')
UNION
(SELECT 0 AS km, week_list.week as week FROM week_list)
) GROUP BY week

Related

mysql getting where clause weather the count is 0 or not

I wanted to show assigned_user_id in the Column User_ID whether the Meeting_Count is 0 or having value.
With below Query I'm getting only
+---------+---------------+
| User_ID | Meeting_Count |
+---------+---------------+
| NULL | 0 |
+---------+---------------+
SELECT
meeting.assigned_user_id AS User_ID,
COUNT(*) AS Meeting_Count
FROM meeting
WHERE meeting.assigned_user_id = '5c3e31bb4b708eeb5'
AND meeting.date_start >= NOW() - INTERVAL 60 DAY
My Requirement is to get as this,
+-------------------+---------------+
| User_ID | Meeting_Count |
+-------------------+---------------+
| 5c3e31bb4b708eeb5 | 0 |
+-------------------+---------------+
Can anyone help me on this. I'm still a learner.
The where clause filters out rows that do not belong to the given user, resulting in an empty resultset if no row at all matches. One workaround is to move the filtering within a conditional expression. In MySQL, you would phrase this as:
select
'5c3e31bb4b708eeb5' as user_id,
sum(assigned_user_id = '5c3e31bb4b708eeb5') as meeting_count
from meeting
where date_start >= now() - interval 60 day
If there is a possibility that the table contains no rows for the last 60 days, and you still want a result in that case, then you can take on step forward and move the date filtering to the sum() as well:
select
'5c3e31bb4b708eeb5' as user_id,
sum(assigned_user_id = '5c3e31bb4b708eeb5' and date_start >= now() - interval 60 day) as meeting_count
from meeting
This is guaranteed to always return one row.
It should be noted that conditional aggregation is by nature less efficient than filtering with a where clause.

SQL consecutive occurrences for availability based query

I am a bit stuck trying to create a pretty complex on SQL, and more specifically MySQL.
The database deals with car rentals, and the main table of what is a snowflake patters looks a bit like:
id | rent_start | rent_duration | rent_end | customerID | carId
-----------------------------------------------------------------------------------
203 | 2016-10-03 | 5 | 2016-11-07 | 16545 | 4543
125 | 2016-10-20 | 9 | 2016-10-28 | 54452 | 5465
405 | 2016-11-01 | 2 | 2016-01-02 | 43565 | 346
My goal is to create a query that allows given
1) A period range like, for example: from 2016-10-03 to 2016-11-03
2) A number of days, for example: 10
allows me to retrieve the cars that are actually available for at least 10 CONSECUTIVE days between the 10th of October and the 11th.
A list of IDs for those cars is more than enough... I just don't really know how to setup a query like that.
If it can help: I do have a list of all the car IDs in another table.
Either way, thanks!
I think it is much simpler to work with availability, rather than rentals, for this purpose.
So:
select r.car_id, r.rent_end as avail_start,
(select min(r2.rent_start
from rentals r2
where r2.car_id = r.car_id and r2.rent_start > r.rent_start
) as avail_end
from rentals r;
Then, for your query, you need at least 10 days. You can use a having clause or subquery for that purpose:
select r.*
from (select r.car_id, r.rent_end as avail_start,
(select min(r2.rent_start
from rentals r2
where r2.car_id = r.car_id and r2.rent_start > r.rent_start
) as avail_end
from rentals r
) r
where datediff(avail_end, avail_start) >= $days;
And finally, you need for that period to be during the dates you specify:
select r.*
from (select r.car_id, r.rent_end as avail_start,
(select min(r2.rent_start
from rentals r2
where r2.car_id = r.car_id and r2.rent_start > r.rent_start
) as avail_end
from rentals r
) r
where datediff(avail_end, avail_start) >= $days and
( (avail_end > $end and avail_start < $start) or
(avail_start <= $start and avail_end >= $start + interval 10 day) or
(avail_start > $start and avail_start + interval 10 day <= $end)
);
This handles the various conditions where the free period covers the entire range or starts/ends during the range.
There are no doubt off-by-one errors in this logic (is a car available the same date it returns). The this should give you a solid approach for solving the problem.
By the way, you should also include cars that have never been rented. But that is not possible with the tables you describe in the question.

Retrieving data from two different tables with common column using left outer join and on clause

Guys I have two different tables.
Table-1:
contract-id | contract-date
1 | 01-05-2016
1 | 01-06-2016
1 | 01-07-2016
2 | 01-01-2016
2 | 01-02-2016
3 | 01-05-2016
3 | 01-06-2016
Table-2:
invoice-number|contract-id|invoice-from-date|invoice-to-date
1 |1 |01-05-2016 |31-05-2016
2 |1 |15-05-2016 |31-05-2016
3 |1 |25-05-2016 |31-05-2016
My requirement is, I have to show what are all the contracts that comes in the current month from table-1 and I have to show the count of the invoice raised for those contracts from table-2. If there are any invoice raised, it should show its count or simply zero. The expected answer is:
contract-id|counts
1 |3
3 |0
The count for contract-id: 1 is 3, because I've raised three invoices for the current month and the count for contract-id: 3 is 0, because I haven't raised any invoice for it. And contract-id:2 is missing , because, it does not come under current month.
This is what I've tried:
select cbm_contract_period_value_split.contract_id,
count(cbm_contract_invoice_request.contract_id),
cbm_contract_period_value_split.contract_date
from cbm_contract_period_value_split
left outer join cbm_contract_invoice_request
on cbm_contract_period_value_split.contract_id = cbm_contract_invoice_request.contract_id
and MONTH(cbm_contract_period_value_split.contract_date) = MONTH(CURDATE())
and MONTH(cbm_contract_invoice_request.invoice_for_the_period_of_from_date) = MONTH(CURDATE())
I've already posted this question, but now I'm posting this with an edited version with the answers I got. Yet, I'm not getting the desired answer. What's wrong and what should I do?
try this one. I think you need a GROUP BY and I put in the WHERE to filter out the rows that aren't current month.
select pvs.contract_id,
count(ir.contract_id) as counts,
pvs.contract_date
from cbm_contract_period_value_split as pvs
left join cbm_contract_invoice_request as ir
on pvs.contract_id = ir.contract_id
and MONTH(ir.invoice_for_the_period_of_from_date) = MONTH(CURDATE())
WHERE MONTH(pvs.contract_date) = MONTH(CURDATE())
GROUP BY pvs.contract_id,pvs.contract_date
http://sqlfiddle.com/#!9/e5f08c/9
result
contract_id counts contract_date
1 3 May, 01 2016 00:00:00
3 0 May, 01 2016 00:00:00
Try this query..
SELECT
t1.`contract-id`,
(
CASE
WHEN t2.`contract-id` IS NULL
THEN 0
ELSE COUNT(*)
END
) AS counts
FROM
t1
LEFT JOIN t2
ON t1.`contract-id` = t2.`contract-id`
WHERE MONTH(`contract-date`) = MONTH(CURDATE())
GROUP BY t2.`contract-id`
ORDER BY t1.`contract-id`,`contract-date` ;
FIDDLE
SELECT Table-1.contract_id, Table-1.contract-date, COUNT(*) AS count FROM Table-2 LEFT JOIN Table-1 ON Table-1.contract_id = Table-2.contract_id WHERE MONTH(Table-1.contract-date) = MONTH(CURDATE()) GROUP BY Table-1.contract_id

Select a default for missing rows in mysql

I have a simple table to keep count of the number of visitors on a website.
|Day|Visitors|
|1 |2 |
|2 |5 |
|4 |1 |
I want to select the number of visitors per day for days 1 to 4, but I also want a value for day 3. Since day 3 is missing, I wonder if it is possible to select all integers in a range, and if the column is missing, a default is to be returned. A simple "SELECT visitors FROM table WHERE day >= 1 AND day <= 4 ORDER By day" query will return "2, 5, 1", but the query I'm looking for will return "2, 5, 0, 1".
Here is an example for your data:
select n.n as days, coalesce(visitors, 0) as visitors
from (select 1 as n union all select 2 union all select 3 union all select 4
) n left outer join
t
on t.days = n.n;
You need to fill in all the numbers of days in the n subquery. Perhaps you have another table with sequential numbers which can help with this and other queries.
Use the power of the scripting language that you are using for the website to check for the missing days and show 0 for those days
If you REALLY NEED to get this from the database, you can use a table to hold the day numbers and do a LEFT JOIN with it:
SELECT coalesce(table.visitors, 0) AS visitors
FROM days_table
LEFT JOIN table ON days_table.day = table.day
WHERE table.day >= 1 AND day <= 4 ORDER By day

MySQL - Count Yearly Totals when some Years have nulls

I have 1 table with similar data:
CustomerID | ProjectID | DateListed | DateCompleted
123456 | 045 | 07-29-2010 | 04-03-2011
123456 | 123 | 10-12-2011 | 11-30-2011
123456 | 157 | 12-12-2011 | 02-10-2012
123456 | 258 | 06-07-2011 | NULL
Basically, a customer contacts us, we get a project on our list, and we mark it completed when we're done with it.
What I'm after is a simple (you'd think, at least) count of all projects, with expected output like below:
YEAR | TotalListed | TotalCompleted
2010 | 1 | 0
2011 | 3 | 2
2012 | 0 | 1
However, my query below - because of the join - isn't showing 2012's count, because there's been no listed project for 2012. However, I can't really reverse the query, as then 2010's count wouldn't show up (since nothing was completed in 2010).
I'm open to any suggestions, or tips like how to do this. I've pondered a temp table, is that the best way to go? I'm open to anything that gets me what I need!
(If the code looks familiar, ya'll helped me get the subquery made! MySQL Subquery with main query data variable)
SELECT YEAR(p1.DateListed) AS YearListed, COUNT(p1.ProjectID) As Listed, PreQuery.Completed
FROM(
SELECT YEAR(DateCompleted) AS YearCompleted, COUNT(ProjectID) AS Completed
FROM projects
WHERE CustomerID = 123456 AND DateListed >= DATE_SUB(Now(), INTERVAL 5 YEAR)
GROUP BY YEAR(DateCompleted)
) PreQuery
RIGHT OUTER JOIN projects p1 ON PreQuery.YearCompleted = YEAR(p1.DateListed)
WHERE CustomerID = 123456 AND DateListed >= DATE_SUB(Now(), INTERVAL 5 YEAR)
GROUP BY YearListed
ORDER BY p1.DateListed
After reviewing your table, query, and expected results - I believe I have found a more-revised query to suit your needs. It is a fairly-full rewrite of your existing query though, but I've tested it with your given data and received the same results you want/expect:
SELECT
years.`year`,
SUM(IF(YEAR(DateListed) = years.`year`, 1, 0)) AS TotalListed,
SUM(IF(YEAR(DateCompleted) = years.`year`, 1, 0)) AS TotalCompleted
FROM
projects
LEFT JOIN (
SELECT DISTINCT `year` FROM (
SELECT YEAR(DateListed) AS `year` FROM projects
UNION SELECT YEAR(DateCompleted) AS `year` FROM projects WHERE DateCompleted IS NOT NULL
) as year_inner
) AS years
ON YEAR(DateListed) = `year`
OR YEAR(DateCompleted) = `year`
WHERE
CustomerID = 123456 AND DateListed >= DATE_SUB(Now(), INTERVAL 5 YEAR)
GROUP BY
years.`year`
ORDER BY
years.`year`
To explain, we should start with the inner query (aliased as year_inner). It selects a full list of years in the DateListed and DateCompleted columns and then selects a DISTINCT list of those to create the years alias sub-query. This sub-query is used to get a full list of "years" that we want data for. Doing it this way, opposed to a sub-query with counts and groupings will allow you to only have to define the WHERE clause on the outermost query (though, if efficiency becomes an issue with thousands and thousands of records, you could always add a WHERE clause to the inner query too; or an index to the date columns).
After we've built our inner queries, we join the projects table on the results with a LEFT JOIN for the DateListed or DateCompleted's YEAR() value - which will allow us to bring back null columns too!
For the field selections, we use the year column from our inner query to assure that we get a full list of years to display. Then, we compare the current row's DateListed & DateCompleted YEAR() value to the current year; if they're equal, add 1 - else add 0. When we GROUP BY year, our SUM() will count all of the 1's for that year for each column and give you the output you want (hopefully, of course =P).