I have this table: table1, and four varaible $START_MONTH $START_YEAR $END_MONTH $END_YEAR which return: month:1, year: 2014 and 3, 2015
+----+-----+-------+------+-------------+
| ID | DAY | MONTH | YEAR | DESCRIPTION |
+----+-----+-------+------+-------------+
| 1 | 1 | 1 | 2014 | Product 1 |
| 2 | 1 | 2 | 2015 | Product 2 |
| 3 | 2 | 3 | 2014 | Product 1 |
| 4 | 10 | 1 | 2015 | Product 3 |
| 5 | 9 | 5 | 2015 | Product 3 |
+----+-----+-------+------+-------------+
SELECT
DATE_FORMAT (CONCAT (DAY,' ', MONTH,' ',YAR), GET_FORMAT(DATE,'EUR')) AS NEW_DATA,
DESCRIPTION
FROM table1
WHERE
NEW_DATA BETWEEN $START_MONTH . $START_YEAR AND $END_MONTH . $END_YEAR
I need return the rows included between: 1.2014 and 3.2015. Thank you!
In MySQL you can filter by combined columns:
SELECT *
FROM table1
WHERE (`YEAR`, `MONTH`) >= (2014,1)
AND (`YEAR`, `MONTH`) <= (2015,3)
http://sqlfiddle.com/#!9/87000/2
Update: As Strawberry stated, MySQL will not use an index for this condition. But it's not that it's impossible - MySQL is just missing this optimisation. As far as i know PostgreSQL is fine with this kind of index range check. So we can hope that MySQL/MariaDB will support it in the future.
However, if you face performance problems, you can add redundant conditions for the year range:
WHERE (`YEAR`, `MONTH`) >= (2014,1)
AND (`YEAR`, `MONTH`) <= (2015,3)
AND `YEAR` >= 2014
AND `YEAR` <= 2015
This way MySQL will first filter the rows by YEAR using an index like YEAR, MONTH or YEAR, MONTH, DAY (or any index that starts with YEAR).
All alternatives i know are not very readable and no of them can use an index:
WHERE DATE(CONCAT_WS('-', `YEAR`, `MONTH`, 1)) >= '2014-01-01'
AND DATE(CONCAT_WS('-', `YEAR`, `MONTH`, 1)) <= '2015-03-01'
Or
WHERE (`YEAR` = 2014 AND `MONTH` >= 1)
OR (`YEAR` = 2015 AND `MONTH` >= 3)
OR (`YEAR` > 2014 AND `YEAR` < 2015)
If you are fighting for every milisecond you can also create an indexed generated (virtual) column (MySQL 5.7):
`DATE` AS STR_TO_DATE(CONCAT_WS('-', `YEAR`, `MONTH`, `DAY`),'%Y-%m-%d') VIRTUAL KEY
And query like:
WHERE `DATE` >= '2014-01-01'
AND `DATE` < '2015-04-01'
SELECT
DATE_FORMAT (CONCAT (DAY,' ', MONTH,' ',YEAR), GET_FORMAT(DATE,'EUR')) AS NEW_DATA,
DESCRIPTION
FROM table1
WHERE
(YEAR BETWEEN $START_YEAR AND $END_YEAR)
AND (MONTH BETWEEN $START_MONTH AND $END_MONTH)
Related
I have a table named "counter" that has 3 fields:
| Field | Type | Null | Key | Default | Extra |
+--------+------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| record | datetime | YES | | NULL | |
| passed | tinyint(1) | YES | | NULL | |
+--------+------------+------+-----+---------+----------------+
My table records
mysql> SELECT record, passed FROM counter;
+---------------------+--------+
| record | passed |
+---------------------+--------+
| 2019-09-19 00:00:00 | 1 |
I would like to make a query to group the results by weeks, and months from the field record.
I have tried to do it like this but doesn't look right
SELECT DATE_FORMAT(record, '%d-%m-%Y') AS ts, COUNT(*) FROM counter WHERE record >= '2019-10-09' AND record < '2019-10-09' + INTERVAL 7 DAY GROUP BY DATE_FORMAT(record, '%d-%m-%Y');
Simple grouping with date extraction must work
select
EXTRACT(YEAR FROM record) V_YEAR,
EXTRACT(MONTH FROM record) V_MONTH,
EXTRACT(WEEK FROM record) V_WEEK,
COUNT(*)
FROM COUNTER
GROUP BY
EXTRACT(YEAR FROM record) ,
EXTRACT(MONTH FROM record) ,
EXTRACT(WEEK FROM record)
edit same works with DATE_FORMAT(date, format) as well
select
DATE_FORMAT(record, '%Y') V_YEAR,
DATE_FORMAT(record, '%M') V_MONTH,
DATE_FORMAT(record, '%d') V_WEEK,
COUNT(*)
FROM COUNTER
GROUP BY
DATE_FORMAT(record, '%Y') ,
DATE_FORMAT(record, '%M') ,
DATE_FORMAT(record, '%d')
Number of passes, fails etc per week:
SELECT week(record_date) as weekofyear, sum(passed) as numpasses, count(*) - sum(passed) as numfails, count(*) as total
FROM counter
WHERE record_date >= '2019-01-01' and record_date < '2020-01-01'
GROUP BY week(record_date)
Swap week for month etc
MySQL supports ROLLUP which can summarise weeks and months in the same statement:
SELECT month(record_date) as monthofyear, week(record_date) as weekofyear, sum(passed) as numpasses, count(*) - sum(passed) as numfails, count(*) as total
FROM counter
WHERE record_date >= '2019-01-01' and record_date < '2020-01-01'
GROUP BY month(record_date), week(record_date) WITH ROLLUP
lines where the week is null are the total for the month
The goal of the code is to select Month, SaleID, Total and Growth. I can display Month, SaleID and Total but can't get Growth to work because it calculates from the the first row always. What am I doing wrong?
I've tried setting up variables, Emulating LAG(), PREV, CURRENT, NEXT to get the row the calculation should use but it won't register the native functions.
CREATE VIEW SalesTemp
AS
SELECT
DATE_FORMAT(Sales.SaleDate, "%Y-%m") AS Month,
Sales.SaleID,
Sales.Total
FROM Sales
WHERE SaleDate BETWEEN '2018-04-00' AND '2040-00-00'
GROUP BY DATE_FORMAT(Sales.SaleDate, "%Y-%m");
SELECT * FROM SalesTemp;
DROP VIEW IF EXISTS PercentageGrowth;
CREATE VIEW PercentageGrowth
AS
SELECT
DATE_FORMAT(Sales.SaleDate, "%Y-%m") AS Month,
Sales.SaleID,
Sales.Total,
CONCAT(ROUND(((Sales.Total) - SalesTemp.Total) / (SELECT SalesTemp.Total FROM SalesTemp GROUP BY DATE_FORMAT(SalesTemp.Month, "%Y-%m")) * 100, 2), "%") AS Growth
FROM Sales, SalesTemp
GROUP BY DATE_FORMAT(Sales.SaleDate, "%Y-%m");
SELECT * FROM PercentageGrowth;
DROP VIEW PercentageGrowth;
DROP VIEW SalesTemp;
I want it to display growth of a company through the calculation of ((newValue - oldValue) / oldValue).
Since I can't link pictures I'll ascii what the result is. What I get from the SELECT now is:
+--------------------------------------+
| Month | SaleID | Total | Growth |
| ------- | ------ | ------- | ------- |
| 2018-04 | 1 | 310.46 | 00.00% |
| 2018-05 | 3 | 2160.62 | 595.54% |
| 2018-06 | 6 | 1087.89 | 250.21% |
| 2018-07 | 14 | 2314.54 | 645.09% |
+--------------------------------------+
I want it to say:
+--------------------------------------+
| Month | SaleID | Total | Growth |
| ------- | ------ | ------- | ------- |
| 2018-04 | 1 | 310.46 | 00.00% |
| 2018-05 | 3 | 2160.62 | 595.54% |
| 2018-06 | 6 | 1087.89 | -49.64% |
| 2018-07 | 14 | 2314.54 | 112.76% |
+--------------------------------------+
Currently, you are cross joining your table pairings across all SaleID and all formatted date months and this is then further impacted by your unclear aggregations.
Assuming you use MySQL 8+, consider a couple of CTEs which includes LAG by one offset of your aggregated month totals:
WITH cte1 AS
(SELECT DATE_FORMAT(Sales.SaleDate, "%Y-%m") AS `Month`,
Sales.SaleID,
SUM(Sales.Total) AS `Total_Sales`
FROM Sales
WHERE SaleDate BETWEEN '2018-04-00' AND '2040-00-00'
GROUP BY
DATE_FORMAT(Sales.SaleDate, "%Y-%m"),
Sales.SaleID
),
cte2 AS
(SELECT *,
LAG(`Total_Sales`) OVER (PARTITION BY `SaleID`
ORDER BY `Month`) AS `Lag_Total_Sales`
FROM cte1)
SELECT `Month`, `SaleID`, `Total_Sales`,
CONCAT(
ROUND(
(`Total_Sales` - `Lag_Total_Sales`) / `Lag_Total_Sales`
, 2) * 100
, '%') AS `Growth`
FROM cte2
For MySQL 5.7 or less, consider a self-join of subquery that explicitly joins SaleID and any date in last month normalizing all dates to the first of their respective months.
SELECT DATE_FORMAT(curr.FirstMonth, "%Y-%m") AS `Month`,
curr.SaleID,
curr.Total_Sales,
CONCAT(
ROUND((`curr`.Total_Sales - `prev`.Total_Sales) / `prev`.Total_Sales
, 2)*100
, '%') AS `Growth`
FROM
(SELECT DATE_ADD(LAST_DAY(DATE_SUB(SaleDate, INTERVAL 1 MONTH))
, INTERVAL 1 DAY) As FirstMonth,
SaleID,
SUM(`Total`) As `Total_Sales`
FROM Sales
GROUP BY
DATE_ADD(LAST_DAY(DATE_SUB(SaleDate, INTERVAL 1 MONTH))
, INTERVAL 1 DAY),
SaleID
) AS `curr`
LEFT JOIN
(SELECT DATE_ADD(LAST_DAY(DATE_SUB(SaleDate, INTERVAL 1 MONTH))
, INTERVAL 1 DAY) As FirstMonth,
SaleID,
SUM(`Total`) As `Total_Sales`
FROM Sales
GROUP BY
DATE_ADD(LAST_DAY(DATE_SUB(SaleDate,
INTERVAL 1 MONTH))
, INTERVAL 1 DAY),
SaleID
) AS `prev`
ON `curr`.SaleID = `prev`.SaleID
AND `curr`.FirstMonth - INTERVAL 1 MONTH = `prev`.FirstMonth
AND `curr`.FirstMonth BETWEEN '2018-04-00' AND '2040-00-00'
Rextester Demo (MySQL 5.7 version)
I'm aware that there are several answers on SO about cumulative totals. I have experimented and have not found a solution to my problem.
Here is a sqlfiddle.
We have a contacts table with two fields, eid and create_time:
eid create_time
991772 April, 21 2016 11:34:21
989628 April, 17 2016 02:19:57
985557 April, 04 2016 09:56:39
981920 March, 30 2016 11:03:12
981111 March, 30 2016 09:36:48
I would like to select the number of new contacts in each month along with the size of our contacts database at the end of each month. New contacts by year and month is simple enough. For the size of the contacts table at the end of each month I did some research and found what looked to be a straight forwards method:
set #csum = 0;
select
year(c.create_time) as yr,
month(c.create_time) as mth,
count(c.eid) as new_contacts,
(#csum + count(c.eid)) as cumulative_contacts
from
contacts c
group by
yr,
mth
That runs but gives me unexpected results.
If I run:
select count(*) from contacts where date(create_time) < current_date
I get the total number of records in the table 146.
I therefore expected the final row in my query using #csum to have 146 for April 2016. It has only 3?
What my goal is for field cumulative_contacts:
For the record with e.g. January 2016.
select count(*) from contacts where date(create_time) < '2016-02-01';
And the record for February would have:
select count(*) from contacts where date(create_time) < '2016-03-01';
And so on
Try this, a bit of modification from your sql;)
CREATE TABLE IF NOT EXISTS `contacts` (
`eid` char(50) DEFAULT NULL,
`create_time` timestamp NULL DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=COMPACT;
INSERT INTO `contacts` (`eid`, `create_time`) VALUES
('991772', '2016-04-21 11:34:21'),
('989628', '2016-04-17 02:19:57'),
('985557', '2016-04-04 09:56:39'),
('981920', '2016-03-30 11:03:12'),
('981111', '2016-03-30 09:36:48');
SET #csum = 0;
SELECT t.*, #csum:=(#csum + new_contacts) AS cumulative_contacts
FROM (
SELECT YEAR(c.create_time) AS yr, MONTH(c.create_time) AS mth, COUNT(c.eid) AS new_contacts
FROM contacts c
GROUP BY yr, mth) t
Output results is
| yr | mth | new_contacts | cumulative_contacts |
------ ----- -------------- ---------------------
| 2016 | 3 | 2 | 2 |
| 2016 | 4 | 3 | 5 |
This sql will get the cumulative sum and is pretty efficient. It numbers each row first and then uses that as the cumulative sum.
SELECT s1.yr, s1.mth, s1.new_contacts, s2.cummulative_contacts
FROM
(SELECT
YEAR(create_time) AS yr,
MONTH(create_time) AS mth,
COUNT(eid) AS new_contacts,
MAX(eid) AS max_eid
FROM
contacts
GROUP BY
yr,
mth
ORDER BY create_time) s1 INNER JOIN
(SELECT eid, (#sum:=#sum+1) AS cummulative_contacts
FROM
contacts INNER JOIN
(SELECT #sum := 0) r
ORDER BY create_time) s2 ON max_eid=s2.eid;
--Result sample--
| yr | mth | new_contacts | cumulative_contacts |
|------|-----|--------------|---------------------|
| 2016 | 1 | 4 | 132 |
| 2016 | 2 | 4 | 136 |
| 2016 | 3 | 7 | 143 |
| 2016 | 4 | 3 | 146 |
Try this: fiddele
Here you have a "greater than or equal" join, so each group "contains" all previous values. Times 12 part, converts the hole comparation to months. I did offer this solution as it is not MySql dependant. (can be implemented on many other DBs with minimun or no changes)
select dates.yr, dates.mth, dates.new_contacts, sum(NC.new_contacts) as cumulative_new_contacts
from (
select
year(c.create_time) as yr,
month(c.create_time) as mth,
count(c.eid) as new_contacts
from
contacts c
group by
year(c.create_time),
month(c.create_time)
) as dates
left join
(
select
year(c.create_time) as yr,
month(c.create_time) as mth,
count(c.eid) as new_contacts
from
contacts c
group by
year(c.create_time),
month(c.create_time)
) as NC
on dates.yr*12+dates.mth >= NC.yr*12+NC.mth
group by
dates.yr,
dates.mth,
dates.new_contacts -- not needed by MySql, present here for other DBs compatibility
order by 1,2
I am looking for a solution to count days in a daterange per year. My table looks like this:
+----+-----------+------------+------------+
| id | source_id | start_date | end_date |
+----+-----------+------------+------------+
| 1 | 1 | 2015-11-01 | 2017-01-31 |
+----+-----------+------------+------------+
Now I want to count the days in between. Its easy with DATEDIFF() in complete, but how to do it per year?
I tried a kind of temp. transformation into single rows to perform count and group actions:
+----+-----------+------------+------------+
| id | source_id | start_date | end_date |
+----+-----------+------------+------------+
| 1 | 1 | 2015-11-01 | 2015-12-31 |
+----+-----------+------------+------------+
| 1 | 1 | 2016-01-01 | 2016-12-31 |
+----+-----------+------------+------------+
| 1 | 1 | 2017-01-01 | 2017-01-31 |
+----+-----------+------------+------------+
EDIT:
The desired output should like that:
+-----------+------+------+
| source_id | year | days |
+-----------+------+------+
| 1 | 2015 | 60 |
+-----------+------+------+
| 1 | 2016 | 365 |
+-----------+------+------+
| 1 | 2017 | 30 |
+-----------+------+------+
So it become possible to summarize all days grouped by source_id and year.
Is there an easy way to do it in MySQL?
Create another table that lists all the years:
CREATE TABLE years (
year_start DATE,
year_end DATE
);
INSERT INTO years VALUES
('2015-01-01', '2015-12-31'),
('2016-01-01', '2016-12-31'),
('2017-01-01', '2017-12-31');
Then you can join with this table
SELECT t.source_id, YEAR(y.year_start) AS year, DATEDIFF(LEAST(year_end, end_date), GREATEST(year_start, start_date)) AS day_count
FROM yourTable AS t
JOIN years AS y
ON y.year_start BETWEEN t.start_date AND t.end_date
OR y.year_end BETWEEN t.start_date AND t.end_date
DEMO
If you don't want to create a real table, you can use a subquery that creates it on the fly:
SELECT t.source_id, YEAR(y.year_start) AS year, DATEDIFF(LEAST(year_end, end_date), GREATEST(year_start, start_date)) AS day_count
FROM yourTable AS t
JOIN (SELECT CAST('2015-01-01' AS DATE) AS year_start, CAST('2015-12-31' AS DATE) AS year_end
UNION
SELECT CAST('2016-01-01' AS DATE) AS year_start, CAST('2016-12-31' AS DATE) AS year_end
UNION
SELECT CAST('2017-01-01' AS DATE) AS year_start, CAST('2017-12-31' AS DATE) AS year_end
) AS y
ON y.year_start BETWEEN t.start_date AND t.end_date
OR y.year_end BETWEEN t.start_date AND t.end_date
DEMO
I found some other snippet and I combined both. Its more a working hack than a solution, but it works good enough for my purpose.
SELECT r.source_id,
YEAR(y.year_start) AS year,
DATEDIFF(LEAST(year_end, end_date), GREATEST(year_start, start_date)) AS day_count,
r.start_date,
r.end_date
FROM ranges AS r
JOIN (
SELECT #i:= #i + 1 AS YEAR,
CAST(CONCAT(#i, '-01-01') AS DATE) AS year_start,
CAST(CONCAT(#i, '-12-31') AS DATE) AS year_end
FROM INFORMATION_SCHEMA.COLLATION_CHARACTER_SET_APPLICABILITY,
(SELECT #i:= 1899) AS i
) AS y
ON r.start_date >= y.year_start AND r.start_date <= y.year_end
OR r.end_date >= y.year_start AND r.end_date <= y.year_end;
I think, the table INFORMATION_SCHEMA.COLLATION_CHARACTER_SET_APPLICABILITY is just a workaround to do the iteration. Not nice, but maybe someone needs something like that.
I have a table with :
user_id | order_date
---------+------------
12 | 2014-03-23
12 | 2014-01-24
14 | 2014-01-26
16 | 2014-01-23
15 | 2014-03-21
20 | 2013-10-23
13 | 2014-01-25
16 | 2014-03-23
13 | 2014-01-25
14 | 2014-03-22
A Active user is someone who has logged in last 12 months.
Need output as
Period | count of Active user
----------------------------
Oct-2013 - 1
Jan-2014 - 5
Mar-2014 - 10
The Jan 2014 value - includes Oct -2013 1 record and 4 non duplicate record for Jan 2014)
You can use a variable to calculate the running total of active users:
SELECT Period,
#total:=#total+cnt AS `Count of Active Users`
FROM (
SELECT CONCAT(MONTHNAME(order_date), '-', YEAR(order_date)) AS Period,
COUNT(DISTINCT user_id) AS cnt
FROM mytable
GROUP BY Period
ORDER BY YEAR(order_date), MONTH(order_date) ) t,
(SELECT #total:=0) AS var
The subquery returns the number of distinct active users per Month/Year. The outer query uses #total variable in order to calculate the running total of active users' count.
Fiddle Demo here
I've got two queries that do the thing. I am not sure which one's the fastest. Check them aginst your database:
SQL Fiddle
Query 1:
select per.yyyymm,
(select count(DISTINCT o.user_id) from orders o where o.order_date >=
(per.yyyymm - INTERVAL 1 YEAR) and o.order_date < per.yyyymm + INTERVAL 1 MONTH) as `count`
from
(select DISTINCT LAST_DAY(order_date) + INTERVAL 1 DAY - INTERVAL 1 MONTH as yyyymm
from orders) per
order by per.yyyymm
Results:
| yyyymm | count |
|---------------------------|-------|
| October, 01 2013 00:00:00 | 1 |
| January, 01 2014 00:00:00 | 5 |
| March, 01 2014 00:00:00 | 6 |
Query 2:
select DATE_FORMAT(order_date, '%Y-%m'),
(select count(DISTINCT o.user_id) from orders o where o.order_date >=
(LAST_DAY(o1.order_date) + INTERVAL 1 DAY - INTERVAL 13 MONTH) and
o.order_date <= LAST_DAY(o1.order_date)) as `count`
from orders o1
group by DATE_FORMAT(order_date, '%Y-%m')
Results:
| DATE_FORMAT(order_date, '%Y-%m') | count |
|----------------------------------|-------|
| 2013-10 | 1 |
| 2014-01 | 5 |
| 2014-03 | 6 |
The best thing I could do is this:
SELECT Date, COUNT(*) as ActiveUsers
FROM
(
SELECT DISTINCT userId, CONCAT(YEAR(order_date), "-", MONTH(order_date)) as Date
FROM `a`
ORDER BY Date
)
AS `b`
GROUP BY Date
The output is the following:
| Date | ActiveUsers |
|---------|-------------|
| 2013-10 | 1 |
| 2014-1 | 4 |
| 2014-3 | 4 |
Now, for every row you need to sum up the number of active users in previous rows.
For example, here is the code in C#.
int total = 0;
while (reader.Read())
{
total += (int)reader['ActiveUsers'];
Console.WriteLine("{0} - {1} active users", reader['Date'].ToString(), reader['ActiveUsers'].ToString());
}
By the way, for the March of 2014 the answer is 9 because one row is duplicated.
Try this, but thise doesn't handle the last part: The Jan 2014 value - includes Oct -2013
select TO_CHAR(order_dt,'MON-YYYY'), count(distinct User_ID ) cnt from [orders]
where User_ID in
(select User_ID from
(select a.User_ID from [orders] a,
(select a.User_ID,count (a.order_dt) from [orders] a
where a.order_dt > (select max(b.order_dt)-365 from [orders] b where a.User_ID=b.User_ID)
group by a.User_ID
having count(order_dt)>1) b
where a.User_ID=b.User_ID) a
)
group by TO_CHAR(order_dt,'MON-YYYY');
This is what I think you are looking for
SET #cnt = 0;
SELECT Period, #cnt := #cnt + total_active_users AS total_active_users
FROM (
SELECT DATE_FORMAT(order_date, '%b-%Y') AS Period , COUNT( id) AS total_active_users
FROM t
GROUP BY DATE_FORMAT(order_date, '%b-%Y')
ORDER BY order_date
) AS t
This is the output that I get
Period total_active_users
Oct-2013 1
Jan-2014 6
Mar-2014 10
You can also do COUNT(DISTINCT id) to get the unique Ids only
Here is a SQL Fiddle