For example now is August, I want a select query with result like:
+-----------+
| September |
+-----------+
| October |
+-----------+
| November |
+-----------+
| December |
+-----------+
| January |
+-----------+
| February |
+-----------+
| March |
+-----------+
| April |
+-----------+
| May |
+-----------+
| June |
+-----------+
| July |
+-----------+
| August |
+-----------+
And this order will change in next month.
Sorry if it is duplicate, I don't know what is keyword to search.
You can get the name of the month of a date with monthname() and use date_add() to subtract months from a date. That is, you can build a query using UNION ALL and tableless SELECTs getting the name of the month to get the desired result.
SELECT monthname(date_add(curdate(), interval -11 month))
UNION ALL
SELECT monthname(date_add(curdate(), interval -10 month))
...
UNION ALL
SELECT monthname(date_add(curdate(), interval -1 month))
UNION ALL
SELECT monthname(curdate());
Edit:
To make this a little more dynamic, you could once create a number table.
CREATE TABLE integers
(i integer);
INSERT INTO integers
(i)
VALUES ...
(-11),
(-10),
...
(-1),
(0),
...;
You can then select the desired range from that table, to build the list of months.
SELECT monthname(date_add(curdate(), interval i month))
FROM integers
WHERE i >= -11
AND i <= 0
ORDER BY i;
Create the month rows on-the-fly with UNION ALL or create a table and insert the records there. Couple month name and month number. Use the number for sorting. If you want to start with next month, compare with the current month:
select name
from
(
select 1 as num, monthname('2000-01-01') as name
union all
select 2 as num, monthname('2000-02-01') as name
union all
select 3 as num, monthname('2000-03-01') as name
union all
select 4 as num, monthname('2000-04-01') as name
union all
select 5 as num, monthname('2000-05-01') as name
union all
select 6 as num, monthname('2000-06-01') as name
union all
select 7 as num, monthname('2000-07-01') as name
union all
select 8 as num, monthname('2000-08-01') as name
union all
select 9 as num, monthname('2000-09-01') as name
union all
select 10 as num, monthname('2000-10-01') as name
union all
select 11 as num, monthname('2000-11-01') as name
union all
select 12 as num, monthname('2000-12-01') as name
) months
order by num <= month(current_date), num;
Rextester demo: http://rextester.com/NOYU32731
(This gives you the month names according to the language setting in the database. So the guy in France will see the month names in the same language as the girl in Spain when using this query.)
Related
Ok, i have a table with a date column and a integer column, and i want to retrieve all the rows
grouped by date's day within a certain date range; since there are not rows for every day, is it possible to make mysql return rows for those days with a default value?
example
source table:
date value
2020-01-01 1
2020-01-01 2
2020-01-03 2
2020-01-07 3
2020-01-08 4
2020-01-08 1
Standard behaviour after grouping by date and summing values:
2020-01-01 3
2020-01-03 2
2020-01-07 3
2020-01-08 5
Desired behaviour/result with empty rows:
2020-01-01 3
2020-01-02 0
2020-01-03 2
2020-01-04 0
2020-01-05 0
2020-01-06 0
2020-01-07 3
2020-01-08 5
You can do something like the below:
# table creation:
drop table if exists test_table;
create table test_table (your_date date, your_value int(11));
insert into test_table (your_date, your_value) values ('2020-01-01', 1);
insert into test_table (your_date, your_value) values ('2020-01-01', 2);
insert into test_table (your_date, your_value) values ('2020-01-03', 2);
insert into test_table (your_date, your_value) values ('2020-01-07', 3);
insert into test_table (your_date, your_value) values ('2020-01-08', 4);
insert into test_table (your_date, your_value) values ('2020-01-08', 1);
This creates a list of basically all the dates. You then filter for the dates your interested in, join with your table and group.
You could also replace the dates in the where statement with subqueries (min and max date of your table) to make it dynamic
It's a bit of a work-around but it works.
select sbqry.base_date, sum(ifnull(t.your_value, 0))
from (select adddate('1970-01-01',t4.i*10000 + t3.i*1000 + t2.i*100 + t1.i*10 + t0.i) base_date from
(select 0 i union select 1 union select 2 union select 3 union select 4 union select 5 union select 6 union select 7 union select 8 union select 9) t0,
(select 0 i union select 1 union select 2 union select 3 union select 4 union select 5 union select 6 union select 7 union select 8 union select 9) t1,
(select 0 i union select 1 union select 2 union select 3 union select 4 union select 5 union select 6 union select 7 union select 8 union select 9) t2,
(select 0 i union select 1 union select 2 union select 3 union select 4 union select 5 union select 6 union select 7 union select 8 union select 9) t3,
(select 0 i union select 1 union select 2 union select 3 union select 4 union select 5 union select 6 union select 7 union select 8 union select 9) t4) sbqry
left join test_table t on base_date = t.your_date
where sbqry.base_date between '2020-01-01' and '2020-01-08'
group by sbqry.base_date;
input:
+------------+------------+
| your_date | your_value |
+------------+------------+
| 2020-01-01 | 1 |
| 2020-01-01 | 2 |
| 2020-01-03 | 2 |
| 2020-01-07 | 3 |
| 2020-01-08 | 4 |
| 2020-01-08 | 1 |
+------------+------------+
output:
+------------+------------------------------+
| base_date | sum(ifnull(t.your_value, 0)) |
+------------+------------------------------+
| 2020-01-01 | 3 |
| 2020-01-02 | 0 |
| 2020-01-03 | 2 |
| 2020-01-04 | 0 |
| 2020-01-05 | 0 |
| 2020-01-06 | 0 |
| 2020-01-07 | 3 |
| 2020-01-08 | 5 |
+------------+------------------------------+
You could also achieve what you want with the following query which may be easier to understand :
SELECT
date_table.date,
IFNULL(SUM(value),0) as sum_val
FROM (
SELECT DATE_ADD('2020-01-01', INTERVAL (#i:=#i+1)-1 DAY) AS `date`
FROM information_schema.columns,(SELECT #i:=0) gen_sub
WHERE DATE_ADD('2020-01-01',INTERVAL #i DAY) BETWEEN '2020-01-01' AND '2020-01-08'
) date_table
LEFT JOIN test ON test.date_value = date_table.date
GROUP BY date;
FIND A DEMO HERE
You could set some variable to fix min and max dates :
SET #date_min = '2020-01-01';
SET #date_max = '2020-01-08';
SELECT DATE_ADD(#date_min, INTERVAL (#i:=#i+1)-1 DAY) AS `date`
FROM information_schema.columns, (SELECT #i:=0) gen_sub
WHERE DATE_ADD(#date_min, INTERVAL #i DAY) BETWEEN #date_min AND #date_max
Some explanation :
In fact, your question encourage us to generate a set of dates because we are looking to 'left join' 'your table' with a continuous set of date in order to match dates with no records in 'your table'.
This would be pretty easy in PostgreSQL because of generate_series function but this is not that easy in MySQL as such a useful function doesn't exist. That's why we need to be smart.
Both solutions here have the same logic behind it : I mean they are both incrementing a date value (day per day) for each row joined in another table, let's call it 'source table'. In the answer above (not mine), 'source table' is made with many unions and cross joins (it generates 100k rows), in my case here 'source table' is 'information_schema.columns' which already contains lots of rows (1800+).
In above case, initial date is fixed to 1970-01-01 and then it will increment this date 100 000 times in order to have a set of 100 000 dates beginning with 1970-01-01.
In my case, initial date is fixed to your min range date, 2020-01-01, and then it will increment this date for each row found in information_schema.columns, so around 1800 times. You will end with a set of around 1800 dates beginning with 2020-01-01.
Finally, you can left join your table with this generated set of dates (whatever the way to do it) in order to sum(value) for each day in your desired range.
Hope that would help you understand the logic behind both queries ;)
I am getting number of visits every day for generating a chart. Even when there are zero records, I want to get the record with count 0.
I am planning to create a table which will contain every day, and when fetching - data will join with this table and get count of the records from visit table. Is there any other way to do the same in mySQL?
Visit Table with Sample Data
Date | ........
----------------------
01/11/2014 | --------
03/11/2014 | --------
I want results even for 02/11/2014 with count 0. If I group by date - I will get count only when records exists on a particular date.
I'll try to read in between lines of your question... Sort of game where I write the question and the answer :-/
You have a table (my_stats) holding two fields, one is the date (my_date) the other is a integer (my_counter).
By some mean, you will need a table holding a list of all dates you want to use in your output.
This could be done with a temp table... (but not all hosting solution will allow you this) the other is to build it up on the fly, using a view or a stored procedure.
Then you will LEFT JOIN this table/view/stored procedure/etc... to your table my_visits based on the date field.
This will output you all dates, and when there won't be a match in mour my_visits you'll have a NULL value. ( IFNULL(my_visits.my_counter, 0) will give you a 0 (zero) when there is no matching value.
inspiration:
Get a list of dates between two dates +
How to get list of dates between two dates in mysql select query and a nice solution here that needs no loops, procedures, or temp tables generate days from date range
Based on that last link, here we go...
first a sample table
DROP TABLE IF EXISTS `my_stats`;
CREATE TABLE IF NOT EXISTS `my_stats` (
`my_date` date NOT NULL,
`my_counter` int(11) NOT NULL
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4;
INSERT INTO `my_stats` (`my_date`, `my_counter`) VALUES
('2017-11-01', 2),
('2017-11-02', 3),
('2017-11-03', 5),
('2017-11-05', 3),
('2017-11-07', 7);
And now a working exemple BETWEEN '2017-11-01' AND '2017-11-09'
SELECT date_range.date AS the_date,
IFNULL(my_stats.my_counter, 0) AS the_counter
FROM (
SELECT a.date
FROM (
SELECT Curdate() - INTERVAL (a.a + (10 * b.a) + (100 * c.a)) day
AS date
FROM (
SELECT 0 AS a
UNION ALL
SELECT 1
UNION ALL
SELECT 2
UNION ALL
SELECT 3
UNION ALL
SELECT 4
UNION ALL
SELECT 5
UNION ALL
SELECT 6
UNION ALL
SELECT 7
UNION ALL
SELECT 8
UNION ALL
SELECT 9
) AS a
CROSS JOIN (
SELECT 0 AS a
UNION ALL
SELECT 1
UNION ALL
SELECT 2
UNION ALL
SELECT 3
UNION ALL
SELECT 4
UNION ALL
SELECT 5
UNION ALL
SELECT 6
UNION ALL
SELECT 7
UNION ALL
SELECT 8
UNION ALL
SELECT 9
) AS b
CROSS JOIN (
SELECT 0 AS a
UNION ALL
SELECT 1
UNION ALL
SELECT 2
UNION ALL
SELECT 3
UNION ALL
SELECT 4
UNION ALL
SELECT 5
UNION ALL
SELECT 6
UNION ALL
SELECT 7
UNION ALL
SELECT 8
UNION ALL
SELECT 9
) AS c
) AS a
WHERE a.date BETWEEN '2017-11-01' AND '2017-11-09'
) AS date_range
LEFT JOIN my_stats
ON( date_range.date = my_stats.my_date )
ORDER BY the_date ASC
Output
+------------+-------------+
| the_date | the_counter |
+------------+-------------+
| 2017-11-01 | 2 |
| 2017-11-02 | 3 |
| 2017-11-03 | 5 |
| 2017-11-04 | 0 |
| 2017-11-05 | 3 |
| 2017-11-06 | 0 |
| 2017-11-07 | 7 |
| 2017-11-08 | 0 |
| 2017-11-09 | 0 |
+------------+-------------+
Description:
I want to select my site content's categories. Most of them will be created by users so I will ve to deal with problem of many categories in table. I want to respect some kind content's trends on my site. My solution is:
Select all categories from past 2 days and sort it by number of appearances (ascending),
Union query (distinct)
Select all categories from date < past 2 days and sort it like above.
Thanks to it I ve all most popular categories from small amount of time + most popular categories in global scope.
Query:
(SELECT category, COUNT(*) AS number FROM data WHERE date BETWEEN ADDDATE(NOW(), INTERVAL -2 DAY) AND NOW() GROUP BY category)
UNION
(SELECT category, COUNT(*) AS number FROM data WHERE date < ADDDATE(NOW(), INTERVAL -2 DAY) GROUP BY category)
ORDER BY number DESC LIMIT 50
Output:
+----------+--------+
| category | number |
+----------+--------+
| 2 | 3 |
| 4 | 3 |
| 6 | 3 |
| 5 | 2 |
| 1 | 2 |
| 2 | 1 |
+----------+--------+
6 rows in set (0.00 sec)
Note there is duplicated content in category (id 2), UNION DISTINCT (default) is not excluding this because it compares rows from both columns, so:
+----------+--------+
| category | number |
+----------+--------+
| 2 | 3 | //is not equal to
| 2 | 1 | //below values
+----------+--------+
//wont be excluded
Problem to slove:
I need to select distinct values from only category column.
(number is only for sorting purposes and used only in this query)
If I understand your question correctly, this should be the query that you need:
SELECT category
FROM (
SELECT category, COUNT(*) AS number
FROM data WHERE date BETWEEN ADDDATE(NOW(), INTERVAL -2 DAY) AND NOW()
GROUP BY category
UNION ALL
SELECT category, COUNT(*) AS number
FROM data WHERE date < ADDDATE(NOW(), INTERVAL -2 DAY)
GROUP BY category
ORDER BY number DESC
) s
GROUP BY category
ORDER BY MAX(number) DESC
LIMIT 50
I removed brackets () around your two queries that make your union query because the ORDER BY of your UNION query will be applied to both. I also used UNION ALL instead of UNION because categories are grouped again in the outer query, i would try both UNION/UNION ALL to see which one is faster.
Then I'm grouping again, by category, and ordering by the MAX(number) of your category, and keeping only the first 50 rows.
How can I get the current week in a generic query, currently I can get a date range but I would like to get the current week in a dynamic way.
This is what I have:
WITH mycte AS
(
SELECT CAST('2011-01-01' AS DATETIME) DateValue
UNION ALL
SELECT DateValue + 1
FROM mycte
WHERE DateValue + 1 < '2021-12-31'
)
SELECT DateValue
FROM mycte
OPTION (MAXRECURSION 0)
Based on todays date I want to get something like:
11-04-2013 11-05-2013 11-06-2013 11-07-2013 11-08-2013 11-09-2013 11-10-2013
One way of doing it in SQL Server
WITH weekdays AS
(
SELECT 0 day
UNION ALL
SELECT day + 1 FROM weekdays WHERE day < 6
)
SELECT DATEADD(DAY, day, DATEADD(DAY, 2-DATEPART(WEEKDAY, CONVERT (date, GETDATE())), CONVERT (date, GETDATE()))) date
FROM weekdays
Output:
| DATE |
|------------|
| 2013-11-04 |
| 2013-11-05 |
| 2013-11-06 |
| 2013-11-07 |
| 2013-11-08 |
| 2013-11-09 |
| 2013-11-10 |
Here is SQLFiddle demo
In MySQL
SELECT CURDATE() + INTERVAL 1 - DAYOFWEEK(CURDATE()) DAY + INTERVAL day DAY date
FROM
(
SELECT 1 day UNION ALL
SELECT 2 UNION ALL
SELECT 3 UNION ALL
SELECT 4 UNION ALL
SELECT 5 UNION ALL
SELECT 6 UNION ALL
SELECT 7
) w
Here is SQLFiddle demo
I'm in trouble with a mysql statement counting appointments for one day within a given time period. I've got a calendar table including starting and finishing column (type = DateTime). The following statement should count all appointments for November including overall appointments:
SELECT
COUNT('APPOINTMENTS') AS Count,
DATE(c.StartingDate) AS Datum
FROM t_calendar c
WHERE
c.GUID = 'blalblabla' AND
((DATE(c.StartingDate) <= DATE('2012-11-01 00:00:00')) AND (DATE(c.EndingDate) >= DATE('2012-11-30 23:59:59'))) OR
((DATE(c.StartingDate) >= DATE('2012-11-01 00:00:00')) AND (DATE(c.EndingDate) <= DATE('2012-11-30 23:59:59')))
GROUP BY DATE(c.StartingDate)
HAVING Count > 1
But how to include appointments that starts before a StartingDate and ends on the StartingDate?
e.g.
StartingDate = 2012-11-14 17:00:00, EndingDate = 2012-11-15 08:00:00
StartingDate = 2012-11-15 09:00:00, EndingDate = 2012-11-15 10:00:00
StartingDate = 2012-11-15 11:00:00, EndingDate = 2012-11-15 12:00:00
My statement returns a count of 2 for 15th of November. But that's wrong because the first appointment is missing. How to include these appointments? What I am missing, UNION SELECT, JOIN, sub selection?
A possible solution?
SELECT
c1.GUID, COUNT('APPOINTMENTS') + COUNT(DISTINCT c2.ANYFIELD) AS Count,
DATE(c1.StartingDate) AS Datum,
COUNT(DISTINCT c2.ANYFIELD)
FROM
t_calendar c1
LEFT JOIN
t_calendar c2
ON
c2.ResourceGUID = c1.ResourceGUID AND
(DATE(c2.EndingDate) = DATE(c1.StartingDate)) AND
(DATE(c2.StartingDate) < DATE(c1.StartingDate))
WHERE
((DATE(c1.StartingDate) <= DATE('2012-11-01 00:00:00')) AND (DATE(c1.EndingDate) >= DATE('2012-11-30 23:59:59'))) OR
((DATE(c1.StartingDate) >= DATE('2012-11-01 00:00:00')) AND (DATE(c1.EndingDate) <= DATE('2012-11-30 23:59:59')))
GROUP BY
c1.ResourceGUID,
DATE(c1.StartingDate)
First: Consolidate range checking
First of all your two range where conditions can be replaced by a single one. And it also seems that you're only counting appointments that either completely overlap target date range or are completely contained within. Partially overlapping ones aren't included. Hence your question about appointments that end right on the range starting date.
To make where clause easily understandable I'll simplify it by using:
two variables to define target range:
rangeStart (in your case 1st Nov 2012)
rangeEnd (I'll rather assume to 1st Dec 2012 00:00:00.00000)
won't be converting datetime to dates only (using date function) the way that you did, but you can easily do that.
With these in mind your where clause can be greatly simplified and covers all appointments for given range:
...
where (c.StartingDate < rangeEnd) and (c.EndingDate >= rangeStart)
...
This will search for all appointments that fall in target range and will cover all these appointment cases:
start end
target range |==============|
partial front |---------|
partial back |---------|
total overlap |---------------------|
total containment |-----|
Partial front/back may also barely touch your target range (what you've been after).
Second: Resolving the problem
Why you're missing the first record? Simply because of your having clause that only collects those groups that have more than 1 appointment starting on a given day: 15th Nov has two, but 14th has only one and is therefore excluded because Count = 1 and is not > 1.
To answer your second question what am I missing is: you're not missing anything, actually you have too much in your statement and needs to simplified.
Try this statement instead that should return exactly what you're after:
select count(c.GUID) as Count,
date(c.StartingDate) as Datum
from t_calendar c
where (c.GUID = 'blabla') and
(c.StartingDate < str_to_date('2012-12-01', '%Y-%m-%d') and
(c.EndingDate >= str_to_date('2012-11-01', '%Y-%m-%d'))
group by date(c.StartingDate)
I used str_to_date function to make string to date conversion more safe.
I'm not really sure why you included having in your statement, because it's not really needed. Unless your actual statement is more complex and you only included part that's most relevant. In that case you'll likely have to change it to:
having Count > 0
Getting appointment count per day in any given date range
There are likely other ways as well but the most common way would be using a numbers or ?calendar* table that gives you the ability to break a range into individual points - days. They you have to join your appointments to this numbers table and provide results.
I've created a SQLFiddle that does the trick. Here's what it does...
Suppose you have numbers table Num with numbers from 0 to x. And appointments table Cal with your records. Following script created these two tables and populates some data. Numbers are only up to 100 which is enough for 3 months worth of data.
-- appointments
create table Cal (
Id int not null auto_increment primary key,
StartDate datetime not null,
EndDate datetime not null
);
-- create appointments
insert Cal (StartDate, EndDate)
values
('2012-10-15 08:00:00', '2012-10-20 16:00:00'),
('2012-10-25 08:00:00', '2012-11-01 03:00:00'),
('2012-11-01 12:00:00', '2012-11-01 15:00:00'),
('2012-11-15 10:00:00', '2012-11-16 10:00:00'),
('2012-11-20 08:00:00', '2012-11-30 08:00:00'),
('2012-11-30 22:00:00', '2012-12-05 00:00:00'),
('2012-12-01 05:00:00', '2012-12-10 12:00:00');
-- numbers table
create table Nums (
Id int not null primary key
);
-- add 100 numbers
insert into Nums
select a.a + (10 * b.a)
from (select 0 as a union all
select 1 union all
select 2 union all
select 3 union all
select 4 union all
select 5 union all
select 6 union all
select 7 union all
select 8 union all
select 9) as a,
(select 0 as a union all
select 1 union all
select 2 union all
select 3 union all
select 4 union all
select 5 union all
select 6 union all
select 7 union all
select 8 union all
select 9) as b
Now what you have to do now is
Select a range of days which you do by selecting numbers from Num table and convert them to dates.
Then join your appointments to those dates so that those appointments that fall on particular day are joined to that particular day
Then just group all these appointments per each day and get results
Here's the code that does this:
-- just in case so comparisons don't trip over
set names 'latin1' collate latin1_general_ci;
-- start and end target date range
set #s := str_to_date('2012-11-01', '%Y-%m-%d');
set #e := str_to_date('2012-12-01', '%Y-%m-%d');
-- get appointment count per day within target range of days
select adddate(#s, n.Id) as Day, count(c.Id) as Appointments
from Nums n
left join Cal c
on ((date(c.StartDate) <= adddate(#s, n.Id)) and (date(c.EndDate) >= adddate(#s, n.Id)))
where adddate(#s, n.Id) < #e
group by Day;
And this is the result of this rather simple select statement:
| DAY | APPOINTMENTS |
-----------------------------
| 2012-11-01 | 2 |
| 2012-11-02 | 0 |
| 2012-11-03 | 0 |
| 2012-11-04 | 0 |
| 2012-11-05 | 0 |
| 2012-11-06 | 0 |
| 2012-11-07 | 0 |
| 2012-11-08 | 0 |
| 2012-11-09 | 0 |
| 2012-11-10 | 0 |
| 2012-11-11 | 0 |
| 2012-11-12 | 0 |
| 2012-11-13 | 0 |
| 2012-11-14 | 0 |
| 2012-11-15 | 1 |
| 2012-11-16 | 1 |
| 2012-11-17 | 0 |
| 2012-11-18 | 0 |
| 2012-11-19 | 0 |
| 2012-11-20 | 1 |
| 2012-11-21 | 1 |
| 2012-11-22 | 1 |
| 2012-11-23 | 1 |
| 2012-11-24 | 1 |
| 2012-11-25 | 1 |
| 2012-11-26 | 1 |
| 2012-11-27 | 1 |
| 2012-11-28 | 1 |
| 2012-11-29 | 1 |
| 2012-11-30 | 2 |