Select even if count=0 mysql - mysql

So I have 3 tables:
accident, involve and car.
Car (registration_nr, model,year)
involve (licence_nr,registration_nr (FK of car), raport_nr (FK of accident))
accident(raport_nr, date)
I have to count the number of accidents for each car model from Jan 1,2014 but must select also the cars with count 0 (that from jan 01,2014 haven't been in an accident) This is the difficult part for me , till now I can select only those that have been in an accident.
Select c.model,count(m.registration_nr)
from car as c
join involve as i
on i.registration_nr=c.registration_nr
join accident as a
on i.raport_nr=a.raporti_nr
where a._date between '2014-01-01' and curdate()
group by c.model;

You're clearly stating that you only want rows with accidents:
where a._date between '2014-01-01' and curdate()
Just add an OR clause to allow records where a._date IS NULL.

You can use conditional aggregation:
Select c.model, sum(a._date between '2014-01-01' and curdate() )
from car c left join
involve i
on i.registration_nr = c.registration_nr left join
accident a
on i.raport_nr = a.raporti_nr
group by c.model;
I also added left joins -- just in case some models have no accidents at all.

I have encountered the same problem and it took me very long to figure it out.
It turned out that the aggregate function COUNT will return 0 if the collection is all NULL. So here is how you would put the query:
SELECT model, COUNT(registration_nr)
FROM car LEFT JOIN (involve NATURAL JOIN accident) USING (registration_nr)
WHERE date > '01-01-2014' OR date IS NULL
GROUP BY model
In my case, the output would be something like:
Model Count
Mazda 1
Honda 0

Related

Mysql Query where max(time) less than today

I have two tables, the first table ( job ) stores the data and the second table ( job_locations ) stores the locations for each job, I'm trying to show the number of jobs that job locations are less than today
I use the DateTime for the Date Column
unfortunately, the numbers that appear after test the next code are wrong
My code
SELECT *
FROM `job`
left join job_location
on job_location.job_id = job.id
where job_location.cutoff_time < CURDATE()
group by job.id
Please help me to write the working Query.
I think you need to rephrase your query slightly. Select a count of jobs where the cutoff time is earlier than the start of today.
SELECT
j.id,
COUNT(CASE WHEN jl.cutoff_time < CURDATE() THEN 1 END) AS cnt
FROM job j
LEFT JOIN job_location jl;
ON j.id = jl.job_id
GROUP BY
j.id;
Note that the left join is important here because it means that we won't drop any jobs having no matching criteria. Instead, those jobs would still appear in the result set, just with a zero count.
As a note, you can simplify the count (in MySQL). And, assuming that all jobs have at least one location, you don't need a JOIN at all. So:
SELECT jl.job_id, sum( jl.cutoff_time < CURDATE() )
FROM job_location jl
GROUP BY jl.job_id;
If this is not correct (and you need the JOIN), then the condition on the date should go in the ON clause:
SELECT jl.job_id, COUNT(jo.job_id)
FROM job LEFT JOIN
job_location jl
ON jl.job_id = j.id AND jl.cutoff_time < CURDATE()
GROUP BY jl.job_id;

SQL Join with data associated to dates

Currently I have a simple SQL request to get aall group departure date and the associated group size (teamLength) between 2 dates but it doesn't work properly.
SELECT `groups`.`departure`, COUNT(`group_users`.`group_id`) as 'teamLength'
FROM `groups`
INNER JOIN `group_users`
ON `groups`.`id` = `group_users`.`group_id`
WHERE departure BETWEEN '2017-03-01' AND '2017-03-31'
In fact, if I have more than 1 group between the 2 dates, only 1 date will be recovered in association with the total number of teamLength.
For exemple, if I have 2 groups in the same interval with, for group 1, 2 people and for group 2, 1 people, the result will be:
Here are 2 screenshots of the current state of my groups and group_users tables:
Is it even possible to do what I want in only 1 SQL request ? Thanks
In addition to what jarlh commented (JOIN with ON). Don't ever group data without an explicit GROUP BY. I don't know why MYSQL still allows this...
Change your query to something like this and you should get the result you are looking for. Currently, the other departure dates get lost in the aggregation.
SELECT
groups.departure,
COUNT(1) as team_length
FROM
groups
INNER JOIN group_users
ON groups.id = group_users.group_id
WHERE
groups.departure BETWEEN '2017-03-01' AND '2017-03-31'
GROUP BY
groups.departure
I think that you have a syntax issue in your query. You are missing the ON statement so your database could be trying to get a cartesian product since there is no join clause.
SELECT `groups`.`departure`, COUNT(`group_users`.`id`) as 'teamLength'
FROM `groups`
INNER JOIN `group_users` ON `groups`.`id` = `group_users`.`group_id`
WHERE departure BETWEEN '2017-03-01' AND '2017-03-31'
GROUP BY `groups`.`departure`
You also are missing the GROUP BYclause which is not mandatory in all RDBS but it is a good practice to set it.

Getting Total Cost Grouped by User

I've been struggling with this query for two days now. I've got a user table with some values which has a relation with an order table (user can have multiple orders). This table has a relation with order_item (order can have multiple orderItems). Order_Item has a relation with invoice (order_item can have multiple invoices.
The branch and shop have a one-on-one relation with the user.
Here are the most important values of all the tables:
user:
-userId (int)
order
-orderId (int)
-userId (int)
-inserted (date)
order_item
-orderItemId (int)
-orderId (int)
invoice
-invoiceId (int)
-orderItemId (int)
-cost (double)
The foreign keys are self-explanatory here. User->Order->OrderItem->Invoice.
What I need is a query in which each row in the result represents a user with two columns representing the total sales (sum of costs) in 2014 and 2015.
So what it has to do is show each user in a row with some info from the user table (company name, e-mail etc etc) and two columns with the total costs of 2014 and one of 2015 based on the order.inserted date value.
An example would be:
Name: | E-Mail | 2014 Cost | 2015 Cost
Google | info#google.com | €50.000 | €45.000
Now I've gotten so far that I've got a result for the first sum (showing all users regardless of cost), only when I join a second time (to calculate the 2015 cost) my previous sum costs get completely screwed up.
I tried some select queries within joins but I couldnt get any query to work. It's not like I'm a complete beginner in SQL but this is too complex for me to figure out this exact moment.
This is the query I use to get the 2014 result (and as soon as I add a second join for 2015 it gets screwed up):
SELECT t.userId, SUM(i.cost),
t.companyName, t.email,
t.website, t.tel, t.priority,
b.name AS Branch, s.name AS `Shop Name`
FROM `user` AS t
LEFT JOIN branch AS b ON b.branchId = t.branchId
LEFT JOIN shop AS s ON s.shopId = t.shopId
LEFT JOIN `order` AS o ON (o.userId = t.userId AND YEAR(o.inserted) = 2014)
LEFT JOIN order_item AS oi ON oi.orderId = o.orderId
LEFT JOIN invoice AS i ON i.orderItemId = oi.orderItemId
GROUP BY t.userId
I really hope somebody can help me with this. (I'm using mySQL/innoDB in Navicat 8).
Ultimately, this is a form of pivot table you are trying to produce. Instead of joining and testing the year conditions in the joins' ON clauses, you may place the condition directly inside SUM() aggregates like:
-- If the year matches, add the cost value into the sum
-- Otherwise, add zero
SUM(CASE WHEN YEAR(o.inserted) = 2014 THEN i.cost ELSE 0 END) AS `2014 Cost`
This eliminates the need for those extra joins. When applying the GROUP BY, it should include all columns which could potentially be different per group. MySQL allows you to omit columns in SELECT from GROUP BY where most other RDBMS would result in a query compile error.
SELECT
t.userId,
-- Apply the aggregate SUM() conditionally for each year
SUM(CASE WHEN YEAR(o.inserted) = 2014 THEN i.cost ELSE 0 END) AS `2014 Cost`
SUM(CASE WHEN YEAR(o.inserted) = 2015 THEN i.cost ELSE 0 END) AS `2015 Cost`
t.companyName,
t.email,
t.website,
t.tel,
t.priority,
b.name AS Branch,
s.name AS `Shop Name`
FROM
`user` AS t
LEFT JOIN branch AS b ON b.branchId = t.branchId
LEFT JOIN shop AS s ON s.shopId = t.shopId
LEFT JOIN `order` AS o ON (o.userId = t.userId)
LEFT JOIN order_item AS oi ON oi.orderId = o.orderId
LEFT JOIN invoice AS i ON i.orderItemId = oi.orderItemId
GROUP BY
t.userId,
-- Adding remaining SELECT fields
-- though MySQL will allow these to be omitted
-- without breaking this particular query
t.companyName,
t.email,
t.website,
t.tel,
t.priority,
Branch,
`Shop Name`

MySQL getting SUM of balance grouped by user's job location

Im trying to get the SUM of all user balances in a specific month, and grouped by the user's region, which depends on the Point of Sell they work at.
balance
id_balance
date
id_user
value ($$$)
user
id_user
id_pos
name (not relevant)
pos (Point of Sell)
id_pos
id_region
name (not relevant)
location_region
id_region
name (Florida, Texas, etc)
Basically, I would need it to present this data (filtered by month):
location_region.name | SUM(balance.value)
---------------------|-------------------
Florida | 45730
Texas | 43995
I've tried a few approaches with no luck. This was my closest attempt.
SELECT location_region.name, SUM(balance.value) AS money
FROM balance
LEFT JOIN user ON user.id_user
LEFT JOIN pos ON pos.id_pos = user.id_pos
LEFT JOIN location_region ON location_region.id_region = pos.id_region
WHERE balance.date BETWEEN '2014-02-01' AND DATE_ADD('2014-02-01', INTERVAL 1 MONTH)
GROUP BY location_region.id_region
ORDER BY money DESC
Any ideas? Thanks!
Your current query has a logical error, JOIN condition between balance and user tables is incomplete (missing balance.id_user). So instead of balance LEFT JOIN user ON user.id_user you should have balance LEFT JOIN user ON user.id_user=balance.id_user. This is causing the JOINed table to have more rows (number of rows in balance times number of rows in user table). So the final SUM is bringing a way too higher value.
I tried the following query on your sample data (I changed some values) and it seems to be working fine:
SELECT location_region.name, SUM(balance.value) AS money
FROM balance
LEFT JOIN user USING(id_user)
LEFT JOIN pos USING(id_pos)
LEFT JOIN location_region USING(id_region)
WHERE balance.date BETWEEN '2014-02-01' AND DATE_ADD('2014-02-01', INTERVAL 1 MONTH)
GROUP BY location_region.id_region
ORDER BY money DESC
Working demo: http://sqlfiddle.com/#!2/dda28/3
On having a detailed look at your table structure and the query that you gave, what I feel is this mismatch could happen because of duplicate number of rows that might be cropping up due to the JOIN. What I suggest in this case is to find the DISTINCT rows and summing it up so that you get an exact result. Now since SUM DISTINCT is not available in MySQL, you could try this different approach to accomplish what you want:
SELECT location_region.name,
SUM(balance.value)*COUNT(DISTINCT id_user)/COUNT(id_user) AS money
FROM balance
LEFT JOIN user ON user.id_user = balance.id_user
LEFT JOIN pos ON pos.id_pos = user.id_pos
LEFT JOIN location_region ON location_region.id_region = pos.id_region
WHERE balance.date BETWEEN '2014-02-01' AND DATE_ADD('2014-02-01', INTERVAL 1 MONTH)
GROUP BY location_region.id_region
ORDER BY money DESC
In my comment, was wondering why u did not JOIN user table with ON clause as user.id_user = balance.id_user. I have added that however in my query. Hope this helps.

mysql Get missing values

I know this is ordinary question but I need something more. I have an issue about getting values that are not inserted in one table.
Ok here are my tables:
name: importantDates; cols: id, date
name: inserts; cols: id, date, employe_id
My question: how to get missing values for each employe? Let's say I need missing inserts from employe with id=213?
So far, I wrote this, but it doesn't work yet as if there is insert for one worker in one day, it eliminates one day for all workers.
code:
SELECT i.date
FROM importantDates i
LEFT OUTER JOIN inserts s
ON i.date = DATE(s.date)
WHERE i.date BETWEEN '2013-1-1'
AND '2013-2-23'
AND s.date IS NULL;
Now how can I add checking for employe_id?
Thanks guys, if you need anything more I'm always available.
EDIT:
Here is sample:
Employe:
1. sam
2. mike
3. joe
importantDate:
1. 2013-01-01
2. 2013-01-02
3. ...
40. 2013-02-23
inserts:
1. 2013-02-01, 1
2. 2013-02-01, 2
3. 2013-02-01, 3
4. 2013-02-02, 3
5. 2013-02-03, 1
6. 2013-02-03, 2
7. 2013-01-12, 1
So, when I run query, I should get all "missing" inserts. For each employe I should get date and ID of employee when insert is missing. A lot of data but it is important to know which are not inserted and which are.
Assuming you have an employee table, try:
select sq.* from
(select e.employe_id, i.date
FROM importantDates i
CROSS JOIN employee e
WHERE i.date BETWEEN '2013-1-1' AND '2013-2-23') sq
LEFT OUTER JOIN inserts s
ON sq.date = DATE(s.date) and sq.employe_id = s.employe_id
WHERE s.date IS NULL;
If you don't have a separate employee table, you can simulate one by changing employee in the above query to be:
(select distinct employe_id from inserts) as e
Instead of LEFT OUTER JOIN use a simple LEFT JOIN and provide the dates correctly
SELECT
i.date
FROM importantDates i
LEFT JOIN inserts s
ON i.date = DATE(s.date)
WHERE i.date BETWEEN '2013-01-01'
AND '2013-02-23'
AND s.date IS NULL;
I'm not quite sure if I understand, what you're trying to achieve, but if you want to get every row from table_a which is not in table_b you can do this:
SELECT * FROM table_a
WHERE table_a.col NOT IN
(
SELECT col FROM table_b
)
So (if I understand you correctly) in your case:
SELECT i.date FROM importantDates i
WHERE i.date NOT IN
(
SELECT date FROM inserts WHERE employe_id = 213
)
AND i.date BETWEEN '2013-01-01' AND '2013-02-23';
For more documentation to the IN-clause see mysql documentation
UPDATE:
To get the corresponding employee you can alter the statement to this:
SELECT i.date, e.* FROM importantDates i
JOIN employees e
WHERE i.date NOT IN
(
SELECT s.date FROM inserts s WHERE s.employe_id = e.employe_id
)
AND i.date BETWEEN '2013-01-01' AND '2013-02-23';
However, this is not recommendable because the subquery is correlated to the mainquery.