MYSQL fill group by "gaps" - mysql

I´m trying to fill the gaps after using group by using an aux table, can you help?
aux table to deal with days with no orders
date quantity
2014-01-01 0
2014-01-02 0
2014-01-03 0
2014-01-04 0
2014-01-05 0
2014-01-06 0
2014-01-07 0
group by result from "orders" table
date quantity
2014-01-01 7
2014-01-02 1
2014-01-04 2
2014-01-05 3
desired result joining "orders" table with "aux table"
date quantity
2014-01-01 7
2014-01-02 1
2014-01-03 0
2014-01-04 2
2014-01-05 3
2014-01-06 0
2014-01-07 0

Without knowing how you create your group by result table, what you're looking for in an outer join, perhaps with coalesce. Something like this:
select distinct a.date, coalesce(b.quantity,0) quantity
from aux a
left join yourgroupbyresults b on a.date = b.date
Please note, you may or may not need distinct -- depends on your data.
Edit, given your comments, this should work:
select a.date, count(b.date_sent)
from aux a
left join orders b on a.date = date_format(b.date_sent, '%Y-%m-%d')
group by a.date
SQL Fiddle Demo

Using your results it would be something like:
SELECT a.date
,COALESCE(b.quantity,0) as quantity
FROM auxtable a
LEFT JOIN groupbyresult b
ON a.date = b.date
You can also do your grouping in the same query as the left join:
SELECT a.date
,COALESCE(COUNT(b.somefield),0) as quantity
FROM auxtable a
LEFT JOIN table1 b
ON a.date = b.date
GROUP BY a.date

One familiar approach to solving a problem like this is to use a row source that has the distinct list of dates you want to return, and then do an outer join to the table that has gaps. That way, you get all the dates back, and you can substitute a zero for the "missing" quantity values.
For example:
SELECT d.date
, IFNULL(SUM(s.quantity),0) AS quantity
FROM distinct_list_of_dates d
LEFT
JOIN information_source s
ON s.date = d.date
GROUP BY d.date
It's not clear why a GROUP BY would be eliminating some date values. We might conjecture that you are using a MySQL extension to ANSI-standard GROUP BY semantics, and that is eliminating rows. Or, you may have a WHERE clause that is excluding rows. But we're just guessing.
FOLLOW UP based on further information revealed by OP in comments...
In the query above, replace distinct_list_of_dates with aux, and replace information_source with orders, and adjusting the join predicate to account for datetime comparison to date
SELECT d.date
, IFNULL(SUM(s.quantity),0) AS quantity
FROM aux d
LEFT
JOIN orders s
ON s.date >= d.date
AND s.date < d.date + INTERVAL 1 DAY
GROUP BY d.date

Related

MySQL Query trying to (CROSS?) JOIN on one table

Been trying to figure this out for a couple hours and hoping for some expert assistance:
I have a single Mysql table with data such as:
Date version amount
2021-03-01 A 100
2021-03-02 A 35
2021-03-02 B 80
2021-03-03 A 7
2021-03-03 B 90
2021-03-03 C 3
2021-03-03 A 8
2021-03-04 B 15
2021-03-04 C 90
2021-03-04 B 10
And trying to get output for each version for every day, with amount populated as '0' for null;
Result:
Date version SUM(amount)
2021-03-01 A 100
2021-03-01 B 0
2021-03-01 c 0
2021-03-02 A 35
2021-03-02 B 80
2021-03-02 C 0
2021-03-03 A 15
2021-03-03 B 90
2021-03-03 C 3
2021-03-04 A 0
2021-03-04 B 25
2021-03-04 C 90
I tried various 'JOIN', 'LEFT JOIN' and 'CROSS JOIN' permutations without success.
SELECT distinct c1.date, c2.version
FROM crash_log c1
LEFT OUTER JOIN crash_log c2 ON c1.date = c2.date
GROUP BY c1.date, c2.version
(not even messing with the SUM, just trying to get all the rows with this one)
For now, I have a script that does this by brute force: gets DISTINCT date, then get DISTINCT version, then do a nested loop and build an array for each combination. One trouble is it's not scalable and seems the web connection is timing out before the process finishes on a large set.
I'm thinking there's one (semi-?) efficient query that can do this, but I haven't been able to figure it out.
Write subqueries to get all the dates and versions. Cross join these to get every combination.
Then left join that with the table to get either the actual value or default to 0 when NULL.
SELECT d.date, v.version, IFNULL(c.sum, 0) AS sum
FROM (
SELECT DISTINCT date
FROM crash_log) AS d
CROSS JOIN (
SELECT DISTINCT version
FROM crash_log) AS v
LEFT JOIN (
SELECT date, version, SUM(amount) AS sum
FROM crash_log
GROUP BY date, version) AS c ON d.date = c.date AND v.version = c.version
ORDER BY d.date, v.version
Just like your script, but in SQL.
Cross join the distinct dates to the distinct versions and left join to the table and finally aggregation:
SELECT d.Date, v.version, COALESCE(SUM(t.amount), 0) sum_amount
FROM (SELECT DISTINCT Date FROM tablename) d
CROSS JOIN (SELECT DISTINCT version FROM tablename) v
LEFT JOIN tablename t
ON t.Date = d.Date AND t.version = v.version
GROUP BY d.Date, v.version

MySQL multiple sums, using numbers from multiple tables

Let's say I have two tables, one storing accounts, one storing transactions :
id acct_name opening_bal
1 checking 1029.99
2 savings 2002.19
...
And
id date amount from_acct to_acct
...
99 2018-01-21 12.15 1 2
100 2018-01-21 9.99 4 1
101 2018-01-23 10.01 5 2
...
For example, row 99 in the transactions table is saying that 12.15 was transfered from checking to savings on 21 Jan 2018.
I would like to write a query that returns all accounts together with their balances on a given date (like today or 12 Oct 2018, etc), something like this :
acct balance
checking 1599.21
savings 2221.99
...
How would I write such a query?
Edit: Here's a solution, which is close enough to what I want (it just has an additional id column). You can replace CURDATE() with an arbitrary date to get the corresponding table of balances on that date.
SELECT id, acct_name, opening_bal+amt-amt2 as balance FROM accounts
INNER JOIN
(SELECT to_acct, sum(amount) AS amt
FROM transactions
WHERE date <= CURDATE()
GROUP BY to_acct) as T2
ON accounts.id=T2.to_acct
INNER JOIN
(SELECT from_acct, sum(amount) AS amt2
FROM transactions
WHERE date <= CURDATE()
GROUP BY from_acct) as T3
ON T2.to_acct = T3.from_acct
;
Something like this. If you'd provided more data, answer could be more precise. Table1 is your first table, Table2 is the second.
select acct_name as acct,
opening_bal - t1.put_money + t2.get_money as balance
from Table1
left join (select from_acct, ifnull(sum(amount),0) as put_money from Table2 group by from_acct) t1
on t1.from_acct = Table1.id
left join (select to_acct, ifnull(sum(amount),0) as get_money from Table2 group by to_acct) t2
on t2.to_acct = Table1.id;

How can I join two unrelated mysql table and use group by date

I have 2 tables voucher and ledger the fields are
Ledger:
id total_sale cancel_amount date
1 3000 0 2018-01-20
2 3000 0 2018-01-29
3 5000 0 2018-01-30
4 10000 500 2018-01-30
5 2000 100 2018-01-31
6 2000 0 2018-01-31
voucher:
id expenditure date
1 500 2018-01-20
2 800 2018-01-30
3 1000 2018-01-30
4 200 2018-01-31
5 300 2018-01-31
I want a result like[ date between 2018-01-29 to 2018-01-31]
date total_sale total_expenditure
2018-01-29 3000 0
2018-01-30 15000 1800
2018-01-31 4000 500
Please someone help
For such requirements, always prefer a dimension table. Then you'll never get confused on joining two dissimilar tables witch no keys in common.
Tables:
create schema test;
create table date_dim(date date);
insert into date_dim values ('2018-01-20'),
('2018-01-21'),
('2018-01-22'),
('2018-01-23'),
('2018-01-24'),
('2018-01-25'),
('2018-01-26'),
('2018-01-27'),
('2018-01-28'),
('2018-01-29'),
('2018-01-30'),
('2018-01-31');
create table ledger(id int,total_sale int, cancel_amount int, date date);
insert into ledger values
(1,3000,0,'2018-01-20'),
(2,3000,0,'2018-01-29'),
(3,5000,0,'2018-01-30'),
(4,10000,500,'2018-01-30'),
(5,2000,100,'2018-01-31'),
(6,2000,0,'2018-01-31');
create table voucher(id int, expenditure int, date date);
insert into voucher values
(1,500,'2018-01-20'),
(2,800,'2018-01-30'),
(3,1000,'2018-01-30'),
(4,200,'2018-01-31'),
(5,300,'2018-01-31');
SQL Script (for solution):
select l.date,total_sale,
total_expenditure
from
(select d.date date,sum(total_sale)total_sale
from ledger l right join date_dim d on l.date=d.date
where d.date between '2018-01-29' and '2018-01-31'
group by 1)l
join
(select d.date date,sum(expenditure)total_expenditure
from voucher v right join date_dim d on v.date=d.date
where d.date between '2018-01-29' and '2018-01-31'
group by 1)v
on l.date=v.date
group by 1,2,3;
Resultset:
date total_sale total_expenditure
2018-01-29 3000 (null)
2018-01-30 15000 1800
2018-01-31 4000 500
Check the solution at SQL Fiddle
You're hoping to present three different aggregates as columns of your result set.
distinct dates
net sales (total less canceled) (I guess that's what you want.)
expenditures.
You need to create three subqueries and join them.
The dates: (http://sqlfiddle.com/#!9/666dcb/1/0)
SELECT DISTINCT DATE(date) date FROM Ledger
UNION
SELECT DISTINCT DATE(date) date FROM Voucher
The net sales: (http://sqlfiddle.com/#!9/666dcb/2/0)
SELECT DATE(date) date,
SUM(total_sale) - SUM(cancel_amount) total_sale
FROM Ledger
GROUP BY DATE(date)
The expenditures: (http://sqlfiddle.com/#!9/666dcb/3/0)
SELECT DATE(date) date,
SUM(expenditure) total_expenditures
FROM Voucher
GROUP BY DATE(date)
Then you need to join them together on date. (http://sqlfiddle.com/#!9/666dcb/5/0)
SELECT d.date, s.total_sale, e.total_expenditures
FROM (
SELECT DISTINCT DATE(date) date FROM Ledger
UNION
SELECT DISTINCT DATE(date) date FROM Voucher
) d
LEFT JOIN (
SELECT DATE(date) date,
SUM(total_sale) - SUM(cancel_amount) total_sale
FROM Ledger
GROUP BY DATE(date)
) s ON d.date = s.date
LEFT JOIN (
SELECT DATE(date) date,
SUM(expenditure) total_expenditures
FROM Voucher
GROUP BY DATE(date)
) e ON d.date = e.date
WHERE d.date >= somedate AND d.date <= anotherdate
ORDER BY d.date
Why do the first subquery--the one with the dates? It makes sure you get a row in your final result set for dates that don't have any sales or any expenditures.
Why do separate subqueries? Because you want to end up joining three virtual tables--three subqueries--that have either zero or one row per date. If you join tables with more than one row per date, you'll get combinatorial explosion of sales and expenditures, which will exaggerate your sums.
Why use DATE(date)? Because that will allow the date columns in your detail tables to contain date/time values in case you want that.
select l.dates,l.total_sale,
v.total_expenditure
from
(select l.dates dates,sum(total_sale)total_sale
from ledger l
where l.dates between '2018-01-29' and '2018-01-31'
group by l.dates)l
left join
(select v.dates,sum(expenditure)total_expenditure
from voucher v
where v.dates between '2018-01-29' and '2018-01-31'
group by v.dates)v
on l.dates=v.dates;

How to repeat MySQL query for multiple intervals

I've got a query which produces the proper results for a given time interval of 15 minutes.
-- Query for the interval 10:00-10:15
SELECT count(r.id) as nof_reservations_in_interval
FROM reservations r
LEFT JOIN assets a ON r.asset_id = a.id
WHERE r.deleted_at is null
AND a.type_id = 23 --just an ID
AND r.start_utc <= '2017-02-21 10:15:00'
AND r.end_utc >= '2017-02-21 10:00:00'
-- result: 2
If I want to make a 'table/relation' with the results for this query between, lets say, 10:00 and 18:00 on the same day. How could I achieve that?
I could just query the statement from php for every interval; but I hoped there was some kind of smart MySQL function to do this :)
Desired result relation:
interval_start | interval_end | nof_reservations_in_interval
---------------+--------------+------------------------------
10:00 | 10:15 | 2
10:15 | 10:30 | 3
etc etc
A simple way is to define the intervals using a subquery:
SELECT t.time_start, t.time_end, count(r.id) as nof_reservations_in_interval
FROM (SELECT time('10:10:00') as time_start, time('10:15:00') as time_end UNION ALL
SELECT time('10:15:00') as time_start, time('10:30:00') as time_end
) t LEFT JOIN
reservations r
ON r.start_utc <= addtime('2017-02-21', t.time_end) AND
r.end_utc >= addtime('2017-02-21', t.time_start) LEFT JOIN
assets a
ON r.asset_id = a.id AND
a.type_id = 23
WHERE r.deleted_at is null
GROUP BY t.time_start, t.time_end;
Note: I moved the condition on a.type_id to the on clause for the left joins to work.

Fetching count from database by days

I have the following structure:
id | some_foreign_id | date
1 5 2015-09-29 23:14:23
2 5 2015-09-29 14:13:21
3 8 2015-09-28 22:23:12
For the specified some_foreign_id I want to return the count of rows in this table for each day from last 2 weeks. I created this:
SELECT DATE(t.sent_at), COUNT(*)
FROM table t
INNER JOIN sometable st ON st.some_id = t.id
INNER JOIN someOtherTable sot ON sot.someother_id = st.id
WHERE t.sent_at >= DATE_ADD(CURDATE(), INTERVAL -14 DAY)
AND t.some_foreign_id = 5
GROUP BY DATE(t.sent_at);
It shows some results, but:
Doesn't show 0's if the day has 0 records.
Changing interval to -15 changes the count from the last day - don't know why.
How could I do this properly?
To solve 1., you'll need to left join to something like this and use IFNULL()
To solve 2. (or try to), try changing your query to this (I suggest you solve this first):
SELECT DATE(t.sent_at), COUNT(*)
FROM table t
INNER JOIN sometable st ON st.some_id = t.id
INNER JOIN someOtherTable sot ON sot.someother_id = st.id
WHERE DATE(t.sent_at) >= DATE(DATE_ADD(CURDATE(), INTERVAL -14 DAY))
AND t.some_foreign_id = 5
GROUP BY DATE(t.sent_at);