Insert N rows with date interval - mysql

I need to insert rows into a database, where every row is the same except a date column which should have its date incremented by 1 week for each new row. So, basically this:
for(n = 0; n<X; n++)
insert into events (date, title) values (start_date + 7*n, 'static title');
Any MySQL trick that can be used to do this?

You can use:
SELECT
'static_title' AS title,
DATE_ADD(#start_date, INTERVAL #i:=#i+1 WEEK) AS result_date
FROM
(SELECT
(two_1.id + two_2.id + two_4.id +
two_8.id + two_16.id) AS id
FROM
(SELECT 0 AS id UNION ALL SELECT 1 AS id) AS two_1
CROSS JOIN (SELECT 0 id UNION ALL SELECT 2 id) AS two_2
CROSS JOIN (SELECT 0 id UNION ALL SELECT 4 id) AS two_4
CROSS JOIN (SELECT 0 id UNION ALL SELECT 8 id) AS two_8
CROSS JOIN (SELECT 0 id UNION ALL SELECT 16 id) AS two_16
) AS sequence
CROSS JOIN
-- #i:=0 for not including current week
(SELECT #i:=-1, #start_date:=CURDATE()) AS init
WHERE
sequence.id<10;
-that will produce N rows (here N=10). To insert rows, just use INSERT .. SELECT syntax. Fiddle is here. Also in sample start_date is set to CURDATE() - but you can easily adjust that in query, of course.

Related

select statement that gives a range of dates without any table?

If I have a MySQL database with ZERO tables in it. Is there an SQL statement that can return results like:
+------------+
| date |
+------------+
| 2017-06-01 |
| 2017-06-02 |
| 2017-06-03 |
| 2017-06-04 |
etc.... to any end date I want
+------------+
The reason I want this is because I want to be able to generate a table like this on the fly to help me with some queries in a different database.
In MariaDB, you can use a built-in seq table to do this. This query, for example, returns the 100 days starting at 1-Nov-2017
SELECT '2017-11-01' + INTERVAL seq.seq DAY AS sequential_day FROM seq_0_to_99 seq
In MySQL, you need to engage in some monkey business to get a sequence of numbers with no tables. This ugly little query generates the numbers from zero to 15,625.
SELECT A.N + 5*(B.N + 5*(C.N + 5*(D.N + 5*(E.N + 5*(F.N))))) AS seq
FROM (SELECT 0 AS N UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4) AS A
JOIN (SELECT 0 AS N UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4) AS B
JOIN (SELECT 0 AS N UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4) AS C
JOIN (SELECT 0 AS N UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4) AS D
JOIN (SELECT 0 AS N UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4) AS E
JOIN (SELECT 0 AS N UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4) AS F
You can use it as a subquery to generate a sequence of dates.
select '2017-11-01' + INTERVAL seq.seq DAY AS sequential_day
from (
SELECT A.N + 5*(B.N + 5*(C.N + 5*(D.N + 5*(E.N + 5*(F.N))))) AS seq
FROM (SELECT 0 AS N UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4) AS A
JOIN (SELECT 0 AS N UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4) AS B
JOIN (SELECT 0 AS N UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4) AS C
JOIN (SELECT 0 AS N UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4) AS D
JOIN (SELECT 0 AS N UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4) AS E
JOIN (SELECT 0 AS N UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4) AS F
) AS seq
where seq.seq <= 99
It's not very elegant. It fact, it's ugly. But it works fine.
Or you can make yourself a date table and use it.
I think you can do it via Stored Procedures and Inline Tables
drop procedure if exists timer;
CREATE PROCEDURE timer()
BEGIN
DECLARE i INT DEFAULT 1;
declare d date default now();
drop table B;
create table B (id date);
WHILE (d<='2017-11-23') DO
insert into B values(d);
set d = CURRENT_DATE()+i;
set i = i+1;
END WHILE;
select * from B;
END;
And call this SP when you need such that CALL timer();
NOTE I am not expert in MYSQL and I depend on those answers to compose this answers (: so you can also benefit from, compare dates in mysql, Inline tables in mysql, MySQL functions, and also While Loops
Also Note you can use IN parameter for a target date in the stored procedure
You can use subquery to declare the first date and then increment it, like following:
SELECT #date := DATE_ADD(#date, INTERVAL 1 DAY) AS dates
FROM mysql.help_relation , (
SELECT #date:= DATE_SUB('2017-06-01', INTERVAL 1 DAY)) d
WHERE #date BETWEEN #date AND DATE_SUB('2017-06-04', INTERVAL 1 DAY
);
One method is to create a loop and insert. Note I'm not keen on using "date" as a column name as it gets way too confusing in queries. Also suggest the date is used as primary key.
delimiter \\
## Create the calendar table.
CREATE TABLE calendar (
cal_date date primary key
);
\\
## accepts a date range
CREATE PROCEDURE create_calendar(IN startdate date, IN enddate date)
BEGIN
SET #x = 0;
WHILE (startdate + INTERVAL #x DAY) < enddate DO
## Insert another row
INSERT INTO calendar (cal_date) VALUES (startdate + INTERVAL #x DAY);
SET #x = #x + 1;
END WHILE;
END
\\
## populate the calendar table with wanted range
## nb the enddate is NOT included in the table
CALL create_calendar('2017-01-01','2017-02-01');
\\
select * from calendar;
\\
When you need more dates in the table, re-run the procedure with the wanted range (but those dates must not already exist in the calendar table).
Derived from How to Create a Tally Table in MySQL

MYSQL - Total registrations per day

I have the following structure in my user table:
id(INT) registered(DATETIME)
1 2016-04-01 23:23:01
2 2016-04-02 03:23:02
3 2016-04-02 05:23:03
4 2016-04-03 04:04:04
I want to get the total (accumulated) user count per day, for all days in DB
So result should be something like
day total
2016-04-01 1
2016-04-02 3
2016-04-03 4
I tried some sub querying, but somehow i have now idea how to achieve this with possibly 1 SQL statement. Of course if could group by per day count and add them programmatically, but i don't want to do that if possible.
You can use a GROUP BY that does all the counts, without the need of doing anything programmatically, please have a look at this query:
select
d.dt,
count(*) as total
from
(select distinct date(registered) dt from table1) d inner join
table1 r on d.dt>=date(r.registered)
group by
d.dt
order by
d.dt
the first subquery returns all distinct dates, then we can join all dates with all previous registrations, and do the counts, all in one query.
An alternative join condition that can give some improvements in performance is:
on d.dt + interval 1 day > r.registered
Not sure why not just use GROUP BY, without it this thing will be more complicated, anyway, try this;)
select
date_format(main.registered, '%Y-%m-%d') as `day`,
main.total
from (
select
table1.*,
#cnt := #cnt + 1 as total
from table1
cross join (select #cnt := 0) t
) main
inner join (
select
a.*,
if(#param = date_format(registered, '%Y-%m-%d'), #rowno := #rowno + 1 ,#rowno := 1) as rowno,
#param := date_format(registered, '%Y-%m-%d')
from (select * from table1 order by registered desc) a
cross join (select #param := null, #rowno := 0) tmp
having rowno = 1
) sub on main.id = sub.id
SQLFiddle DEMO

MySQL infinite loop in a subquery?

The following query probably results an infinite loop:
SELECT
*,
(SELECT
t2.`value`
FROM
`table` t2
WHERE
t2.`variable` = 'xxx'
AND t2.`read` = (SELECT
MAX(t1.`read`)
FROM
`table` t1
WHERE
t1.`variable` = 'xxx'
AND UNIX_TIMESTAMP(t1.`read`) < (1401801648 - n.integers)
)
)
FROM
(SELECT
#N:=#N + 1 AS integers
FROM
mysql.help_relation, (SELECT #N:=0) dum
LIMIT 48) n
I need a result with 48 rows for 48 different time ranges (In this example 1401801648 minus {1..48}). Each row should contain a value depending on the current time range. The query on the bottom is for these 48 ranges.
The query in the middle is needed to find the date for the newest entry which is older than the calculated timestamp (1401801648 - n.integers). The upper query tells me the value of the row with the date from the query in the middle.
When the "n.integers" is replaced by a number everything works fine.
Without the subquery (t2) the query is not in a loop(?):
SELECT
*,
(SELECT
MAX(t1.`read`)
FROM
`table` t1
WHERE
t1.`variable` = 'xxx'
AND UNIX_TIMESTAMP(t1.`read`) < (1401801648 - n.integers)
)
FROM
(SELECT
#N:=#N + 1 AS integers
FROM
mysql.help_relation, (SELECT #N:=0) dum
LIMIT 48) AS n
An alternative method avoiding using variables:-
SELECT sub1.a_cnt, t2.value
FROM table t2
INNER JOIN
(
SELECT sub1.a_timestamp, sub1.a_cnt, t1.variable, MAX(t1.read) AS max_timestamp
FROM
(
SELECT (1401801648 - units.i + 10 * tens.i) AS a_cnt,(1401801648 - units.i + 10 * tens.i) AS a_timestamp
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) units,
(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) tens
WHERE units.i + 10 * tens.i BETWEEN 1 AND 48
) sub1
INNER JOIN table t1
AND UNIX_TIMESTAMP(t1.read) < sub1.a_timestamp
WHERE t1.variable = 'xxx'
GROUP BY sub1.a_timestamp
) sub2
ON t2.read = sub2.max_timestamp
AND t2.variable = sub2.variable
This uses a load of unioned queries getting constants to generate the numbers 0 to 9, cross joins that against another copy of itself and does a minor calulation to get all the numbers from 0 to 99, with a WHERE clause to narrow it down to the range 1 to 48, and uses this to calculate the timestamps required.
This is then joined against your table to get the max read date for each timestamp / generated number.
The results of this are then joined back against your table to get the other details from that row (in this case your value column).
Not tested it but hopefully it gives you an idea.

SQL statement to insert repetitive data

current situation is to add below value of A01, B03, Z11 and X21 in repetitive way in field code for 400 hundreds row of data in table BabyCode.
Above is current table - without value in 'Code" column
Above is to be updated table - repetitive value is added in 'Code' column
You can do this:
INSERT INTO BabyCode
SELECT Codes.Code
FROM
(
SELECT id
FROM
(
SELECT t3.digit * 100 + t2.digit * 10 + t1.digit + 1 AS id
FROM TEMP AS t1
CROSS JOIN TEMP AS t2
CROSS JOIN TEMP AS t3
) t
WHERE id <= 400
) t,
(
SELECT 1 AS ID, 'A01' AS Code
UNION ALL
SELECT 2, 'B03'
UNION ALL
SELECT 3, 'Z11'
UNION ALL
SELECT 4, 'X21'
) codes;
But you will need to define a temp table, to use as an anchor table:
CREATE TABLE TEMP (Digit int);
INSERT INTO Temp VALUES(0),(1),(2),(3),(4),(5),(6),(7),(8),(9);
SQL Fiddle Demo
This will insert 400 hundred rows of the values A01, B03, Z11, and X21, into the code column in the table BabyCode.
You could put the four values into a virtual table identical to that used in #Mahmoud Gamal's answer, and, if the ID values in your table start at 1 and are sequential (have neither gaps nor duplicates), you could use the following method to join to the virtual table and update the target's Code column:
UPDATE YourTable t
INNER JOIN (
SELECT 1 AS ID, 'A01' AS Code
UNION ALL SELECT 2, 'B03'
UNION ALL SELECT 3, 'Z11'
UNION ALL SELECT 4, 'X21'
) x
ON (t.ID - 1) MOD 4 + 1 = x.ID
SET t.Code = x.Code
;
Otherwise you could use variables to assign 1, 2, 3, 4 sequentially to every row of your table, then you would be able join to the virtual table using those values:
UPDATE YourTable t
INNER JOIN (
SELECT ID, #rnk := CASE WHEN #rnk = 4 THEN 0 ELSE #rnk END + 1 AS rnk
FROM YourTable
CROSS JOIN (SELECT #rnk := 0) x
ORDER BY ID
) r ON t.ID = r.ID
INNER JOIN (
SELECT 1 AS ID, 'A01' AS Code
UNION ALL SELECT 2, 'B03'
UNION ALL SELECT 3, 'Z11'
UNION ALL SELECT 4, 'X21'
) x
ON r.rnk = x.ID
SET t.Code = x.Code
;
Both queries can be played with at SQL Fiddle:
Method 1
Method 2

How to generate data in MySQL?

Here is my SQL:
SELECT
COUNT(id),
CONCAT(YEAR(created_at), '-', MONTH(created_at), '-', DAY(created_at))
FROM my_table
GROUP BY YEAR(created_at), MONTH(created_at), DAY(created_at)
I want a row to show up even for days where there was no ID created. Right now I'm missing a ton of dates for days where there was no activity.
Any thoughts on how to change this query to do that?
SQL is notoriously bad at returning data that is not in the database. You can find the beginning and ending values for gaps of dates, but getting all the dates is hard.
The solution is to create a calendar table with one record for each date and OUTER JOIN it to your query.
Here is an example assuming that created_at is type DATE:
SELECT calendar_date, COUNT(`id`)
FROM calendar LEFT OUTER JOIN my_table ON calendar.calendar_date = my_table.created_at
GROUP BY calendar_date
(I'm guessing that created_at is really DATETIME, so you'll have to do a bit more gymnastics to JOIN the tables).
General idea
There are two main approaches to generating data in MySQL. One is to generate the data on the fly when running the query and the other one is to have it in the database and using it when necessary. Of course, the second one would be faster than the first one if you're going to run your query frequently. However, the second one will require a table in the database which only purpose will be to generate the missing data. It will also require you to have privileges enough to create that table.
Dynamic data generation
This approach involves making UNIONs to generate a fake table that can be used to join the actual table with. The awful and repetitive query is:
select aDate from (
select #maxDate - interval (a.a+(10*b.a)+(100*c.a)+(1000*d.a)) day aDate 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) a, /*10 day range*/
(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) b, /*100 day range*/
(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) c, /*1000 day range*/
(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) d, /*10000 day range*/
(select #minDate := '2001-01-01', #maxDate := '2002-02-02') e
) f
where aDate between #minDate and #maxDate
Anyway, it is simpler than it seems. It makes cartesian products of derived tables with 10 numeric values so the result will have 10^X rows where X is the amount of derived tables in the query. In this example there is 10000 day range so you would be able to represent periods of over 27 years. If you need more, add another UNION to the query and update the interval, and if you don't need so many you can remove UNIONs or individual values from the derived tables. Just to clarify, you can fine tune the date period by applying a filter with a WHERE clause on #minDate and #maxDate variables (but don't use a longer period than the one you created with the cartesian products).
Static data generation
This solution will require you to generate a table in your database. The approach is similar to the previous one. You'll have to first insert data into that table: a range of integers ranging from 1 to X where X is the maximum needed range. Again, if you are unsure just insert 100000 values and you'll be able to create day ranges for over 273 years. So, once you've got the integer sequence, you can transform it into a date range like this:
select '2012-01-01' + interval value - 1 day aDay from seq
having aDay <= '2012-01-05'
Assuming a table named seq with a column named value. On top the from date and at the bottom the to date.
Turning this into something useful
Ok, now we have our date periods generated but we're still missing a way to query data and display the missing values as an actual 0. This is where left join comes to the rescue. To make sure we're all on the same page, a left join is similar to an inner join but with only one difference: it will preserve all records from the left table of the join, regardless of whether there is a matching record on the table of the right. In other words, an inner join will remove all non-matched rows on the join while the left join will keep the ones on the left table and, for the records on the left that have no matching record on the right table, the left join will fill that "space" with a null value.
So we should join our domain table (the one that has "missing" data) with our newly generated table putting the latter on the left part of the join and the former on the right, so that all elements are considered, regardless of their presence in the domain table.
For example, if we had a table domainTable with fields ID, birthDate and we would like to see a count of all the birthDate in the first 5 days of 2012 per day and if the count is 0 to show that value, then this query could be run:
select allDays.aDay, count(dt.id) from (
select '2012-01-01' + interval value - 1 day aDay from seq
having aDay <= '2012-01-05'
) allDays
left join domainTable dt on allDays.aDay = dt.birthDate
group by allDays.aDay
This generates a derived table with all the requried days (notice I'm using the static data generation) and performs a left join against our domain table, so all days will be displayed, regardless of whether they have a matching values in our domain tables. Also note the count should be done on the field that will have null values as those are not counted.
Notes to be considered
1) The queries can be used to query other intervals (months, years) performing small changes to the code
2) Instead of hardcoding the dates you can query for min and max values from the domain tables like this:
select (select min(aDate) from domainTable) + interval value - 1 day aDay
from seq
having aDay <= (select max(aDate) from domainTable)
This would avoid generating more records than necessary.
Actually answering your question
I think you should have already figured out how to do what you want. Anyway, here are the steps so that others can benefit from them too. Firstly, create the integer table. Secondly, run this query:
select allDays.aDay, count(mt.id) aCount from (
select (select date(min(created_at)) from my_table) + interval value - 1 day aDay
from seq s
having aDay <= (select date(max(created_at)) from my_table)
) allDays
left join my_table mt on allDays.aDay = date(mt.created_at)
group by allDays.aDay
I guess created_at is a datetime and that's why you're concatenating that way. However, that happens to be the way MySQL natively stores dates, so I'm just grouping by the date field but casting the created_at to an actual date datatype. You can play with it using this fiddle.
And here is the solution generating data dynamically:
select allDays.aDay, count(mt.id) aCount from (
select #maxDate - interval a.a day aDay 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) a, /*10 day range*/
(select #minDate := (select date(min(created_at)) from my_table),
#maxDate := (select date(max(created_at)) from my_table)) e
where #maxDate - interval a.a day between #minDate and #maxDate
) allDays
left join my_table mt on allDays.aDay = date(mt.created_at)
group by allDays.aDay
As you can see the skeleton of the query is the same as the previous one. The only thing that changes is how the derived table allDays is generated. Now, the way the derived table is generated is also slightly different from the one I added before. This is because in the example filddle I only needed a 10-day range. As you can see, it is more readable than adding a 1000 day range. Here is the fiddle for the dynamic solution so that you can play with it too.
Hope this helps!
The way to do it in one query:
SELECT COUNT(my_table.id) AS total,
CONCAT(YEAR(dates.ddate), '-', MONTH(dates.ddate), '-', DAY(dates.ddate))
FROM (
-- Creates "on the fly" 65536 days beginning from 2000-01-01 (179 years)
SELECT DATE_ADD("2000-01-01", INTERVAL (b1.b + b2.b + b3.b + b4.b + b5.b + b6.b + b7.b + b8.b + b9.b + b10.b + b11.b + b12.b + b13.b + b14.b + b15.b + b16.b) DAY) AS ddate FROM
(SELECT 0 AS b UNION SELECT 1) b1,
(SELECT 0 AS b UNION SELECT 2) b2,
(SELECT 0 AS b UNION SELECT 4) b3,
(SELECT 0 AS b UNION SELECT 8) b4,
(SELECT 0 AS b UNION SELECT 16) b5,
(SELECT 0 AS b UNION SELECT 32) b6,
(SELECT 0 AS b UNION SELECT 64) b7,
(SELECT 0 AS b UNION SELECT 128) b8,
(SELECT 0 AS b UNION SELECT 256) b9,
(SELECT 0 AS b UNION SELECT 512) b10,
(SELECT 0 AS b UNION SELECT 1024) b11,
(SELECT 0 AS b UNION SELECT 2048) b12,
(SELECT 0 AS b UNION SELECT 4096) b13,
(SELECT 0 AS b UNION SELECT 8192) b14,
(SELECT 0 AS b UNION SELECT 16384) b15,
(SELECT 0 AS b UNION SELECT 32768) b16
) dates
LEFT JOIN my_table ON dates.ddate = my_table.created_at
GROUP BY dates.ddate
ORDER BY dates.ddate
The next code is only necessary if you want to test and don't have the "my_table" indicated on the question:
create table `my_table` (
`id` int (11),
`created_at` date
);
insert into `my_table` (`id`, `created_at`) values('1','2000-01-01');
insert into `my_table` (`id`, `created_at`) values('2','2000-01-01');
insert into `my_table` (`id`, `created_at`) values('3','2000-01-01');
insert into `my_table` (`id`, `created_at`) values('4','2001-01-01');
insert into `my_table` (`id`, `created_at`) values('5','2100-06-06');
Testbed:
create table testbed (id integer, created_at date);
insert into testbed values
(1, '2012-04-01'),
(1, '2012-04-30'),
(2, '2012-04-02'),
(3, '2012-04-03'),
(3, '2012-04-04'),
(4, '2012-04-04');
I also use any_table, which I created artificially like this:
create table any_table (id integer);
insert into any_table values (1), (2), (3), (4), (5), (6), (7), (8), (9), (10);
insert into any_table select * from any_table; -- repeat this insert 7-8 times
You can use any table in your database that is expected to have more rows then max(created_dt) - min(created_dt) range, at least 365 to cover a year.
Query:
SELECT concat(year(dr._date),'-',month(dr._date),'-',day(dr._date)),
-- or, instead of concat(), simply: dr._date
count(id)
FROM (
SELECT date_add(r.mindt, INTERVAL #dist day) _date,
#dist := #dist + 1 AS days_away
FROM any_table t
JOIN (SELECT min(created_at) mindt,
max(created_at) maxdt,
#dist := 0
FROM testbed) r
WHERE date_add(r.mindt, INTERVAL #dist day) <= r.maxdt) dr
LEFT JOIN testbed tb ON dr._date = tb.created_at
GROUP BY dr._date;