Query to include non-existent rows as 0 value - mysql

Although there are a number of similar questions on SO, I could not find any answer that would give me the desired output.
Situation: table "purchaseorders" containing POs with various details. Example record:
| id | orderId | number | vendorNumber | grossValue | bookingYear |
|----|---------|--------|--------------|------------|-------------|
| 14 | 691 | 1 | 12345 | 152.43 | 2016 |
I want aggregate sums(grossValue) for specific vendors for the past 5 years, including 0 values.
Required output:
Current actual output:
My attempt at SQL:
SELECT po.bookingYear, IFNULL (sum(po.grossValue),0) FROM purchaseorder as po
LEFT JOIN (select 2016 as yr UNION ALL select 2017 as yr UNION ALL select 2018 as yr UNION ALL select 2019 as yr UNION ALL select 2020 as yr) as years ON po.bookingYear=years.yr
WHERE po.vendorNumber=12345 group by po.bookingYear;
I started out with a simple "WHERE bookingYear IN (...)", but since there are no records for the past three years, none are returned. Searching here and on Google suggested I left join a subquery like above (though probably not exactly like above).
I think the biggest issue here is, that I don't have NULL values for certain fields in certain years but simply no records at all for those years for some vendors.
Can anyone point me in the right direction? My SQL has become a bit rusty it seems. Speaking of: yes: mySQL.

Right idea. Wrong order. The years go first:
SELECT years.yr, COALESCE(SUM(po.grossValue), 0)
FROM (select 2016 as yr UNION ALL select 2017 as yr UNION ALL select 2018 as yr UNION ALL select 2019 as yr UNION ALL select 2020 as yr
) years LEFT JOIN
purchaseorder po
ON po.bookingYear = years.yr AND
po.vendorNumber = 12345
GROUP BY years.yr;
Note the other changes to the query:
The filtering on vendorNumber is now in the ON clause rather then WHERE clause. In the WHERE clause it would turn the outer join to an inner join.
The GROUP BY and SELECT use years.yr. The value po.bookingYear might be NULL.
I prefer COALESCE(), the SQL standard function, to IFNULL() a bespoke MySQL function.

If you are running MySQL 8.0, you can use a recursive query to generate the dates. This incurs less typing than union all if you have a lot of years:
with years as (
select 2016 bookingYear
union all select bookingYear + 1 from years where bookingYear < 2020
)
select y.bookingYear, coalesce(sum(po.grossValue), 0) grossValue
from years y
left join purchaseorder po on po.bookingYear = y.bookingYear and po.vendorNumber = 12345
group by y.bookingYear

Related

MYSQL - Select count with group by with only a table

I have these data
idhouse year
7 2016
2 2018
2 2017
3 2017
4 2015
14 2003
3 2018
5 2018
4 2017
4 2018
I want to counting the number of houses belong to two years.
I tried with mysql select but didn't work.
How I should do it?
EDITED
Sorry for my bad explanation.
I have only one mysql table.
Filtering by 2017 and 2018 and count the numbrer these houses, I should get these match:
idhouse year
7 2016
2 2018*
2 2017*
3 2017*
4 2015
14 2003
3 2018*
5 2018
4 2017*
4 2018*
And the SELECT should be show 3
I assume a house can only appear once in a year. Try this:
SELECT
COUNT(*) nb_houses
FROM (SELECT house
FROM yourTable
GROUP BY house
HAVING COUNT(*)>1) A;
See this run on SQL Fiddle.
Assuming a PK on (house,year), if you just want to know how many houses are listed more than once, you can do this...
SELECT COUNT(DISTINCT x.house) total
FROM my_table x
JOIN my_table y
ON y.house = x.house
AND y.year <> x.year;
I asuming you're doing a web app with this question
SELECT house, COUNT(year) as count_year FROM table GROUP BY house HAVING COUNT(year) = 2
By using your data above, the result will be
house | count_year
____2 | _______2
Then if you are using server side scripting like PHP use mysqli_num_rows for get the number of row(s).
Or if you use other language, just adjust the algorithm to get the number of row(s)
With this Select I get it:
select count(*) from (select house from yourTable Where year = 2018 and house in (select house from yourTable where year = '2017')) A;
But can we improve this Select in terms of efficiency?
You can try here
Thanks.

Displaying values that occur consecutively

I'm trying to display a list of all Directors who have directed 2 years consecutively.
Given the following data:
Pantomime table:
Year titleID DirectorID
2000 1 1
2001 2 7
2002 3 7
2003 4 8
2004 5 9
2005 6 9
This is the desired outcome:
DirectorID
7
9
This is the query I have tried so far but was unable to get the desired results.
SELECT directorID
FROM pantomime
where directorID = directorID+1
GROUP BY directorID
One method uses exists:
select distinct p.directorId
from pantomine p
where exists (select 1
from pantomine p2
where p2.directorId = p.directorId and p2.year = p.year + 1
);
There are other fun variants on this idea, such as using in:
select distinct p.directorId
from pantomine p
where p.year in (select p2.year + 1
from pantomine p2
where p2.directorId = p.directorId
);
And here is a totally arcane method that doesn't use join-like mechanisms at all (just aggregation):
select distinct directorId
from ((select directorId, year from pantomine)
union all
(select directorId, year + 1 from pantomine)
) p
group by directorId, year
having count(*) = 2;
This is also one of those really, really rare cases of using select distinct with group by.
you can use join to see which entries has the next year's value, and then with distinct get the relevant id's:
select distinct a.directorID
from Pantomime as a
inner join Pantomime as b on a.year = b.year-1
and a.directorID = b.directorID;
since I'm using inner join, we'll get records from a only if they exist in b- meaning if year-1 appears in your table for this directorId
Try this, no joins or sub-queries, just a simple grouping:
SELECT directorID
FROM pantomime
GROUP BY directorID
HAVING COUNT(*) = 2
AND MAX(Year) = MIN(Year) + 1
Here is a fiddle.

Get last 7 days count even when there are no records

How can I make a SQL query that returns me something like
---------------------
|DATE | Count |
---------------------
|2015/01/07 | 7 |
|2015/01/06 | 0 |
|2015/01/05 | 8 |
|2015/01/04 | 5 |
|2015/01/03 | 0 |
|2015/01/02 | 4 |
|2015/01/01 | 2 |
---------------------
When there are no records for the 6th and 3rd?
You need a table of all the sequence numbers from 0 to 6. This is easy to generate in a simple query, as follows.
SELECT 0 AS seq
UNION ALL SELECT 1 UNION ALL SELECT 2
UNION ALL SELECT 3 UNION ALL SELECT 4
UNION ALL SELECT 5 UNION ALL SELECT 6
Next, let's use this to construct a virtual table of seven dates. For this example, we pick today and the six preceding days.
SELECT DATE(NOW())-INTERVAL seq.seq DAY theday
FROM (
SELECT 0 AS seq
UNION ALL SELECT 1 UNION ALL SELECT 2
UNION ALL SELECT 3 UNION ALL SELECT 4
UNION ALL SELECT 5 UNION ALL SELECT 6
) seq
Then you do your summary query. You didn't say exactly how it goes so I will guess. This one gives you the records from six days ago until today. Today is still in progress.
SELECT DATE(i.item_time) theday
COUNT(*) `count`
FROM items i
WHERE i.item_time >= DATE(NOW()) - INTERVAL 6 DAYS
GROUP BY DATE(i.item_time)
Finally, starting with the list of days, let's LEFT JOIN that summary to it.
SELECT thedays.theday, IFNULL(summary.`count`,0) `count`
FROM (
SELECT DATE(NOW())-INTERVAL seq.seq DAY theday
FROM (
SELECT 0 AS seq
UNION ALL SELECT 1 UNION ALL SELECT 2
UNION ALL SELECT 3 UNION ALL SELECT 4
UNION ALL SELECT 5 UNION ALL SELECT 6
) seq
) thedays
LEFT JOIN (
SELECT DATE(i.item_time) theday
COUNT(*) `count`
FROM items i
WHERE i.item_time >= DATE(NOW()) - INTERVAL 6 DAYS
GROUP BY DATE(i.item_time)
) summary USING (theday)
ORDER BY thedays.theday
It looks complex, but it is simply the combination of three basic queries. Think of it as a sandwich, with bread and cheese and tomato stuck together with an ORDER BY toothpick.
Here's a more thorough writeup. http://www.plumislandmedia.net/mysql/filling-missing-data-sequences-cardinal-integers/
MariaDB version 10 has built-in virtual tables of cardinal number sequences like seq_0_to_6. This is convenient.
You need to build a dummy dates table and left join your current table against it.
SELECT dummy.date, SUM(IFNULL(yourtable.record,0)) recordcount
FROM dummy
LEFT JOIN yourtable on dummy.date=yourtable.date
GROUP BT dummy.date
please note that I'm replacing nulls with a zero.
One solution is to create a calendar table containing all the dates you need. You can then left join it to your data to get what you are after
First of all you have to use left join, converting NULLs to 0s using the IFNULL function. Try to match your table and use left join.

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).

Select a DISTINCT ID, then pull data from another table

I have 2 MySQL tables, one for parts, and one for years. I can't figure out how to make a table on stackoverflow.. keeps making headers so here's my weak attempt to explain what I need.
Table 1
id | part_id | years
====================
0 | 15 | 1945
1 | 15 | 1946
2 | 16 | 1944
3 | 16 | 1947
4 | 16 | 1948
5 | 17 | 1953
As you may have guessed, part_id is the id number of the part in the parts table. Now, I know I have this to pull out a distinct part id, based on the year. That part is easy.
SELECT DISTINCT part_id FROM `years` WHERE year BETWEEN 1945 AND 1949
This is just an example, but that works exactly like I want it to. Gives me
15 and 16. Just one time. Which is great.
Now, do I need to do a loop in php to get the information from parts? I'm not sure how to do a join here.
<?php
foreach($item_pulled_from_db as $newid) {
$query = "SELECT * FROM 'parts' WHERE id = $newid";
} // I know there's more stuff to do in here, just a basic overview for you to look at
?>
Should I do the above code? Is there a way to select a DISTINCT part_id and then pull the data from another table for that ID in MySQL? Or do I have to do a loop like this?
Edit: I hope this isn't too confusing of a question. I'm not very good with words, which is why I like to program. :)
Use a join:
SELECT parts.*
FROM parts
JOIN (SELECT DISTINCT part_id
FROM years
WHERE year BETWEEN 1945 AND 1949) years
ON parts.id = years.part_id
You could pull this off using a JOIN in a single query. Try:
SELECT `parts`.* FROM `parts`
INNER JOIN `years` ON `years`.`part_id` = `parts`.`id`
WHERE `years`.`year` BETWEEN 1945 AND 1949
Execute that single query from PHP and then fetch the result set. It should be the same as what you would get using the multiple queries.
This query give you the result you want:
SELECT DISTINCT
p.*
FROM
years y
INNER JOIN
parts p ON p.id = y.part_id
WHERE
y.year BETWEEN 1945 AND 1949