Forum Programmation.autre SQL toujours : requête sur une non-table....

Posté par  .
Étiquettes : aucune
0
24
août
2004
Salut tout le monde !
Voici mon problème du jour : comment puis-je procéder pour obtenir le nombre de jours ouvrés situés entre deux dates (incluse) ?
Bien sûr, j'aurais aimé éviter de me créer une table juste pour ça....
J'ai donc le select, le where, mais il me manque le from.
En gros, ça ressemblerait à ça :
select count(*)
from ????
where day between dateDeb and dateFin
and to_char(day, 'D')::int4 !=7 and to_char (day, 'D')::int4 != 1;

Sachant que la fonction to_char (date, 'D') retourne 1 pour dimanche, 7 pour samedi.
J'ai essayé plusieurs choses, et rien ne fonctionne. Pourtant, il me semblait bien que le SQL était fait pour traiter des ensembles....
Alors, avis aux gourous : j'ai bien pensé à faire une fonction en plPgSql (hé oui, je bosse sous postgreSQL), mais si c'est pour faire un truc comme ce qui suit, je trouve ça un peu dommage par rapport à la puissance du sql....

create function nbJoursOuvrables (date, date) returns int4 as '
declare
dat_deb alias for $1;
dat_fin alias for $2;
x date;
nbJoursOuvres int4;
begin
nbJoursOuvres := 0;
x := dat_deb;
while (x <= dat_fin) loop
if ((to_char(x, ''D'') != 7) AND (to_char (x, ''D'') != 1)) then
nbJoursOuvres := nbJoursOuvres + 1;
end if;
x := x + 1;
end loop;
return nbJoursOuvres;
end;
' language 'plPgsql';

... et je me demande si vraiment ce sera plus rapide qu'une requête sur un ensemble ? Alors, à votre bon coeur, car j'ai vraiment besoin, et de manière assez intensive, donc, toute solution meilleure sera la bienvenue.
  • # une solution....

    Posté par  . Évalué à 2.

    Avec certains serveurs SQL, Oracle notamment (ce que j'utilise au boulot), il existe une table bidon qui s'appelle "dual" qui est utilisée précisément dans ton cas.

    Comme est exemple est toujours plus clair:

    SQL> select sysdate from dual;

    SYSDATE
    ---------
    24-AUG-04

    SQL>
    • [^] # Re: une solution....

      Posté par  . Évalué à 1.

      Merci...
      J'ai vainement cherché la doc de postgreSQL... pas de table virtuelle ou de mot clef permettant de faire ce que je veux. Jusqu'à preuve du contraire, je vais utiliser la fonction que je donne ci-dessus dans un select !
      Bref, c'est pas tip-top mais pour l'instant je n'ai pas mieux.
  • # peut-être une piste

    Posté par  . Évalué à 3.

    Tout d'abord, ta définition de jour ouvrable semble exclure les jours fériés ... mais passons, c'est pas le sujet.

    Ensuite, obtenir le résultat sans passer par une fonction (juste une requête donc) me paraît un peu illusoire (mais je ne maîtrise pas postgres à fond non plus !).

    Cependant, je me permets de critiquer ta fonction du fait qu'elle est très coûteuse en temps avec des intervalles longs, si en plus on l'applique à de nombreux enregistrements ...

    Ma proposition est donc un truc du style suivant (j'ai laissé les raise notice de débogage à tout hazard) ; ce qui donne (ça va faire mal !) :


    CREATE FUNCTION nbJoursOuvrables(DATE, DATE) RETURNS INTEGER AS'
    DECLARE
    debut ALIAS FOR $1;
    fin ALIAS FOR $2;
    debut_d DATE;
    fin_d DATE;
    debut_j INTEGER;
    fin_j INTEGER;
    periode INTEGER;
    nb INTEGER;
    BEGIN
    debut_j := to_char( debut , ''D'' )::INTEGER;
    IF debut_j <> 2 THEN
    debut_d := date_mii( debut, 7-debut_j+3 ); -- lundi prédécent
    ELSE
    debut_d := debut; -- lundi courant
    END IF;
    RAISE NOTICE ''debut_j = %, debut_d = %'', debut_j, debut_d;

    fin_j := to_char( fin, ''D'')::INTEGER;
    IF fin_j <> 1 THEN
    fin_d := date_mii( fin, fin_j-7-1 );
    ELSE
    fin_d := fin;
    END IF;

    RAISE NOTICE ''fin_j = %, fin_d = %'', fin_j, fin_d;
    periode = date_mi( fin_d, debut_d )+1;
    RAISE NOTICE ''periode = %'', periode;
    nb := periode - periode / 7 * 2; -- nb jours sans sam/dim
    RAISE NOTICE ''periode - s/m = %'', nb;
    IF date_mi( fin_d, debut_d )>7 THEN
    IF fin_j <> 7 THEN
    nb := nb - (debut_j-1) -(7-fin_j-2 );
    ELSE
    nb := nb - (debut_j-1) -(7-fin_j-1);
    END IF;
    ELSE
    nb := nb - debut_j + 2;
    END IF;
    RETURN nb;
    END;'
    LANGUAGE 'plpgsql';


    Le principe de la fonction consiste à repérer les dates limites (lundi, dimanche usuels) englobant au mieux les dates données. On obtient donc un nombre de semaines entières qu'il suffit de diviser par 7 et multiplier par 2 pour obtenir le nombre de samedis/dimaches de cette période. Enfin, il faut ajuster suivant la position du dernier jour i.e un samedi déjà compté ou non.

    La fonction testée sommairement à l'air vachement balèse mais c'est beaucoup plus léger que de boucler sur les jours à mon avis surtout si les dates sont très espacées ...

    A++

    P.S : C'est pas toi qui recherchait comment déterminer si deux périodes se chevauchent ? ... je viens de remarquer que PostgreSQL a justement une fonction overlaps(date, date, date, date) ... à bon codeur ...
    • [^] # Re: peut-être une piste

      Posté par  . Évalué à 1.

      Ouahou !
      Je ne pensais pas trouver des choses aussi balèzes sur ce site !
      100 000 mercis !

      Pour ta fonction, je vais l'étudier avant de la mettre en prod, mais ça m'a l'air plus que bien, en effet.
      La période la plus longue sera du 01/09 de l'année en cours au 31/08 de l'année suivante (année scolaire, donc), mais, car il y a toujours un mais, il y aura plusieurs accès concurentiels aux heures de pointes (une 100aine d'utilisateurs aux heures d'arrivée et de départ, c'est pour une application de pointage).

      Pour la fonction overlaps, ça va m'éviter de charger ma base avec des trucs inutiles.
      J'avais codé ça :
      CREATE FUNCTION areExclusivePeriods(DATE, DATE, DATE, DATE)
      RETURNS boolean AS'
      DECLARE
      debut1 ALIAS FOR $1;
      fin1 ALIAS FOR $2;
      debut2 ALIAS FOR $3;
      fin2 ALIAS FOR $4;
      retval boolean;
      BEGIN
      IF (((debut2 >= fin1) AND (fin2 > debut2)) or ((debut1 >= fin2) and (debut1 > fin1)))
      then
      retval := false;
      else
      retval := true;
      end if;
      return retval;
      end;
      ' LANGUAGE 'plpgsql';

      qui visiblement coûte autant de temps que la fonction overlaps...

      En fait, je suis confronté à d'autres soucis depuis ce matin. Dans la table en question, il s'agit d'enregistrer un certain nombre d'informations par périodes.
      Par défaut, chaque utilisateur a un enregistrement qui court du 01/09/200(n) au 31/08/200(n+1). Il peut sectionner cette période lors de l'ajout d'une nouvelle ligne, ça, j'ai réussi à le faire avec un trigger après insert. Mais je peux aussi avoir la solution inverse : l'aggrégat de périodes, fait after update.... Et ça, ben quand je le met en place, ça fout tout en l'air....
      Je suis en train de me prendre la tête là dessus, et pour l'instant je n'ai pas de solution. Un petit exemple sera peut-être plus parlant....

      Au début : période allant du 01/09 au 31/08 paramétré à 100 par défaut.
      L'utilisateur indique un paramètre différent de celui par défaut, mettons 90 pour le mois de janvier.
      Le after insert effectue dans l'ordre les opérations suivantes :
      1) rétrécicessement de la période initiale du 01/09 au 31/12 avec pour paramètre 100,
      2) insertion de la période allant du 01/02 au 31/08 avec pour paramètre 100,
      3) renvoi des valeurs saisies par l'utilisateur (sa demande d'insertion) allant du 01/01 au 31/01 avec pour paramètre 90.

      On peut répéter l'opération autant qu'on veut.

      Par contre, si l'utilisateur veut modifier la période courant du 01/01 au 31/01 et en passer le paramètre à 100, il est inutile de conserver les 3 périodes.
      Je voudrais donc trouver le moyen de mettre à jour la période
      - avant
      - après
      - ou les 2 (ben oui, ce sont les 3 cas possibles) !
      de façon à ne garder qu'une seule période : celle qui couvre l'intégralité des valeurs pour lesquelles le paramètre passe à 100... Et là, ça pêche....
      Je cherche, je cherche, mais pour l'instant, je ne trouve pas....
      Alors si l'un des mentors d'ici a une idée révolutionnaire, ou simplement un modèle qui répondrait plus à mes besoins, ce serait parfait !
      En tout cas, je poursuis mes recherches en attendant.
      Merci encore pour ton aide précieuse !
      • [^] # Re: peut-être une piste (autre)

        Posté par  . Évalué à 1.

        Bon, c'est pas encore clair dans ma tête, mais ça pourraît donner matière à réfléchir.
        je suppose que ta table, que je nomme période, comporte les colonnes id, userid, debut, fin et param.

        Déterminer si des périodes se suivent est assez simple : il suffit de faire un produit ensembliste restrictif portant sur userdid et param.
        D'où naturellement la requête suivante :

        SELECT p1.id, p1.fin, p2.debut, date_mi( p1.fin, p2.debut)+1 AS intervalle
        FROM periode AS p1, periode AS p2
        WHERE p1.userid=1 AND p1.param=p2.param AND p1.fin<p2.debut
        ORDER BY intervalle DESC;

        N.B1 : la restriction p1.fin<p2.debut permet de ne pas sélectionner tous les résultats possibles (pas intéressants).
        N.B2 : les intervalles qui se suivent directement sont repérables par le 0 pour la colonne intervalle dans la requête ci-dessus.
        N.B3 : avec la jointure sur un index (supposé) de id et malgré le produit ensembliste, cela ne devrait pas être trop couteux en performances ; je doute que chaque utilisateur en arrive à 365 entrées par exemple !

        A priori, il ne resterait donc plus qu'à incorporer cette requête dans un trigger pour que ça fasse l'affaire !

        Bon courage. Si j'ai dispersé inutilement tes efforts, désolé !
        • [^] # Re: peut-être une piste (autre)

          Posté par  . Évalué à 1.

          Bon courage. Si j'ai dispersé inutilement tes efforts, désolé !

          Non mais tu rigoles, là, j'espère !
          Je pose un problème, et tu y réponds plus que je n'aurais espéré, même partiellement, et en plus il faudrait que je ne sois pas content ?
          Manquerai plus que ça.
          Je ne peux pas tester tout ça à cette heure, car je suis à la maison, et n'ai pas accès à ma machine du travail, mais je m'en vais regarder tout ça de plus près.
          Pour ce qui est des triggers, je suis un peu couillon quand je m'y mets, aussi, faut dire !
          En repartant du taf' tout à l'heure, j'ai eu la brillante (?) idée de tout placer non pas dans un trigger, mais dans une fonction d'ajout que j'appellerai exclusivement pour insérer des données. Je procéderai de même pour la modification/suppression, car là, il faut bien le dire, ce sera plus propre, et certainement plus performant !
          J'avais la (modeste, précisons-le) manie de dire que je m'y "connaissait" en bdd, et en particulier en postgres, mais là, je dois dire que non seulement tu m'en apprends beaucoup, mais en plus, tu fais fleurir dans ma tête de linotte pleins d'idées à faire murir tranquillement.
          Tu ne donnerais pas des cours par hasard ;-) ?
          En tout cas, 1000 mercis une fois de plus !
          • [^] # Re: peut-être une piste (autre)

            Posté par  . Évalué à 1.

            Si j'ai pu t'aider, c'est bien ; dans le cas contraire, il n'est pus rien que je puisse faire !

            Cela dit, je ne prétens aucunement maîtriser postgres et au cours de ces sporadiques échanges, tu dois maintenant en connaître à peu près autant que moi ... donc pour les cours, il faudrait d'abord que je me fasse une greffe de cerveau et que ça prenne évidemment !!

Suivre le flux des commentaires

Note : les commentaires appartiennent à celles et ceux qui les ont postés. Nous n’en sommes pas responsables.