I m having a very tricky SQL question, maybe the trickiest I have had
suppose I have one table below
called table_1
Date
spend
Oct. 20
50
Oct. 19
40
Oct. 18
50
Oct. 17
60
Oct. 16
50
and now I need another table that sums up all the spend that I have made, before and include the every date. like to show every I have spend so far for each date.
And for each of the date.
Below is the table that I want compute
called table_2
Date
spent
Oct. 20
250 ( sum of all the spend before and includes Oct.20)
Oct. 19
200 ( sum of all the spend before and includes Oct.19)
Oct. 18
160 ( sum of all the spend before and includes Oct.18)
Oct. 17
110 ( sum of all the spend before and includes Oct.17)
Oct. 16
50 ( sum of all the spend before and includes Oct.16)
I have tried with table_1 left join itself with left join on date_1 <= date_2.
does not work.
Could someone please give me a hint for it?
You want a running total. You get this with the windw function SUM OVER.
select date, sum(spend) over(order by date) as sum_spent
from mytable
order by date desc;
And if you should suffer from having to use an old MySQL version that doesn't support window functions yet, here is an old-fashioned alternative:
select
date,
(select sum(spend) from mytable t2 where t2.date <= t.date) as sum_spent
from mytable t
order by date desc;
Supplementary to Thorsten Kettner's answer, LEFT JOIN you've made attempt also works
SELECT a.date, SUM(b.spend)
FROM table_1 a
LEFT JOIN table_1 b ON a.date >= b.date
GROUP BY a.date
Related
I want to visualize my entries by counting how many have been created at the same day.
SELECT dayname(created_at), count(*) FROM logs
group by day(created_at)
ORDER BY created_at desc
LIMIT 7
So I get something like:
Thursday 4
Wednesday 12
Monday 4
Sunday 1
Saturday 20
Friday 23
Thursday 10
But I also want to have the Tuesday in there with 0 so I have it for one week.
Is there a way to do this with full mysql or do I need to update the result before I can give it to the chart?
EDIT:
This is the final query:
SELECT
DAYNAME(date_add(NOW(), interval days.id day)) AS day,
count(logs.id) AS amount
FROM days LEFT OUTER JOIN
(SELECT *
FROM logs
WHERE TIMESTAMPDIFF(DAY,DATE(created_at),now()) < 7) logs
on datediff(created_at, NOW()) = days.id
GROUP BY days.id
ORDER BY days.id desc;
The table days includes numbers from 0 to -6
You only need a table of offsets which could be a real table or something built on the fly like select 0 ofs union all select -1 ....
create table days (ofs int);
insert into days (ofs) values
(0), (-1), (-2), (-3),
(-4), (-5), (-6), (-7);
select
date_add('20160121', interval days.ofs day) as created_at,
count(data.id) as cnt
from days left outer join logs data
on datediff(data.created_at, '20160121') = days.ofs
group by days.ofs
order by days.ofs;
http://sqlfiddle.com/#!9/3e6bc7/1
For performance it would probably be better to limit the search in the data (logs) table:
select
date_add('20160121', interval days.ofs day) as created_at,
count(data.id) as cnt
from days left outer join
(select * from logs where created_at between <start> and <end>) data
on datediff(data.created_at, '20160121') = days.offset
group by days.offset
order by days.offset;
One downside is that you do have to parameterize this with a fixed anchor date in a couple of expressions. It might be better to have a table of real dates sitting in a table somewhere so you don't have to do the calculations.
Use RIGHT JOIN to a dates table, so you can request data for each and all days, no matter if some days have data or not, simply, mull days will show as CERO or NULL.
You can create a dates table, some sort of calendar table.
id_day | day_date |
--------------------
1 | 2016-01-01 |
2 | 2016-01-02 |
.
.
365 | 2016-12-31 |
With this table, you can relate date, then extract day, month, week, whatever you want with MYSQL DATE AND TIME FUNCTIONS
SELECT t2.dayname(day_date), count(t1.created_at) FROM logs t1 right join dates_table t2 on t1.created_at=t2.day_date group by t2.day_date ORDER BY t1.created_at desc LIMIT 7
Haven't been able to find an answer to this specific issue. Need a total count of inventory grouped by month on different products. Source data has date fields, one for IN and one for OUT. Total count for a specific month would include an aggregate sum of all rows with an IN date prior to specific month as long as the out date is null or a date after the specific month.
Obviously I can get a count for any given month by writing a query for count(distinct productID) with a WHERE clause stating that the IN Date be before the month I'm interested in (IE September 2012) AND the Out Date is null or after 9/2012:
Where ((in_date <= '2012-09-30') AND (out_date >= '2012-09-01' or out_date is null))
If the product was even part of inventory for one day in September I want it to count which is why out date above 9/1/12. Sample data below. Instead of querying for a specific month, how can I turn this:
Raw Data - Each Row Is Individual Item
InDate OutDate ProductAttr ProductID
2008-04-05 NULL Blue 101
2008-06-04 NULL Red 125
2008-01-01 2012-06-01 Blue 134
2008-12-10 2012-10-09 Red 129
2009-10-15 2012-11-01 Blue 153
2012-10-01 2013-06-01 Red 149
Into this?:
Date ProductAttr Count
2008-04 Blue 503
2008-04 Red 1002
2008-05 Blue 94
2008-05 Red 3004
2008-06 Blue 2000
2008-06 Red 322
Through grouping I can get the raw data into this format grouped by months:
InDate OutDate Value Count
2008-05 2012-05 Blue 119
2008-05 2008-06 Red 333
2008-05 2012-10 Blue 4
2008-05 NULL Red 17488
2008-06 2012-11 Blue 711
2008-06 2013-02 Red 34
If you wanted to know how many products were 'IN' as of Oct. 2012- you would sum the counts of all rows except for 2. Group on Value to keep blue and red separate. Row 2 is ruled out because OutDate is before Oct. 2012.
Thank in advance.
EDIT:
Gordon Linoff's solution works just like I need it to. The only issue I am having now is the size and efficiency of the query, because the part I left out above is that the product attribute is actually located in a different table then the IN/OUT dates and I also need to join a third table to limit to a certain type of product (ForSale for example). I have tried two different approaches and they both work and return the same data, but both take far too long to automate this report:
select months.mon, count(distinct d.productID), d.ProductAttr
from (select '2008-10' as mon union all
select '2008-11' union all
select '2008-12' union all
select '2009-01'
) months left outer join
t
on months.mon >= date_format(t.Indate, '%Y-%m') and
(months.mon <= date_format(t.OutDate, '%Y-%m') or t.OutDate is NULL)
join x on x.product_id = t.product_id and x.type = 'ForSale'
join d on d.product_id = x.product_id and d.type = 'Attribute'
group by months.mon, d.ProductAttr;
Also tried the above without the last two joins by adding subqueries for the product attribute and where/exclusion - this seems to run about the same or a bit slower:
select months.mon, count(distinct t.productID), (select ProductAttr from d where productid = t.productID and type = 'attribute' limit 1)
from (select '2008-10' as mon union all
select '2008-11' union all
select '2008-12' union all
select '2009-01'
) months left outer join
t
on months.mon >= date_format(t.Indate, '%Y-%m') and
(months.mon <= date_format(t.OutDate, '%Y-%m') or t.OutDate is NULL)
WHERE exists (select 1 from x where x.productid = t.productID and x.type = 'ForSale')
group by months.mon, d.ProductAttr;
Any ideas to make this more efficient with the additional data that I need to rely on 3 source tables in total (1 just for exclusion). Thanks in advance.
You can do this by generating a list of the months that you need. The easiest way is to do this manually in MySQL (although generating the code in Excel can make this easier).
Then use a left join and aggregation to get the information you want:
select months.mon, t.ProductAttr, count(distinct t.productID)
from (select '2008-10' as mon union all
select '2008-11' union all
select '2008-12' union all
select '2009-01'
) months left outer join
t
on months.mon >= date_format(t.Indate, '%Y-%m') and
(months.mon <= date_format(t.OutDate, '%Y-%m) or t.OutDate is NULL)
group by t months.mon, t.ProductAttr;
This version does all the comparisons as strings. You are working at the granularity of "month" and the format YYYY-MM does a good job for comparisons.
EDIT:
You do need every month that you want in the output. If you have products coming in every month, then you could do:
select months.mon, t.ProductAttr, count(distinct t.productID)
from (select distinct date_format(t.InDate, '%Y-%m') as mon
from t
) months left outer join
t
on months.mon >= date_format(t.InDate, '%Y-%m') and
(months.mon <= date_format(t.OutDate, '%Y-%m) or t.OutDate is NULL)
group by t months.mon, t.ProductAttr;
This pulls the months from the data.
Hi still getting my head around MySQL so was hoping someone may be able to shed some light on this one
I have a table named customers which has the following columns
msisdn BIGINT 20
join_date DATETIME
The msisdn is a unique value to identify customers.
There is a second table named ws_billing_all which has the following structure
id INTEGER 11 (Primary Key)
msisdn BIGINT 20
event_time DATETIME
revenue INTEGER
So this table stores all transactions for each of the customers in the customers table as identified by the msisdn.
What I need to do is to determine the amount from all customers that joined on a particular day after 30 days.
So for example, on the 2nd of Dec 2010, 1,100 customers were acquired. Based on the data in ws_billing_all, how much total revenue did the customers that joined on this day generate 30 days from this date.
I will probably need another table for this but not sure and really not sure on how to go about extracting this data. Any help would be appreciated.
#Cularis was very close... You only care about those customers that joined on the ONE DAY, and want all THEIR REVENUEs earned for the next 30 days... In this scenario, a customer would never have sales prior to their join date, so I didn't add an explicit between on their actual sales dates of consideration.
SELECT
date( c.Join_Date ) DateJoined,
count( distinct c.msisdn ) DistinctMembers,
count(*) NumberOfOrders,
SUM(w.revenue) AmountOfRevenue
FROM
customers c
JOIN ws_billing_all w
ON c.msisdn = w.msisdn
AND date( w.event_time ) <= date_add( c.Join_Date, INTERVAL 30 DAY )
WHERE
c.Join_Date >= SomeDateParameterValue
group by
date( c.Join_Date )
order by
date( C.Join_Date )
EDIT -- For clarification...
If you had 150 people join on Dec 1, 45 people on Dec 2, 83 people on Dec 3, you want to see the total revenue per group of people based on the day they joined going out 30 days of their sales... So...
Joined on Number of People Total Revenue after 30 days
Dec 1 150 $21,394 (up to Dec 31)
Dec 2 45 $ 4,182 (up to Jan 1)
Dec 3 83 $ 6,829 (up to Jan 2)
Does this better clarify what you want? Then we can adjust the query...
FINAL EDIT ...
I think I have what you INTENDED (with a count of orders too that might be useful). In the future, providing a sample output of something of complex nature would be helpful, even if it was as simple as I've done here.
With respect to my WHERE clause from the customers table.... Say you only cared about customers who joined within a given time frame, or only after a given date... THIS is where you would update the clause... if you want based on ALL people, then just remove it completely.
SELECT c.msisdn, SUM(w.revenue)
FROM customers c
INNER JOIN ws_billing_all w ON c.msisdn=w.msisdn
WHERE w.event_time BETWEEN c.join_date AND DATE_ADD(c.join_date, INTERVAL 30 DAY)
GROUP BY c.msisdn
You have to join both tables on the customer id. Then select all events that happened between the join date and 30 days after that. Group by the customer id and use SUM() to get total revenue per costumer.
HI
I have a table listsing_prices (id,listing_id,day_from,day_to,price)
I need to calculate the total cost of an holiday in mysql becouse I need to sort the results by total cost.
EX:
VALUES IN TABLE
1 6 2011-04-27 2011-04-30 55,00
2 6 2011-05-01 2011-05-02 60,00
3 6 2011-05-03 2011-05-15 65,00
holiday from 2011-04-28 to 2011-05-05 total cost = 480
Without creating an actual table to represent every day from start date to end date, you could use mysql query variables. The first query can join to any table as long as it has as many records as days you are concerned with for the hoiday period... in this case, 8 days from April 28 to May 5. By doing a Cartesian and limiting to 8 will in essence, create a temp result set with one record per each day, starting with 2011/04/28 (your starting date).
Then, this is joined back to your pricing table that matches the date period and sums the matching price for total costs...
select
sum( pt.price ) as TotalCosts
from
( SELECT
#r:= date_add(#r, interval 1 day ) CalendarDate
FROM
(select #r := STR_TO_DATE('2011/04/28', '%Y/%m/%d')) vars,
AnyTableWithAtLeast8ays limit 8 ) JustDates,
PricesTable pt
where
JustDates.CalendarDate between pt.date_from and pt.date_to
select count(price) from listing_prices where day_from >= '2011-04-28' and day_to <= '2011-05-05'
-- This will provide a list of ids along with how many days fall between the two
SELECT a.id, DATEDIFF(DAYS, CASE WHEN day_from < '2011-04-28' THEN '2011-04-28' ELSE day_from END CASE, day_to) AS DayCount
FROM listing_prices a
WHERE '2011-04-28' BETWEEN a.date_from AND a.date_to
AND a.date_to <= '2011-05-05'
-- Based on the previous query, sum the number of days within the range
SELECT SUM( a.price * b.DayCount ) AS Total
FROM listing_prices a
JOIN ( SELECT a.id, DATEDIFF(DAYS, CASE WHEN day_from < '2011-04-28' THEN '2011-04-28' ELSE day_from END CASE, day_to) AS DayCount
FROM listing_prices a
WHERE '2011-04-28' BETWEEN a.date_from AND a.date_to
AND a.date_to <= '2011-05-05'
) b ON a.id = b.id
Please note that this is untested ... the query at the top I believe should work but if it doesn't, it can be modified and so that it does work (get the number of days within each range) and then literally copied and pasted into the subquery of the second query. The second query is the one that you will actually use.
Data:
values date
14 1.1.2010
20 1.1.2010
10 2.1.2010
7 4.1.2010
...
sample query about january 2010 should get 31 rows. One for every day. And values vould be added. Right now I could do this with 31 queries but I would like this to work with one. Is it possible?
results:
1. 34
2. 10
3. 0
4. 7
...
This is actually surprisingly difficult to do in SQL. One way to do it is to have a long select statement with UNION ALLs to generate the numbers from 1 to 31. This demonstrates the principle but I stopped at 4 for clarity:
SELECT MonthDate.Date, COALESCE(SUM(`values`), 0) AS Total
FROM (
SELECT 1 AS Date UNION ALL
SELECT 2 UNION ALL
SELECT 3 UNION ALL
SELECT 4 UNION ALL
--
SELECT 28 UNION ALL
SELECT 29 UNION ALL
SELECT 30 UNION ALL
SELECT 31) AS MonthDate
LEFT JOIN Table1 AS T1
ON MonthDate.Date = DAY(T1.Date)
AND MONTH(T1.Date) = 1 AND YEAR(T1.Date) = 2010
WHERE MonthDate.Date <= DAY(LAST_DAY('2010-01-01'))
GROUP BY MonthDate.Date
It might be better to use a table to store these values and join with it instead.
Result:
1, 34
2, 10
3, 0
4, 7
Given that for some dates you have no data, you'll need to fill in the gaps. One approach to this is to have a calendar table prefilled with all dates you need, and join against that.
If you want the results to show day numbers as you have showing in your question, you could prepopulate these in your calendar too as labels.
You would join your data table date field to the date field of the calendar table, group by that field, and sum values. You might want to specify limits for the range of dates covered.
So you might have:
CREATE TABLE Calendar (
label varchar,
cal_date date,
primary key ( cal_date )
)
Query:
SELECT
c.label,
SUM( d.values )
FROM
Calendar c
JOIN
Data_table d
ON d.date_field = c.cal_date
WHERE
c.cal_date BETWEEN '2010-01-01' AND '2010-01-31'
GROUP BY
d.date_field
ORDER BY
d.date_field
Update:
I see you have datetimes rather than dates. You could just use the MySQL DATE() function in the join, but that would probably not be optimal. Another approach would be to have start and end times in the Calendar table defining a 'time bucket' for each day.
This works for me... Its a modification of a query I found on another site. The "INTERVAL 1 MONTH" clause ensures I get the current month data, including zeros for days that have no hits. Change this to "INTERVAL 2 MONTH" to get last months data, etc.
I have a table called "payload" with a column "timestamp" - Im then joining the timestamp column on to the dynamically generated dates, casting it so that the dates match in the ON clause.
SELECT `calendarday`,COUNT(P.`timestamp`) AS `cnt` FROM
(SELECT #tmpdate := DATE_ADD(#tmpdate, INTERVAL 1 DAY) `calendarday`
FROM (SELECT #tmpdate :=
LAST_DAY(DATE_SUB(CURDATE(),INTERVAL 1 MONTH)))
AS `dynamic`, `payload`) AS `calendar`
LEFT JOIN `payload` P ON DATE(P.`timestamp`) = `calendarday`
GROUP BY `calendarday`
To dynamically get the dates within a date range using SQL you can do this (example in mysql):
Create a table to hold the numbers 0 through 9.
CREATE TABLE ints ( i tinyint(4) );
insert into ints (i)
values (0),(1),(2),(3),(4),(5),(6),(7),(8),(9);
Run a query like so:
select ((curdate() - interval 2 year) + interval (t.i * 100 + u.i * 10 + v.i) day) AS Date
from
ints t
join ints u
join ints v
having Date between '2015-01-01' and '2015-05-01'
order by t.i, u.i, v.i
This will generate all dates between Jan 1, 2015 and May 1, 2015.
Output
2015-01-01
2015-01-02
2015-01-03
2015-01-04
2015-01-05
2015-01-06
...
2015-05-01
The query joins the table ints 3 times and gets an incrementing number (0 through 999). It then adds this number as a day interval starting from a certain date, in this case a date 2 years ago. Any date range from 2 years ago and 1,000 days ahead can be obtained with the example above.
To generate a query that generates dates for more than 1,000 days simply join the ints table once more to allow for up to 10,000 days of range, and so forth.
If I'm understanding the rather vague question correctly, you want to know the number of records for each date within a month. If that's true, here's how you can do it:
SELECT COUNT(value_column) FROM table WHERE date_column LIKE '2010-01-%' GROUP BY date_column