Utiliser les fonctions standard de MySQL est un moyen d'écrire une requête qui retournera une liste de jours entre deux dates.
par exemple, étant donné que 2009-01-01 et 2009-01-13, il renverrait un tableau à une colonne avec les valeurs:
2009-01-01
2009-01-02
2009-01-03
2009-01-04
2009-01-05
2009-01-06
2009-01-07
2009-01-08
2009-01-09
2009-01-10
2009-01-11
2009-01-12
2009-01-13
Edit: Il semble que je n'ai pas été clair. Je veux générer cette liste. J'ai des valeurs stockées dans la base de données (par date/heure) mais je souhaite qu'elles soient agrégées dans une jointure externe gauche avec une liste de dates comme ci-dessus (j'attends null du côté droit de certaines de ces jointures pendant quelques jours et je gérerai ).
Je voudrais utiliser cette procédure stockée pour générer les intervalles dont vous avez besoin dans la table temporaire nommée time_intervals, puis JOIN et agréger votre table de données avec la table temp time_intervals.
La procédure peut générer des intervalles de tous les différents types que vous voyez spécifiés dans celle-ci:
call make_intervals('2009-01-01 00:00:00','2009-01-10 00:00:00',1,'DAY')
.
select * from time_intervals
.
interval_start interval_end
------------------- -------------------
2009-01-01 00:00:00 2009-01-01 23:59:59
2009-01-02 00:00:00 2009-01-02 23:59:59
2009-01-03 00:00:00 2009-01-03 23:59:59
2009-01-04 00:00:00 2009-01-04 23:59:59
2009-01-05 00:00:00 2009-01-05 23:59:59
2009-01-06 00:00:00 2009-01-06 23:59:59
2009-01-07 00:00:00 2009-01-07 23:59:59
2009-01-08 00:00:00 2009-01-08 23:59:59
2009-01-09 00:00:00 2009-01-09 23:59:59
.
call make_intervals('2009-01-01 00:00:00','2009-01-01 02:00:00',10,'MINUTE')
.
select * from time_intervals
.
interval_start interval_end
------------------- -------------------
2009-01-01 00:00:00 2009-01-01 00:09:59
2009-01-01 00:10:00 2009-01-01 00:19:59
2009-01-01 00:20:00 2009-01-01 00:29:59
2009-01-01 00:30:00 2009-01-01 00:39:59
2009-01-01 00:40:00 2009-01-01 00:49:59
2009-01-01 00:50:00 2009-01-01 00:59:59
2009-01-01 01:00:00 2009-01-01 01:09:59
2009-01-01 01:10:00 2009-01-01 01:19:59
2009-01-01 01:20:00 2009-01-01 01:29:59
2009-01-01 01:30:00 2009-01-01 01:39:59
2009-01-01 01:40:00 2009-01-01 01:49:59
2009-01-01 01:50:00 2009-01-01 01:59:59
.
I specified an interval_start and interval_end so you can aggregate the
data timestamps with a "between interval_start and interval_end" type of JOIN.
.
Code for the proc:
.
-- drop procedure make_intervals
.
CREATE PROCEDURE make_intervals(startdate timestamp, enddate timestamp, intval integer, unitval varchar(10))
BEGIN
-- *************************************************************************
-- Procedure: make_intervals()
-- Author: Ron Savage
-- Date: 02/03/2009
--
-- Description:
-- This procedure creates a temporary table named time_intervals with the
-- interval_start and interval_end fields specifed from the startdate and
-- enddate arguments, at intervals of intval (unitval) size.
-- *************************************************************************
declare thisDate timestamp;
declare nextDate timestamp;
set thisDate = startdate;
-- *************************************************************************
-- Drop / create the temp table
-- *************************************************************************
drop temporary table if exists time_intervals;
create temporary table if not exists time_intervals
(
interval_start timestamp,
interval_end timestamp
);
-- *************************************************************************
-- Loop through the startdate adding each intval interval until enddate
-- *************************************************************************
repeat
select
case unitval
when 'MICROSECOND' then timestampadd(MICROSECOND, intval, thisDate)
when 'SECOND' then timestampadd(SECOND, intval, thisDate)
when 'MINUTE' then timestampadd(MINUTE, intval, thisDate)
when 'HOUR' then timestampadd(HOUR, intval, thisDate)
when 'DAY' then timestampadd(DAY, intval, thisDate)
when 'WEEK' then timestampadd(WEEK, intval, thisDate)
when 'MONTH' then timestampadd(MONTH, intval, thisDate)
when 'QUARTER' then timestampadd(QUARTER, intval, thisDate)
when 'YEAR' then timestampadd(YEAR, intval, thisDate)
end into nextDate;
insert into time_intervals select thisDate, timestampadd(MICROSECOND, -1, nextDate);
set thisDate = nextDate;
until thisDate >= enddate
end repeat;
END;
Exemple de scénario de données similaire au bas de cet article , où j'ai créé une fonction similaire pour SQL Server.
Pour MSSQL, vous pouvez l'utiliser. C'est très rapide.
Vous pouvez le résumer dans une fonction à valeur de table ou une procédure stockée, puis analyser les dates de début et de fin sous forme de variables.
DECLARE @startDate DATETIME
DECLARE @endDate DATETIME
SET @startDate = '2011-01-01'
SET @endDate = '2011-01-31';
WITH dates(Date) AS
(
SELECT @startdate as Date
UNION ALL
SELECT DATEADD(d,1,[Date])
FROM dates
WHERE DATE < @enddate
)
SELECT Date
FROM dates
OPTION (MAXRECURSION 0)
GO
Nous avions un problème similaire avec les rapports BIRT en ce sens que nous voulions signaler les jours sans données. Comme il n'y avait aucune entrée pour ces dates, la solution la plus simple pour nous était de créer un tableau simple qui stockait toutes les dates et de l'utiliser pour obtenir des plages ou de rejoindre pour obtenir des valeurs nulles pour cette date.
Nous travaillons tous les mois pour nous assurer que la table est peuplée dans les 5 prochaines années. La table est créée ainsi:
create table all_dates (
dt date primary key
);
Il existe sans doute des méthodes magiques et délicates pour le faire avec différents SGBD, mais nous optons toujours pour la solution la plus simple. Les besoins de stockage pour la table sont minimes et rendent les requêtes beaucoup plus simples et portables. Ce type de solution est presque toujours meilleur du point de vue des performances car il ne nécessite pas de calculs par ligne sur les données.
L’autre option (et nous l’avons déjà utilisée) consiste à s’assurer que le tableau contient une entrée pour chaque date. Nous avons balayé la table périodiquement et ajouté zéro entrée pour les dates et/ou les heures qui n'existaient pas. Cela peut ne pas être une option dans votre cas, cela dépend des données stockées.
Si vous vraiment pensez qu'il est fastidieux de garder la table all_dates
remplie, une procédure stockée est le moyen d'aller qui renverra un ensemble de données contenant ces dates. Cela sera presque certainement plus lent, car vous devez calculer la plage à chaque appel, plutôt que de simplement extraire des données précalculées d'une table.
Mais, pour être honnête, vous pourriez remplir le tableau pendant 1 000 ans sans aucun problème sérieux de stockage de données - 365 000 dates 16 octets (par exemple) plus un index dupliquant la date plus 20% de frais généraux pour la sécurité, je dirais approximativement à environ 14M [365 000 * 16 * 2 * 1,2 = 14 016 000 octets]), une table minuscule dans le schéma de choses.
Vous pouvez utiliser les variables utilisateur de MySQL comme ceci:
SET @num = -1;
SELECT DATE_ADD( '2009-01-01', interval @num := @num+1 day) AS date_sequence,
your_table.* FROM your_table
WHERE your_table.other_column IS NOT NULL
HAVING DATE_ADD('2009-01-01', interval @num day) <= '2009-01-13'
@num est -1 parce que vous y ajoutez la première fois que vous l'utilisez. En outre, vous ne pouvez pas utiliser "HAVING date_sequence" car la variable utilisateur est incrémentée deux fois pour chaque ligne.
En empruntant une idée de this answer, vous pouvez configurer une table de 0 à 9 et l’utiliser pour générer votre liste de dates.
CREATE TABLE num (i int);
INSERT INTO num (i) VALUES (0), (1), (2), (3), (4), (5), (6), (7), (8), (9);
select adddate('2009-01-01', numlist.id) as `date` from
(SELECT n1.i + n10.i*10 + n100.i*100 AS id
FROM num n1 cross join num as n10 cross join num as n100) as numlist
where adddate('2009-01-01', numlist.id) <= '2009-01-13';
Cela vous permettra de générer une liste de 1000 dates maximum. Si vous devez augmenter la taille, vous pouvez ajouter une autre jointure croisée à la requête interne.
Pour Access (ou n'importe quel langage SQL)
Créez une table qui a 2 champs, nous appellerons cette table tempRunDates
:
-- Champs fromDate
et toDate
-- Ensuite, insérez un seul enregistrement comportant la date de début et la date de fin.
Créez une autre table: Time_Day_Ref
-- Importer une liste de dates (la liste est facile à créer dans Excel) dans ce tableau.
-- Dans mon cas, le nom du champ est Greg_Dt
, pour Date Grégorien.
-- J'ai établi ma liste du 1er janvier 2009 au 1er janvier 2020.
Exécutez la requête:
SELECT Time_Day_Ref.GREG_DT
FROM tempRunDates, Time_Day_Ref
WHERE Time_Day_Ref.greg_dt>=tempRunDates.fromDate And greg_dt<=tempRunDates.toDate;
Facile!
Généralement, on utilise une table de nombres auxiliaires que vous conservez habituellement à cette fin, avec quelques variantes:
SELECT *
FROM (
SELECT DATEADD(d, number - 1, '2009-01-01') AS dt
FROM Numbers
WHERE number BETWEEN 1 AND DATEDIFF(d, '2009-01-01', '2009-01-13') + 1
) AS DateRange
LEFT JOIN YourStuff
ON DateRange.dt = YourStuff.DateColumn
J'ai vu des variantes avec des fonctions de table, etc.
Vous pouvez également conserver une liste permanente de dates. Nous avons cela dans notre entrepôt de données ainsi qu'une liste des heures de la journée.
CREATE FUNCTION [dbo].[_DATES]
(
@startDate DATETIME,
@endDate DATETIME
)
RETURNS
@DATES TABLE(
DATE1 DATETIME
)
AS
BEGIN
WHILE @startDate <= @endDate
BEGIN
INSERT INTO @DATES (DATE1)
SELECT @startDate
SELECT @startDate = DATEADD(d,1,@startDate)
END
RETURN
END
Eh bien comment trouver des dates entre deux dates données dans le serveur SQL est expliqué sur http://ektaraval.blogspot.com/2010/09/writing-recursive-query-to-find-out-all.html
Cette solution fonctionne avec MySQL 5.0
Créer une table - mytable
.
Le schéma n'a pas d'importance. Ce qui compte, c'est le nombre de lignes qu'il contient.
Ainsi, vous ne pouvez conserver qu'une colonne de type INT avec 10 lignes, valeurs - 1 à 10.
SQL:
set @tempDate=date('2011-07-01') - interval 1 day;
select
date(@tempDate := (date(@tempDate) + interval 1 day)) as theDate
from mytable x,mytable y
group by theDate
having theDate <= '2011-07-31';
Limitation: Le nombre maximal de dates renvoyées par la requête ci-dessus sera (rows in mytable)*(rows in mytable) = 10*10 = 100.
Vous pouvez augmenter cette plage en changeant de pièce dans SQL:
de mytable x, mytable y, mytable z
Ainsi, la plage sera 10*10*10 =1000
et ainsi de suite.
Nous l'avons utilisé dans notre système de gestion des ressources humaines, vous le trouverez utile
SELECT CAST(DAYNAME(daydate) as CHAR) as dayname,daydate
FROM
(select CAST((date_add('20110101', interval H.i*100 + T.i*10 + U.i day) )as DATE) as daydate
from erp_integers as H
cross
join erp_integers as T
cross
join erp_integers as U
where date_add('20110101', interval H.i*100 + T.i*10 + U.i day ) <= '20110228'
order
by daydate ASC
)Days
select * from table_name where col_Date between '2011/02/25' AND DATEADD(s,-1,DATEADD(d,1,'2011/02/27'))
Ici, ajoutez d’abord un jour à la date de fin actuelle. Ce sera le 2011-02-28 00:00:00, puis vous soustrayez une seconde pour obtenir la date de fin le 2011-02-27 23:59:59. En faisant cela, vous pouvez obtenir toutes les dates entre les intervalles donnés.
sortie:
2011/02/25
2011/02/26
2011/02/27
Je me bats avec ça depuis pas mal de temps. Comme c’est le premier coup sur Google lorsque j’ai cherché la solution, laissez-moi vous dire où j’en suis arrivé.
SET @d := '2011-09-01';
SELECT @d AS d, cast( @d := DATE_ADD( @d , INTERVAL 1 DAY ) AS DATE ) AS new_d
FROM [yourTable]
WHERE @d <= '2012-05-01';
Remplacez [yourTable]
par une table de votre base de données. L'astuce est que le nombre de lignes dans la table que vous sélectionnez doit être> = le nombre de dates que vous voulez retourner. J'ai essayé d'utiliser le paramètre fictif de table DUAL, mais il ne renverrait qu'une seule ligne.
J'avais besoin d'une liste avec tous les mois entre 2 dates pour les statistiques. Les 2 dates sont les dates de début et de fin d'un abonnement. La liste affiche donc tous les mois et le montant des abonnements par mois.
MYSQL
CREATE PROCEDURE `get_amount_subscription_per_month`()
BEGIN
-- Select the ultimate start and enddate from subscribers
select @startdate := min(DATE_FORMAT(a.startdate, "%Y-%m-01")),
@enddate := max(DATE_FORMAT(a.enddate, "%Y-%m-01")) + interval 1 MONTH
from subscription a;
-- Tmp table with all months (dates), you can always format them with DATE_FORMAT)
DROP TABLE IF EXISTS tmp_months;
create temporary table tmp_months (
year_month date,
PRIMARY KEY (year_month)
);
set @tempDate=@startdate; #- interval 1 MONTH;
-- Insert every month in tmp table
WHILE @tempDate <= @enddate DO
insert into tmp_months (year_month) values (@tempDate);
set @tempDate = (date(@tempDate) + interval 1 MONTH);
END WHILE;
-- All months
select year_month from tmp_months;
-- If you want the amount of subscription per month else leave it out
select mnd.year_month, sum(subscription.amount) as subscription_amount
from tmp_months mnd
LEFT JOIN subscription ON mnd.year_month >= DATE_FORMAT(subscription.startdate, "%Y-%m-01") and mnd.year_month <= DATE_FORMAT(subscription.enddate, "%Y-%m-01")
GROUP BY mnd.year_month;
END
J'utiliseServer version: 5.7.11-log MySQL Community Server (GPL)
Maintenant, nous allons résoudre cela d'une manière simple.
J'ai créé une table nommée "datetable"
mysql> describe datetable;
+---------+---------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+---------+---------+------+-----+---------+-------+
| colid | int(11) | NO | PRI | NULL | |
| coldate | date | YES | | NULL | |
+---------+---------+------+-----+---------+-------+
2 rows in set (0.00 sec)
maintenant, nous verrons les enregistrements insérés à l'intérieur.
mysql> select * from datetable;
+-------+------------+
| colid | coldate |
+-------+------------+
| 101 | 2015-01-01 |
| 102 | 2015-05-01 |
| 103 | 2016-01-01 |
+-------+------------+
3 rows in set (0.00 sec)
et voici notre requête pour récupérer des enregistrements dans les deux dates plutôt que ces dates.
mysql> select * from datetable where coldate > '2015-01-01' and coldate < '2016-01-01';
+-------+------------+
| colid | coldate |
+-------+------------+
| 102 | 2015-05-01 |
+-------+------------+
1 row in set (0.00 sec)
espérons que cela aiderait beaucoup de gens.
Solution élégante utilisant la nouvelle fonctionnalité récursive (Common Table Expressions) dans MariaDB> = 10.3 et MySQL> = 8.0.
WITH RECURSIVE t as (
select '2019-01-01' as dt
UNION
SELECT DATE_ADD(t.dt, INTERVAL 1 DAY) FROM t WHERE DATE_ADD(t.dt, INTERVAL 1 DAY) <= '2019-04-30'
)
select * FROM t;
Ce qui précède renvoie un tableau des dates comprises entre '2019-01-01' et '2019-04-30'.
Créez une procédure stockée qui prend deux paramètres a_begin et a_end . Créez une table temporaire appelée t, déclarez une variable d, affectez a_begin à d et exécutez une boucle WHILE
loop INSERT
ing d dans et appelez la fonction ADDDATE
pour incrémenter valeur d. Enfin SELECT * FROM t
.
DELIMITER $$
CREATE PROCEDURE popula_calendario_controle()
BEGIN
DECLARE a INT Default 0;
DECLARE first_day_of_year DATE;
set first_day_of_year = CONCAT(DATE_FORMAT(curdate(),'%Y'),'-01-01');
one_by_one: LOOP
IF dayofweek(adddate(first_day_of_year,a)) <> 1 THEN
INSERT INTO calendario.controle VALUES(null,150,adddate(first_day_of_year,a),adddate(first_day_of_year,a),1);
END IF;
SET a=a+1;
IF a=365 THEN
LEAVE one_by_one;
END IF;
END LOOP one_by_one;
END $$
cette procédure insérera toutes les dates du début de l'année jusqu'à maintenant, il suffit de remplacer les jours du "début" et du "fin", et vous êtes prêt à partir!
Je voudrais utiliser quelque chose semblable à ceci:
DECLARE @DATEFROM AS DATETIME
DECLARE @DATETO AS DATETIME
DECLARE @HOLDER TABLE(DATE DATETIME)
SET @DATEFROM = '2010-08-10'
SET @DATETO = '2010-09-11'
INSERT INTO
@HOLDER
(DATE)
VALUES
(@DATEFROM)
WHILE @DATEFROM < @DATETO
BEGIN
SELECT @DATEFROM = DATEADD(D, 1, @DATEFROM)
INSERT
INTO
@HOLDER
(DATE)
VALUES
(@DATEFROM)
END
SELECT
DATE
FROM
@HOLDER
Ensuite, la table @HOLDER
Variable contient toutes les dates incrémentées par jour entre ces deux dates, prêtes à être jointes à votre convenance.