Interpolation sur valeurs de retour

Interpolation sur valeurs de retour - SQL/NoSQL - Programmation

Marsh Posté le 23-11-2011 à 14:41:59    

Bonjour,
 
Je travaille actuellement sur Oracle 11g voici le contexte de mon problème:
J'ai une table production_data qui contient plusieurs centaines de milliers de valeurs de production. Les valeurs ont :
- un id (PK, int)
- un process_id (FK, int)
- une valeur (int)
- un timestamp (Timestamp)
 
Le resultat de select count(*) from production_data group by process_id montre que la répartition en fonction du process_id n'est pas égale (ce qui est normal).
 
Le problème:
Au vu des opérations que je fais après dans ma couche métier, je dois sortir de la base une matrice ayant autant de colonne que de process_id, et autant de lignes que de valeurs. Vu que tous les process_id n'ont pas le même nombre de valeurs, il faut que j'interpole les autres (par interpolation linéaire par exemple).
 
Est-ce que vous voyez une manière simple (et surtout performante) de faire ça? En ce moment je le fais à grands coups de moulinette mais je doute que ce soit très efficace et j'ai peur du scale-up ... J'ai vu que je pourrais peut-être utiliser des fonctions comme percentile_cont(X), en moulinant sur X, mais là aussi je suis sceptique..
 
Merci d'avance!


---------------
Si la vérité est découverte par quelqu'un d'autre,elle perd toujours un peu d'attrait
Reply

Marsh Posté le 23-11-2011 à 14:41:59   

Reply

Marsh Posté le 23-11-2011 à 15:38:41    

Tu veux dire que, par ex, un processus P1 a comme valeurs 1, 2, 3, 4, 5 et qu'un autre processus P2 n'a que 1, 2, 4 et donc, faut compléter pour ce processus en rajoutant les valeurs 3 et 5?


---------------
Astres, outil de help-desk GPL : http://sourceforge.net/projects/astres, ICARE, gestion de conf : http://sourceforge.net/projects/icare, Outil Planeta Calandreta : https://framalibre.org/content/planeta-calandreta
Reply

Marsh Posté le 23-11-2011 à 16:07:11    

En fait l'interpolation doit être fait sur le couple valeur-timestamp.
 
Donc ça serait plutôt :
 
P1 (valeur; timestamp): (1;2000), (2;2001), (3;2005), (4;2010)
P2 (valeur; timestamp): (20;1999), (40;2003)
 
Ce que je dois avoir c'est 2 "Hash" de même longueur (4), dont le premier élément se réfère au 1er timestamp (1999) et le dernier, au dernier (2010).
 
On doit donc interpoler linéairement et reconstruire les valeurs "qui vont bien".
 
Par la suite, il faudra que je puisse spécifier le nombre de valeurs du vecteur (ce qui me force le "timestep" ), le timestep de début et de fin.  
 
Je suis condamné à tout sortir de la base et mouliner dessus en Java dans mon business ou bien il y a moins lourd?


---------------
Si la vérité est découverte par quelqu'un d'autre,elle perd toujours un peu d'attrait
Reply

Marsh Posté le 23-11-2011 à 16:19:55    

Si j'ai bien compris, tu voudrais en sortie, pour chaque processus, une liste de timestamp qui va du plus faible au plus élevé dans la bd, et pour chaque timestamp, une valeur correspondante ou 0 dans le cas où y'a pas de timestamp pour un processus. Ca donnerait :
P1 : (0;1999), (1;2000), (2;2001), (0;2002), (0;2003), (0;2004), (3;2005), (0;2006), (0;2007), (0;2008), (0;2009), (4;2010)
 
P2 : (20;1999), (0;2000), (0;2001), (0;2002), (40;2003), (0;2004), (0;2005), (0;2006), (0;2007), (0;2008), (0;2009), (0;2010)
 
J'ai bon?
 
Si c'est ça, j'ai eu un pb similaire à gérer dans mon soft de help-desk Astres (cf ma signature) pour produire des stats en histogramme empilé. Je devais générer, sur une période et à une certaine fréquence, la liste des dates sur lesquelles consolider les stats.  
Ex : lister les tickets de type T1 et T2 reçus par mois, sur 1 an entre le 01/01/2011 et le 31/12/2011.
Il fallait générer la liste des 12 mois et trouver le nb de tickets reçus pour chaque type.
Ce que je faisais en php, c'est d'abord de générer les mois compris entre mes 2 dates sous la forme "yyyy-mm", ces valeurs étant des clés dans un tableau associatif.

Code :
  1. Ex : $MaStat = array(
  2.                              "2011-01" => 0,
  3.                              "2011-02" => 0,
  4.                              ...
  5.                            );


Ensuite, je faisais une requête SQL qui listait mes tickets d'un type donné et dans le select, je mettais le timestamp de réception de chaque ticket sous la forme "yyyy-mm". Ensuite, j'avais plus qu'à faire +1 dans ma variable $MaStat sur la bonne "clé" ;)


Message édité par rufo le 23-11-2011 à 16:32:41

---------------
Astres, outil de help-desk GPL : http://sourceforge.net/projects/astres, ICARE, gestion de conf : http://sourceforge.net/projects/icare, Outil Planeta Calandreta : https://framalibre.org/content/planeta-calandreta
Reply

Marsh Posté le 23-11-2011 à 16:26:24    

Presque (désolé, j'ai un peu de mal aujourd'hui :) ).
 
P1 : (0;1999), (1;2000), (2;2001), (X;2002), (X;2003), (X;2004), (3;2005), (Y;2006), (Y;2007), (Y;2008), (Y;2009), (4;2010)  
 
X doit être interpolé entre 2 et 3. Suivant la méthode d'interpolation (à voir laquelle on utilise. Pour le moment n'importe laquelle fera l'affaire), la valeur sera différente (2, 3 ou alors une progression linéaire entre 2 et 3). Idem pour Y
 
P2 subi ensuite le même traitement.  
 
C'est plus clair maintenant?  :??:


---------------
Si la vérité est découverte par quelqu'un d'autre,elle perd toujours un peu d'attrait
Reply

Marsh Posté le 23-11-2011 à 16:33:56    

Ah oui, d'accord. Là, c'est chaud ton truc :/ Franchement, je doute que tu y arrive en SQL pure...


---------------
Astres, outil de help-desk GPL : http://sourceforge.net/projects/astres, ICARE, gestion de conf : http://sourceforge.net/projects/icare, Outil Planeta Calandreta : https://framalibre.org/content/planeta-calandreta
Reply

Marsh Posté le 23-11-2011 à 16:35:34    

Au départ j'étais quasiment sûr que c'était impossible mais après avoir trouvé les fonctions d'analyse j'ai eu une lueur d'espoir ... Et je pense que si j'arrive à le faire dans la base ce sera certainement plus rapide que de mouliner dessus en Java :bounce:


---------------
Si la vérité est découverte par quelqu'un d'autre,elle perd toujours un peu d'attrait
Reply

Marsh Posté le 23-11-2011 à 22:01:34    

Salut
 
Faire en une seule requete je suis pas sur que ca soit faisable à première vue, mais d'un autre coté le problème en lui-meme n'a pas l'air bien compliqué.
 
C'est quoi que t'appelles "à grands coups de moulinette" dans ton premier post? Tu peux préciser ce qui va poser problème quand tu vas scaler? Une table avec des centaines de milliers de lignes c'est pas la mort a priori, si t'as les indexs comme il faut sur tes colonnes, obtenir tous les count() ne devrait pas poser de problèmes.
 
Si j'ai bien compris ton exemple, ta matrice avec p1/p2 avant extrapolation devrait ressembler à

   1999 2000 2001 2002 2003 2004 2005 2006 2007 2008 2009 2010
P1 0    1    2    0    0    0    3    0    0    0    0    4  
P2 20   0    0    0    40   0    0    0    0    0    0    0

Une fois que t'as ca, l'extrapolation c'est juste un simple algo non? Je pense qu'il me manque un truc pour bien cerner ton problème. Ou alors t'as des problèmes de performance avec les counts?


---------------
C'était vraiment très intéressant.
Reply

Marsh Posté le 23-11-2011 à 22:13:00    

Salut,
 
Tu as absolument tout compris. Mes index et count sont en ordre, donc il ne devraient pas trop poser de problèmes de perf. Par contre :
- comment obtenir cette matrice de la manière plus performante ?
- si j'ai bien compris, l'interpolation sur les valeurs, je vais devoir la faire de toutes façons dans le layer metier, juste? Il n'y a rien dans oracle qui puisse le faire pour moi avec de bonnes perf?


---------------
Si la vérité est découverte par quelqu'un d'autre,elle perd toujours un peu d'attrait
Reply

Marsh Posté le 24-11-2011 à 06:18:57    

Mh ok, bon je pense qu'il n'y a pas de fonction Oracle prédéfinie dans ton cas.
 
Pour la matrice avant interpolation, je pense pas que tu puisses échapper aux counts sauf si tu crées une table qui tient à jour chaque count pour les paires ID/valeur. Tu l'initialises avec les counts existants lors de sa création, puis tu la maintiens avec des triggers sur la table production_data (à chaque fois que tu insères/modifies/efface une ligne, pouf tu upates le count correspondant). Du coup au lieu de devoir faire les counts à chaque fois, tu peux directement accéder à la valeur dans ta table. Pas sur que ca gagne beaucoup sauf si t'as une quantité énorme de ID/valeurs possibles, ou ptetre aussi si tu passes ton temps à vouloir regénérer ta matrice.
 
Pour l'interpolation, je pense pas que t'y échappes, donc ouai layer métier. Ou tu peux faire ca en PL/SQL aussi (m'enfin bon on pourra toujours considérer ca comme métier, pis c'est surement moins chiant à coder en Java).
 
J'ai dans l'idée que ca serait meme probablement faisaible avec une poignée de requetes SQL qui généreraient une table "réplique" de la matrice, interpolation comprise, mais ca sent le truc fait à la truelle et ca m'étonnerait que ca soit plus performant que de le faire en Java. Mais bon maintenant que j'ai l'idée en tete, j'tenterai demain je pense, mais attends toi à du bon gras qui tache.


---------------
C'était vraiment très intéressant.
Reply

Marsh Posté le 24-11-2011 à 06:18:57   

Reply

Marsh Posté le 24-11-2011 à 19:50:20    

Volontiers :)


---------------
Si la vérité est découverte par quelqu'un d'autre,elle perd toujours un peu d'attrait
Reply

Marsh Posté le 25-11-2011 à 04:11:35    

Ah merde j'avais commencé puis c'était encore plus horrible que prévu alors j'allais laisser tomber, mais si tu insistes...
 
Tiens, voilà du boudin!  
Le fait que le nombre de valeurs soit dynamique empeche de tout faire en SQL simple et force le passage à PL/SQL (pour pouvoir utiliser execute_immediate et consorts).
En plus de ca j'ai tapé dans la limite de longueur de chaine de caractère générable par une concaténation de chaine donc y a fallu contourner, comme si c'était pas assez le bordel.
 
J'ai la flemme de commenter précisément tellement c'est tiré par les cheveux. Si tu comprends pas les requetes c'est normal! Le pire c'est que ca marche!!

Code :
  1. -- Table qui va contenir les counts
  2. create table prod_data_count(process_id number, valeur number, count number);
  3. -- Ci-dessous la facon de remplir cette table à partir des données de production_data (pour toi)
  4. /*
  5. DECLARE
  6.   CURSOR c_init_prod_data_count IS select 'insert into prod_data_count(process_id, valeur, count) values('||process_id||','||valeur||','||count(*)||')' as insert_statement from production_data group by process_id, valeur;
  7. BEGIN
  8.   FOR r_init_prod_data_count IN c_init_prod_data_count LOOP
  9.     BEGIN
  10.       EXECUTE IMMEDIATE r_init_prod_data_count.insert_statement;
  11.     EXCEPTION
  12.       WHEN OTHERS THEN NULL;
  13.     END;
  14.   END LOOP;
  15.   COMMIT;
  16. END;
  17. /
  18. */
  19. -- N'ayant pas la table production_data, j'ai inséré des valeurs manuellement pour reproduire ton exemple avec P1/P2
  20. insert into prod_data_count(process_id, valeur, count) values(1,2000,1);
  21. insert into prod_data_count(process_id, valeur, count) values(1,2001,2);
  22. insert into prod_data_count(process_id, valeur, count) values(1,2005,3);
  23. insert into prod_data_count(process_id, valeur, count) values(1,2010,4);
  24. insert into prod_data_count(process_id, valeur, count) values(2,1999,20);
  25. insert into prod_data_count(process_id, valeur, count) values(2,2003,40);
  26. commit;
  27. -- Maintenant au boulot!
  28. DECLARE
  29.   CURSOR c_create_matrix IS select 'create table matrice_avant_extrapolation(process_id number, v_'||REGEXP_REPLACE(wm_concat(valeur), ',', ' number, v_')||' number)' as create_statement FROM (select (select min(valeur)-1 from prod_data_count) + rownum as valeur from all_objects where rownum<=(select max(valeur)-min(valeur)+1 from prod_data_count) order by 1);
  30.   CURSOR c_insert_processes IS select 'insert into matrice_avant_extrapolation(process_id) values('||process_id||')' as insert_statement from (select distinct(process_id) from prod_data_count);
  31.   CURSOR c_update_counts IS select 'update matrice_avant_extrapolation set v_'||valeur||' = '||count||' where process_id = '||process_id as update_statement from prod_data_count;
  32.   CURSOR c_extrapolation IS select ', case when v_'||valeur||' is not null then v_'||valeur||' else (select pdt.count from prod_data_count pdt where pdt.process_id=mat.process_id and pdt.valeur=(select max(pdt2.valeur) from prod_data_count pdt2 where pdt2.process_id=mat.process_id and pdt2.valeur<'||valeur||'))+(select '||valeur||'-max(pdt3.valeur) from prod_data_count pdt3 where pdt3.process_id=mat.process_id and pdt3.valeur<'||valeur||')*((select pdt4.count from prod_data_count pdt4 where pdt4.process_id=mat.process_id and pdt4.valeur=(select min(pdt5.valeur) from prod_data_count pdt5 where pdt5.process_id=mat.process_id and pdt5.valeur>'||valeur||'))-(select pdt6.count from prod_data_count pdt6 where pdt6.process_id=mat.process_id and pdt6.valeur=(select max(pdt7.valeur) from prod_data_count pdt7 where pdt7.process_id=mat.process_id and pdt7.valeur<'||valeur||')))/((select min(pdt8.valeur) from prod_data_count pdt8 where pdt8.process_id=mat.process_id and pdt8.valeur>'||valeur||')-(select max(pdt9.valeur) from prod_data_count pdt9 where pdt9.process_id=mat.process_id and pdt9.valeur<'||valeur||')) end as v_'||valeur as statement from (select (select min(valeur)-1 from prod_data_count) + rownum as valeur from all_objects where rownum<=(select max(valeur)-min(valeur)+1 from prod_data_count) order by 1);
  33.   v_create_statement VARCHAR2(32767);
  34.   v_select_statement VARCHAR2(32767);
  35.   t_statement DBMS_SQL.varchar2a;
  36.   v_statement_index number;
  37.   v_cur number;
  38.   v_dummy   number;
  39. BEGIN
  40.   -- Partie 1: on crée la matrice avant extrapolation (juste la table)
  41.   select 'create table matrice_avant_extrapolation(process_id number, v_'||REGEXP_REPLACE(wm_concat(valeur), ',', ' number, v_')||' number)' into v_create_statement FROM (select (select min(valeur)-1 from prod_data_count) + rownum as valeur from all_objects where rownum<=(select max(valeur)-min(valeur)+1 from prod_data_count) order by 1);
  42.   execute immediate v_create_statement;
  43.   -- Partie 2: on insère une ligne par processus_id connu
  44.   FOR r_insert_processes IN c_insert_processes LOOP
  45.     BEGIN
  46.       EXECUTE IMMEDIATE r_insert_processes.insert_statement;
  47.     EXCEPTION
  48.       WHEN OTHERS THEN NULL;
  49.     END;
  50.   END LOOP;
  51.  
  52.   -- Partie 3: on insère les counts connus dans la table
  53.   FOR r_update_counts IN c_update_counts LOOP
  54.     BEGIN
  55.       EXECUTE IMMEDIATE r_update_counts.update_statement;
  56.     EXCEPTION
  57.       WHEN OTHERS THEN NULL;
  58.     END;
  59.   END LOOP;
  60.   -- Partie 4: ...? (extrapolation des valeurs)
  61.   t_statement(1) := 'create table matrice_apres_extrapolation as select process_id ';
  62.   v_statement_index := 2;
  63.   FOR r_extrapolation IN c_extrapolation LOOP
  64.     t_statement(v_statement_index) := r_extrapolation.statement;
  65.     v_statement_index := v_statement_index + 1;
  66.   END LOOP;
  67.   t_statement(v_statement_index) := ' from matrice_avant_extrapolation mat';
  68.   v_cur:= DBMS_SQL.open_cursor;
  69.   DBMS_SQL.PARSE(v_cur, t_statement, 1, v_statement_index, false ,dbms_sql.native);
  70.   v_dummy :=  DBMS_SQL.EXECUTE (v_cur);
  71.   DBMS_SQL.close_cursor (v_cur);
  72.  
  73.   -- normalement le commit est déjà fait... dans le doute, on sait jamais.
  74.   commit;
  75.  
  76. END;
  77. /
  78. -- Partie 5: profit!
  79. select * from matrice_apres_extrapolation;

PROCESS_ID V_1999 V_2000 V_2001 V_2002 V_2003 V_2004 V_2005 V_2006 V_2007 V_2008 V_2009 V_2010                  
         1             1      2   2.25   2.5    2.75      3    3.2    3.4    3.6    3.8      4                      
         2     20     25     30     35    40                                                                                                                                                                                      
 
2 rows selected

Bon pour l'instant ca ne calcule pas si la borne inférieure ou supérieure n'est pas connue (ex pour 1999 pour P1 et au-delà de 2003 pour P2). Si tu veux y mettre des valeurs arbitraires (ex: 0 pour borne inf, plus haute valeur atteinte pour borne sup), ca te rajoute une étape. Si tu ne veux pas que le 0 influes sur l'extrapolation tu rajoutes ca dans la matrice extrapolée, sinon tu mets ca dans la matrice non extrapolée avant l'extrapolation.


---------------
C'était vraiment très intéressant.
Reply

Marsh Posté le 27-11-2011 à 07:13:55    

oua.... c'est hallucinant :D
Je vais essayer de comprendre comment ca marche :D. par contre je me demande si c'est plus rapide que en java... à voir..
 
En tous cas merci pour ton effort !! :o


---------------
Si la vérité est découverte par quelqu'un d'autre,elle perd toujours un peu d'attrait
Reply

Marsh Posté le 28-11-2011 à 00:18:13    

Pas de pb, j'ai appris un truc en le faisant donc c'est meme pas perdu pour moi!
Merde en relisant je m'apercois que j'ai laissé des variables qui servent à rien, ca va pas aider pour la compréhension.
Par contre non, ya aucune garantie que ca soit plus rapide qu'en Java.


---------------
C'était vraiment très intéressant.
Reply

Marsh Posté le 28-11-2011 à 08:54:26    

Théoriquement une DB c'est fait pour sortir des données et c'est a l'appli de formater le tout.
En réalité il faut de temps en temps faire un mix pour gagner du temps mais dans ce cas ci ce serai mieux de "boucher les trous" dans l'appli, ce sera plus clair, plus flexible et plus facil a maintenir.
 
En fait le probleme c'est pas vraiment l'interpolation mais le nombre variable de colonne, c'est relativement compliqué de sortir le résultat tout cuit du sgbd alors que c'est facil de le sortir sous forme clé/valeur et de reformater le tout dans l'appli a l'affichage.

Reply

Sujets relatifs:

Leave a Replay

Make sure you enter the(*)required information where indicate.HTML code is not allowed