Overblog
Suivre ce blog
Administration Créer mon blog
15 janvier 2014 3 15 /01 /janvier /2014 11:21
Nouvelle année, nouvelles résolutions.
La première : coder un échiquier pour jouer sous SQL*Plus.

J'ai posté le script complet ici : 

1) Le modèle de données

Le modèle de données est simple : c'est la liste des coups joués, ni plus ni moins.

CREATE TABLE cs_mouv (
    x number, 
    y number,
    x_dest number, 
    y_dest number,
    n_mouv number, 
    piece char(1),
    col char(1), 
    move_str varchar2(10),
    fic char(1) default 'N'
);

Pour cela : 
- on enregistre la case de départ (x, y), la case d'arrivée (x_dest, y_dest), la pièce et sa couleur
- le numéro du coup (n_mouv)
- Le flag "fic", comme fictif, permet de décomposer les coups spéciaux en plusieurs mouvements.
Comme le roque, la prise en passant, la promotion...
- Le "move_str" ne sert qu'à conserver la châne saisie par l'utilisateur, pour des raisons pratiques.

2) Reconstituer la position à partir de la liste des coups

Quand on a la liste des coups, et si on n'est pas complètement stupide, on peut en déduire la position.

CREATE OR REPLACE VIEW pieces AS
SELECT x_dest x, y_dest y, max(piece) keep(dense_rank last order by n_mouv) piece
    , max(col) keep(dense_rank last order by n_mouv) col 
FROM (
    SELECT x_dest, y_dest, n_mouv
         , piece 
         , col
    FROM cs_mouv
    WHERE x_dest IS NOT NULL
    UNION ALL
    SELECT x, y, n_mouv
         , null 
         , null
    FROM cs_mouv
    )
GROUP BY x_dest, y_dest
/

Il s'agit là de se baser sur les cases de l'échiquier, et de chercher quelle est la pièce qui s'y situe actuellement.
La première partie de l'UNION ALL liste les pièces arrivant sur une case donnée, la deuxième partie les cases abandonnées... on retient dans tout ça la pièces correspondant au plus récent numéro de coup pour chaque case.

3) Afficher la position

La procédure pkg_csboard.display affiche la position actuelle. La vue "pieces" donnant déjà toutes les positions, il s'agit juste de metre en forme le résultat.
Le paramètre en entrée permet d'orienter l'affichage (noirs ou blancs) par l'inversion du système de coordonnées.

A noter que par défaut, SQL*Plus justifie les "leading spaces" (les premiers espaces à gauche), ce qui fout en l'air les efforts de mise en forme.
Pour empêcher cela, il faut entrer l'option :

SET SERVEROUTPUT ON FORMAT WRAPPED

4) Initialiser l'échiquier

La procécdure pkg_csboard.init supprime la liste des coups, puis insère toutes les pièces dans leur position initiale.


cs1-copie-1.jpg
 

5) Jouer des coups

La procédure cs_move prend en paramètre la chaîne de caractères représentant la position initiale et la destination de la pièce à déplacer, contrôle la validité du coup, l'exécute, puis affiche la position : 

SQL> exec pkg_csboard.csmove('e2e4');

cs1-copie-2.jpg

La sous-procédure parse_move vérifie la validité du coup, et retourne le type de coup effectué (erreur, coup normal, petit roque, ...). J'ai pas eu la motivation d'implémenter la prise en passant et la promotion, mais ça doit pas être bien compliqué.
La sous-procédure exec_move est appelée ensuite pour effectuer l'insertion effective.

SQL> exec pkg_csboard.csmove('oo');

cs1-copie-4.jpg
6) Afficher la liste des coups.

C'est la procédure display_list. J'aurais pu m'amuser à mettre des tirets, mais j'avais la flemme. 

SQL> exec pkg_csboard.display_list;
  1. e2e4    e7e5
  2. g1f3    b8c6
  3. f1c4    g8f6
  4. oo
  
7) Annuler des coups

Comme on a la liste des coups, ce n'est pas bien compliqué non plus de revenir en arrière...
La procédure cancel_move supprime le nombre de coups passés en paramètres, en rassemblant au préalable les coups fictifs avec leur coup réel associé.

SQL> exec pkg_csboard.cancel_move(2);

cs1-copie-5.jpg

Voilà, il resterait encore plein de choses à faire :
- implémenter les coups spéciaux manquants
- vérifier la validité des coups
- insérer le concept de partie
- utiliser si possibles des queues pour que deux personnes puissent jouer "sur le réseau"
- encapsuler ça dans du shell pour pouvoir faire des boucles
- ...

Mais je préfère faire les choses à moitié, c'est mieux.
Published by Pacman - dans SQL cosmétique
commenter cet article
4 décembre 2013 3 04 /12 /décembre /2013 16:49

Ma grand-mère me disait toujours, "Pacman, n'oublie jamais le côté artistique du SQL, ni le côté obscur de la force".

Et là, force est de constater que depuis un moment, je m'y crois trop : j'ai oublié les valeurs essentielles dans lesquelles mes ancêtres et moi croyons, et dans lesquelles ma propre descendance devra croire : l'absurdité, l'inutilité, la créativité non applicable à la vraie vie.

 

On va donc aborder aujourd'hui : la division relationnelle avec l'opérateur BITAND !

 

Pour un vrai article sérieux voire boursoufflé sur la division relationnelle, cherchez sur le net.

Par exemple, l'article de SQLPro (vous savez, le mec qui, quand vous lui posez une question sur le forum developpez.net, commence par vous insulter, puis vous recommande d'acheter ses bouquins...), dont nous allons d'ailleurs reprendre le script de tests : 

http://sqlpro.developpez.com/cours/divrelationnelle/

 

1) La division relationnelle

 

Il s'agit de repérer dans une table un ensemble de ligne. 

C'est comme faire un filtre where, mais sur tout un groupe dont les lignes doivent matcher, partiellement ou complètement, un certain nombre de valeurs.

Ces valeurs cibles étant par exemple répertoriées dans une autre table.

 

2) Jeu de données

 

Comme dit, c'est repompé de chez SQLPro. Il s'agit de dépôts (identifiés par leur ville) qui servent des rayons.

Lesquels sont capables de déservir tous les rayons ?

 

/* table des rayons */                                                          

CREATE TABLE T_RAYON                                                            

(RAYON_RYN  CHAR(16));                                                          

/* tables des entrepôts */                                                      

CREATE TABLE T_ENTREPOT                                                         

(VILLE_ETP  CHAR(16),                                                           

RAYON_RYN  CHAR(16));                                                           

/* alimentation de la table des rayons */                                       

INSERT INTO T_RAYON (RAYON_RYN) VALUES ('frais');                               

INSERT INTO T_RAYON (RAYON_RYN) VALUES ('boisson');                             

INSERT INTO T_RAYON (RAYON_RYN) VALUES ('conserve');                            

INSERT INTO T_RAYON (RAYON_RYN) VALUES ('droguerie');                           

/* alimentation de la table des entrepots */                                    

INSERT INTO T_ENTREPOT (VILLE_ETP, RAYON_RYN) VALUES ('PARIS', 'boisson');      

INSERT INTO T_ENTREPOT (VILLE_ETP, RAYON_RYN) VALUES ('PARIS', 'frais');        

INSERT INTO T_ENTREPOT (VILLE_ETP, RAYON_RYN) VALUES ('PARIS', 'conserve');     

INSERT INTO T_ENTREPOT (VILLE_ETP, RAYON_RYN) VALUES ('LYON', 'boisson');       

INSERT INTO T_ENTREPOT (VILLE_ETP, RAYON_RYN) VALUES ('LYON', 'conserve');      

INSERT INTO T_ENTREPOT (VILLE_ETP, RAYON_RYN) VALUES ('LYON', 'droguerie');     

INSERT INTO T_ENTREPOT (VILLE_ETP, RAYON_RYN) VALUES ('MARSEILLE', 'boisson');  

INSERT INTO T_ENTREPOT (VILLE_ETP, RAYON_RYN) VALUES ('MARSEILLE', 'frais');    

INSERT INTO T_ENTREPOT (VILLE_ETP, RAYON_RYN) VALUES ('MARSEILLE', 'conserve'); 

INSERT INTO T_ENTREPOT (VILLE_ETP, RAYON_RYN) VALUES ('MARSEILLE', 'droguerie');

INSERT INTO T_ENTREPOT (VILLE_ETP, RAYON_RYN) VALUES ('ANGER', 'boisson');      

INSERT INTO T_ENTREPOT (VILLE_ETP, RAYON_RYN) VALUES ('ANGER', 'frais');        

INSERT INTO T_ENTREPOT (VILLE_ETP, RAYON_RYN) VALUES ('ANGER', 'droguerie');    

INSERT INTO T_ENTREPOT (VILLE_ETP, RAYON_RYN) VALUES ('TOULOUSE', 'boisson');   

INSERT INTO T_ENTREPOT (VILLE_ETP, RAYON_RYN) VALUES ('TOULOUSE', 'frais');     

INSERT INTO T_ENTREPOT (VILLE_ETP, RAYON_RYN) VALUES ('TOULOUSE', 'conserve');  

INSERT INTO T_ENTREPOT (VILLE_ETP, RAYON_RYN) VALUES ('TOULOUSE', 'droguerie'); 

/* modif de l'état initial */                                                   

INSERT INTO T_RAYON VALUES ('conserve');                                        

 

3) La méthode qui perd

 

La représentation binaire, c'est comme la représentation "usuelle" décimale, sauf qu'au lieu d'avoir des nombres composés de chiffres de 0 à 9, on a des nombres composés de 0 et de 1.


Par exemple, si on prend le nombre décimal 153, c'est : 

- En décimal 1 * 10^2 + 5 * 10^1 + 3 * 10^0 = 153 [decimal]

- En binaire 1 * 2^7 + 0 * 2^6 + 0 * 2^5 + 1 * 2^4 + 1 * 2^2 + 1 * 2^0 = 100111 [binaire]

 

Pour deux nombres donnés n1 et n2, bitand(n1, n2) est le nombre résultant de l'opération "pour chaque position, si le chiffre correspondant dans n1 et dans n2 vaut 1, on renvoie 1, sinon 0"

Par exemple, 

bitand(153 [décimal], 15 [décimal])

= bitand (100111[binaire], 001111[binaire])

= 000111 [décimal]

= 7

 

Cette représentation colle parfaitement à une liste de flags : si on affecte à : 

- frais la valeur 1

- boisson la valeur 2

- conserve la valeur 4

- droguerie la valeur 8

 

... et qu'on fait la somme, on obtient 15, qui s'écrit 1111 en binaire.

 

Si ensuite on somme ces valeurs pour chaque dépôt, on pourra chercher quels sont les dépôt de somme n2, tels que bitand(n2, 15) = 15.

 

En SQL, ça donne : 

 

WITH t AS (                                                                       

SELECT distinct rayon_ryn, power(2, dense_rank() over(order by rayon_ryn) - 1) flg

FROM t_rayon)                                                                     

, u as (                                                                          

SELECT sum(flg) sumflg                                                            

FROM t)                                                                           

SELECT ville_etp                                                                  

FROM t_entrepot a                                                                 

  JOIN t ON a.rayon_ryn = t.rayon_ryn                                             

  CROSS JOIN U                                                                    

GROUP BY ville_etp, sumflg                                                        

HAVING bitand(sum(distinct t.flg), sumflg) = sumflg                               

 

Voilà voilà, ne pas appliquer ce genre de méthode au boulot, bien entendu...

 

Published by Pacman - dans SQL cosmétique
commenter cet article
3 juillet 2013 3 03 /07 /juillet /2013 16:08

Comment est-ce qu'après une journée d'archéologie PL/SQLienne je me suis demandé pourquoi j'ai étudié une proc qui n'est jamais appelée ?

 

1) Création d'une proc inutile


SQL> r ismagic

  1  create or replace procedure ismagic

  2  IS

  3  BEGIN

  4          --Etape n°31

  5          dbms_output.put_line('Magic is all around');

  6  END;

  7*

 

Procedure created.

 

2) Lire la proc dans SQLDeveloper

 

 

magic

 

3) L'astuce ? 

  
ismagic2.JPG
  
Oui je sais, mon vim a des couleurs infâmes...
Published by Pacman - dans SQL cosmétique
commenter cet article
11 juin 2011 6 11 /06 /juin /2011 12:19

Bon, l'autre jour, mon chef me demandait : sais tu pallindromer une chaîne de caractères sans fonction PL ?

 

... merci google, il y a une built-in fonction :)

 

1) Fonction REVERSE

 

Attendez, un petit jeu de test d'abord :

 

SQL> CREATE TABLE testrev(txt VARCHAR2(255));

Table crÚÚe.

SQL> INSERT INTO testrev VALUES ('marcel');

1 ligne crÚÚe.

SQL> INSERT INTO testrev VALUES ('robert');

1 ligne crÚÚe.

SQL> INSERT INTO testrev VALUES ('cosmetic sql is good for you');

1 ligne crÚÚe.

SQL> commit;

Validation effectuÚe.

 

Et hop :

 

SQL> SELECT txt, reverse(txt) rev
  2  FROM testrev;

TXT                                                REV

marcel                                             lecram
robert                                             trebor
cosmetic sql is good for you                       uoy rof doog si lqs citemsoc

 

Surprise, ça marche !

Mais bon, c'est pas très drôle, disons le.

EDIT :  et en plus la fonction n'est pas supportée, et elle est limitée dans un certain nombre de cas (Cf. le commentaire de Laurent)

 

Ben on va essayer de le faire à la main quand même !

 

2) Encore de la clause MODEL !

 

Alors, dans la mesure où on est capable de faire de l'itératif avec la clause MODEL, on devrait y arriver :

Il suffit d'itérer, pour chaque ligne, sur la longueur de la chaine, et construire le résultat.

 

SQL>  SELECT txt, rev
  2   FROM (
  3      SELECT txt, length(txt) lng, length(txt) cnt
  4      FROM testrev
  5   ) t
  6   MODEL
  7   PARTITION BY (ROWID)
  8   DIMENSION BY (0 n)
  9   MEASURES (txt, lpad(' ', lng) rev, lng, cnt)
 10   RULES
 11   ITERATE(1000000) UNTIL (cnt[0] = 0)
 12   (
 13      rev[0] = substr(rev[0], 1, iteration_number) || substr(txt[0], lng[0] - iteration_number, 1)
 14      , cnt[0] = cnt[0] -1)
 15   /

TXT                                                REV

cosmetic sql is good for you                       uoy rof doog si lqs citemsoc
marcel                                             lecram
robert                                             trebor

 

La question que je me pose encore cela dit : mais à quoi ça sert de pallindromer ?

Pfiou, heureusement que ce blog ne sert à rien...

 

Published by Pacman - dans SQL cosmétique
commenter cet article
25 mai 2011 3 25 /05 /mai /2011 07:25

Salut les gars, moi c'est Robert, et je n'aime pas les mouches.

(Il me fallait une phrase d'intro, j'ai pas trouvé mieux...)

 

Bon aujourd'hui, c'est le deuxième exo de l'Euler challenge !

 

 http://projecteuler.net/index.php?section=problems


DISCLAIMER :

J'ai la flemme de m'inscrire sur le site, donc je ne prendrais même pas la peine de vérifier mes résultats


  By considering the terms in the Fibonacci sequence whose values do not exceed four million, find the sum of the even-valued terms.

 

1) La méthode brutale

 

Il y a quelques temps, je découvrais la clause MODEL qui permet de faire de l'itératif à partir de 10g... et donc facilement des suites récurrentes.

 

Donc du coup, c'est tout simple :

 

SQL> SELECT sum(u)
  2  FROM (
  3       SELECT n, u
  4       FROM (
  5         SELECT 0 n, 0 U FROM DUAL
  6         UNION ALL SELECT 1 n, 1 U FROM DUAL
  7         ) t
  8       MODEL
  9        dimension by (n)
 10        measures (u)
 11        rules upsert
 12        iterate(33)
 13        (u[2+iteration_number] = u[1+iteration_number] + u[iteration_number])
 14  )
 15  WHERE mod(u, 2) = 0
 16  /

    SUM(U)
----------
  4613732

 

Passionnant, n'est ce pas... allez, on va rigoler un peu plus !

 

2) La méthode mathématique

 

Bon, faire la somme de trucs qui sont déjà définis en se sommant les uns les autres, ça sent l'astuce à plein nez, non ?

(J'ai toujours fait les maths à l'intuition, ce qui explique surement mon destin pitoyable...)

 

Première remarque :

Un+2 = Un+1 + Un

Pour que Un+2 soit pair, il faut que Un+1 et Un soient soit tous les deux pairs, soit tous les deux impairs.

Mais s'ils sont tous les deux pairs, tous les autres termes le sont... et ce n'est pas le cas.

 

Bref, regardez bien les termes de la suite :

0 1 1 2 3 5 8 13 21 34 ...

Un terme sur 3 est pair, et bien sûr il est la somme des deux termes impairs précédents.

 

=> Si on s'arrête au bon endroit, la somme des termes pairs est égale à la somme des termes impairs !

 

La proposition formelle P(p) :

Quel que soit p, somme(U3i, 0<= i <= p) = somme(Ui impairs, 0<=i<=3p)

 

La preuve :

P(0) vraie, disons-le (ça c'est de l'argument !)

 

Supposons p(n) vraie, qu'en est-il de p(n+1) ?

 

somme(U3i, 0<= i <= n+1) = somme(U3i, 0<= i <= n)  + U3(n+1)

                                                 = somme(U3i, 0<= i <= n)  + U3n+1 + U3n+2

                                                 = somme(Ui impairs, 0<= i <= 3n)  + U3n+1 + U3n+2

                                                 = somme(Ui impairs, 0<= i <= 3(n+1))

 

Ca, c'est fait... 

 

somme(U3i, 0<= i <= n) = somme(Ui, 0<= i <= 3n) / 2

 

Il ne reste plus qu'à calculer la "série" de Fibonacci !

Pour cela, deuxième remarque :

quand on somme des termes qui justement se définissent comme la somme des deux précédents, on doit avoir des chances de retomber sur un de ces termes, non ?

 

Fibo 0 1 1 2 3 5 8 13 21
Série 0 1 2 4 7 12 20 33 54

 

Ben on retombe pas tout à fait dessus, mais à un près...

Somme(Ui, 0<=i<=n) = Un+2 - 1

 

(La démo par récurrence est triviale, vous la ferez vous-même !)

 

Et du coup, le résultat (en utilisant la forme analytique de Fibonacci) : 

 

somme(U3i, 0<= i <= n) = (U3n+2 - 1) / 2 = ((((1 + sqrt(5)) / 2) ^ (3n+2) - ((1 - sqrt(5)) / 2) ^ (3n+2)) / sqrt(5) - 1) / 2

 

(Ok, c'est illisible...)

 

SQL> SELECT ((power((1+sqrt(5))/2, 35) - power((1-sqrt(5))/2, 35)) / sqrt(5) - 1) / 2 res
  2  FROM dual
  3  /

       RES
----------
   4613732

 

Bon ok, le U33, je l'ai cherché à tâtons et c'est laid, mais bon !

Published by Pacman - dans SQL cosmétique
commenter cet article
19 mai 2011 4 19 /05 /mai /2011 20:04

Bon début, d'une longue, longue série...

 

Le challenge Nr. 1 :

 

 
Ho, facile !
 
1) La méthode brute

Ben en SQL il faut :
- générer les 1000 premier entiers
(
  - filtrer les non multiples de 3 et / ou de 5 et sommer
OU
  - sommer en annulant les non multiples de 3 et / ou de 5
)
Sans discussion, la deuxième solution est la plus proche de la philosophie du PacBlog !
 
SQL> SELECT sum(level * greatest(0, 1 - mod(level, 3) * mod(level, 5)))
  2  FROM DUAL
  3  CONNECT BY level <= 1000
  4  /

SUM(LEVEL*GREATEST(0,1-MOD(LEVEL,3)*MOD(LEVEL,5)))
--------------------------------------------------
                                            234168

- On génère les 1000 premiers entiers
- On les aditionne... avec une astuce :
  - s'ils sont multiples, le reste est 0 et le facteur est neutre
 - s'il ne sont pas multiple,1 - le produit des mods est strictement négatif, et on multiplie par 0... 
 
2) La méthode mathématique

Ben on sait additionner les nombre de 1 à N : N (N+1) / 2
(la piste de démo est : en les couplant deux à deux symétriquement par rapport au milieu, c'est comme multiplier le terme du milieu par 2)
 
Sachant que sommer des multiples de 3 (resp. 5)... c'est comme sommer des entiers consécutifs puis les multiplier par 3 (resp. 5) (vive la distributivité !), il suffit de :
- sommer les multiples de 3
- sommer les multiples de 5
- ah zut il y en a qu'on a ajoutés deux fois... ce sont les multiples de 3 et de 5 : on retranche les multiples de 3 et de 5 (ça vous rappelle pas une formule de patates ?)  
Ce qui donne :
 
SQL> SELECT
  2      trunc (1000/3) * (trunc (1000/3) + 1) / 2 * 3
  3     + trunc (1000/5) * (trunc (1000/5) + 1) / 2 * 5
  4     - trunc (1000/15) * (trunc(1000/15) + 1) /2 * 15 as res
  5  FROM DUAL
  6  /

       RES
----------
    234168

Attendez, je cherche une conclusion à tout cela...
Ho !
 
La mort, n'est qu'une étape de la vie !
EDIT : merci Keeh pour ta vigilance !
Published by Pacman - dans SQL cosmétique
commenter cet article
29 avril 2011 5 29 /04 /avril /2011 13:49

La récursivité pure SQL sous Oracle, c'est pas évident avant la 11g.
Souvent lors de petits défis entre amis, je me suis bien pris la tête, sans grands résultats.
... jusqu'aujourd'hui, où j'ai dépilé une importante tâche de mon énooooorme to-do list : me documenter sur la CLAUSE MODEL de 10g.

 

A vrai dire, je ne suis toujours pas sûr de ce qu'on peut en faire dans la vraie vie... mais en tous cas on peut bien rigoler avec !

 

1) La clause MODEL en quelques mots

 

S'appliquant sur le résultat d'un SELECT, elle permet de considérer le résultat comme un tableau (éventuellement multi-dimensionnel), et de faire joujou avec.
 

Pour avoir les détails, je vous renvoie vers l'excellent article de l'excellent Antoun qui est en fait une excellente traduction d'un non moins excellent Neerlandais dont j'ai oublié le très excellent nom (je me souviens juste qu'il fait plein de points au scrabble) :
http://antoun.developpez.com/oracle/model/

 

2) Les suites numériques !

 

Donc voilà moi ce que j'en ai retenu, c'est qu'on peut créer des nouvelles "cases" dans le "tableau", et ce de manière itérative en faisant référence aux cases précédentes.

 

Du coup, mon grand fantasme est réalisé : sortir la sutie de Fibonacci en "pur" SQL :


  SELECT n, u
  FROM (
    SELECT 0 n, 0 U FROM DUAL
    UNION ALL SELECT 1 n, 1 U FROM DUAL
    ) t
  MODEL
   dimension by (n)
   measures (u)
   rules upsert
   iterate(10)
   (u[2+iteration_number] = u[1+iteration_number] + u[iteration_number])

 

Petite explication pour ceux qui ont eu la flemme de suivre le lien :
- Le résultat du SELECT sans le MODEL, c'est le tableau initial.

 

- J'y mets n et U(n), j'y initialise les premiers éléments de la suite : U0 = 0, U1 = 1

 

- Dimension, c'est ce qui indexe le tableau : n, puisqu'on fait du U(n)

 
- Measures, ça doit lister toutes les grandeurs du tableau, ici juste U(n)

 
- Rules : c'est là que commencent les règles de calcul

 


- Upsert : c'est pour dire que si pour un n donné U(n) existe déjà dans le tableau, on le met à jour, sinon on crée une nouvelle ligne. On peut spécifier Update ou Upsert All (ce dernier étant un truc obscur, je le rajoute à ma to-do list)
Iterate(10) : ca dit que la règle qui va suivre, faudra l'appliquer 10 fois (en fait 11, parce qu'on commence à 0 je dirais)

 
- Et la dernière ligne, c'est la règle de calcul à appliquer. Ici c'est la définition de la récurrence : Un+2   =   Un+1   +   Un.

 

Tadaaaam !

 

         N          U
---------- ----------
         0          0
         1          1
         2          1
         3          2
         4          3
         5          5
         6          8
         7         13
         8         21
         9         34
        10         55
        11         89

 

12 rows selected.

 

(Va falloir que je change de froc, je me suis fait dessus)

 

3) Produit cumulatif

 

Je sais pas si ça se fait dans la vraie vie, mais parmi les nombreuses applications de la récursivité, il y a le produit cumulatif.

Et la requête MODEL, je sens qu'elle est encore plus grottesque que la version Exp(Sum(Ln(x))

 

Alors, un jeu de données :

SQL> CREATE TABLE testcumproduct
  2  (grp NUMBER,
  3   ratio NUMBER);

 

C'est parti !

 

SQL> select grp, ratio, rn, nb
  2  from (
  3    select grp, ratio, row_number() over(partition by grp order by null) rn, count(*) over(partition by grp) nb
  4    from testcumproduct
  5  ) t
  6  model
  7    return updated rows
  8    partition by (grp)
  9    dimension by (rn)
 10    measures(ratio, nb)
 11    rules upsert
 12    iterate (100) until (iteration_number = nb[1]-1)
 13    ( ratio[1] = ratio[1] * coalesce(1+ratio[2+iteration_number], 1) );

 

       GRP      RATIO         RN         NB
---------- ---------- ---------- ----------
         1      .3432          1          4
         0     1.0296          1          5

 

A noter que :
- j'ai multiplié par 1+ ratio, pour faire genre ce sont des taux
- La clause partition permet d'émuler le group by
- J'itère en mettant à jour la première ligne de chaque groupe
- La clause return updated rows permet de compléter l'émulation du group by
- J'aurais bien voulu mettre directement mon nb dans en paramètre d'itération, mais il veut pas...

 

Voilà voilà, encore un article complètement inutile, vous êtes bien sur le PacBlog !

 

 

Published by Pacman - dans SQL cosmétique
commenter cet article
9 mars 2010 2 09 /03 /mars /2010 17:24

Recently, i've read an article from Steven Feuerstein about stripping strings :
http://www.oracle.com/technology/oramag/oracle/06-jan/o16plsql.html

Starting with Oracle 10g, you can use REGEXP_REPLACE to strip any string from a list of given chars.
However, in earlier version, he illustrates the use of the TRANSLATE function for this purpose.

The issues can be summed up as :
- Passing a NULL second argument to the translate function nullyfies the result :

SELECT translate('Steven Feuerstein', 'x', '') FROM dual;
T
-
 
1 row selected.

- The trick : use a joker char which you replace by... itself

SELECT translate('Steven Feuerstein', 'xe', 'x') FROM dual
TRANSLATE('S
------------
Stvn Furstin

1 row selected.

... but you cannot use a constant joker character for all of you strippings, because this character might be the one you want to strip off !
That's why Uncle Steven suggest you should have this joker char beeing as well an argument of your stripping function.

Today in this article, i'll just show a funny way to do it without joker char argument ;)
Disclaimer : i don't want to compete with master Steven, just having a little fun with Oracle SQL !


1) CONNECT BY LEVEL forever !

Let's use the CONNECT BY LEVEL bug to "spell" the string in rows :

SELECT substr('Steven Feuerstein', level, 1) as c, level as rk
FROM dual
CONNECT BY level <= length('Steven Feuerstein')

C         RK
- ----------
S          1
t          2
e          3
v          4
e          5
n          6
           7
F          8
e          9
u         10
e         11
r         12
s         13
t         14
e         15
i         16
n         17

17 rows selected.

Now the stripping just looks like a filter :

SELECT c, row_number() over(order by rk) rk from
    (SELECT substr('Steven Feuerstein', level, 1) as c, level as rk
    FROM dual
    CONNECT by level <= length('Steven Feuerstein')) inp
 WHERE inp.c <> 'e'

C         RK
- ----------
S          1
t          2
v          3
n          4
           5
F          6
u          7
r          8
s          9
t         10
i         11
n         12


12 rows selected.

2) Re-aggregate it... using replace ?!

You sure noticed the ranking of the lines in the previous query. The purspose was obvious : beeing able to re-aggregate the chars after filtering !

In Oracle 11g version, the build in function LISTAGG would make it quite easy.

I choose the XMLAGG method that works in 9i :
- you put your string in single XML tags
- you aggregate the tags...
... then you strip the tags from the resulting string !


Really funny, was there really an issue from the beginning ?


SELECT replace('Steven Feuerstein', 'e', '') as A, translate('Steven Feuerstein', 'e', '') AS b
FROM dual;

REPLACE('STE T
------------ -
Stvn Furstin 


1 row selected.


Yeah, just as expected !
But you can only give one character to strip (unless you want to strip it from a string, which differs from stripping several different chars) as argument of the REPLACE function.


Let's change our example to prove the worthiness of our trick and strip the input string from two chars : "e" and "t"
... and re-aggregate the result :


SELECT replace(replace(xmlagg(xmlelement("marcel", c) order by rk), '<marcel>', ''), '</marcel>', '')
FROM
(SELECT c, row_number() over(order by rk) rk from
    (SELECT substr('Steven Feuerstein', level, 1) as c, level as rk
    FROM dual
    CONNECT by level <= length('Steven Feuerstein')) inp
 WHERE inp.c NOT IN (SELECT substr('et', level, 1) FROM dual CONNECT BY level <= length('et'))
 )

REPLACE(REPLACE(XMLAGG(XMLELEMENT("MARCEL",C)ORDERBYRK),'<MARCEL>',''),'</MARCEL
--------------------------------------------------------------------------------
Svn Fursin                                        
                             

1 row selected.

Great...
...But if you really want to use this underperforming method that could become obsolete whenever the CONNECT BY LEVEL bug would be fixed, you have to put it in a function !
Because you cannot "pass" arguments to the deeper levels of a nested query expression.


Cosmetic SQL Rules !


 

Published by Pacman - dans SQL cosmétique
commenter cet article
1 septembre 2009 2 01 /09 /septembre /2009 14:17
Je ne sais pas trop pourquoi, mais je me sens toujours très fier quand j'arrive à éliminer tous mes CASE ... WHEN ... THEN ... END de mes expressions.
Surtout quand mon expression n'implique que des entiers relatifs !
Pour les petits malins, il ne s'agit pas de les remplacer par des DECODE, mais de se restreindre aux opérateurs "arithmétiques" usuels :
+, -, *, /, mod, partie entière, valeur absolue
Bien entendu, on voit tout de suite que "partie entière" et "valeur absolue" contiennent un CASE ... WHEN .. END, en quelques sortes : et c'est bien ce qu'on va tenter d'exploiter...
Avant de commencer, réglons la question de l'utilité : aucune. J'irais même plus loin :
N'APPLIQUEZ PAS CES METHODES AU TRAVAIL, VOUS SERIEZ LICENCIES POUR FAUTE PROFESSIONNELLE !

Afin d'arriver à une méthode générique de transformation du CASE, nous suivrons les étapes suivantes :
0) Périmètre et jeux de données
1) Le sélecteur simple : CASE x WHEN a THEN b ELSE 0 END
2) La condition non exacte : CASE WHEN x >= a THEN y ELSE 0 END
3) Composition des conditions unitaires : CASE WHEN x > a AND y > b THEN z ELSE 0 END
4) Branchement de diverses conditions : CASE WHEN Cond1 THEN y WHEN Cond2 THEN z ... END

0) Périmètre et jeux de données
Les valeurs "élémentaires" sont entières, éventuellement négatives
Les opérateurs permis autorisés sont : +, -, *, /, mod (modulo), trunc noté aussi [x], abs noté aussi |x|)
Les expressions de CASE que l'on transformera seront :
>=, <=, =, BETWEEN, IN, NOT IN. Avec les connecteurs AND, OR et NOT

Pour les jeux de données, nous créons la table contenant toutes les quadruplets formés de nombres entiers dans l'intervalle [-10, 10]

CREATE TABLE ME_MANQUE_UNE_CASE AS
WITH t AS (
SELECT 10 - level + 1 as nb FROM DUAL
CONNECT BY level <= 21)
select a.nb as a, b.nb as b, c.nb as c, d.nb as d
FROM t a
  CROSS JOIN t b
  CROSS JOIN t c
  CROSS JOIN t d


1) Le sélecteur simple : CASE x WHEN a THEN b ELSE 0 END
C'est la base de tout !
Comment distinguer la valeur "a" d'une variable "x" parmi toutes les autres valeurs ?
D'instinct, on remarque que l'on peut généraliser le problème par la translation :
x -> x - a
... ce qui revient à poser la question : quelle expression renvoit 1 quand x vaut 0, 0 sinon ?
(soit CASE x WHEN 0 THEN 1 ELSE 0 END)

La première idée, c'est de se dire que  1 / x est strictement compris entre 0 et 1, et on aimerait en prendre la partie entière supérieure.
Mais la division par 0, c'est généralement interdit.

Tout en restant dans cet esprit (ramener sur l'intervalle et prendre la partie entière pour générer une "discontinuité" en 0), on a envie de tenter :
x / (x + 1)

Car on cherche l'expression qui ne dépend que de x est qui lui est strictement supérieure. Cela ne marche pas très bien pour les nombres négatifs...
Et on en vient tout naturellement à :
f(x) = [1 - |x| / (|x| + 1)], qui renvoit 1 quand x vaut 0, 0 sinon


Et donc on réécrit :
f(x) = CASE x WHEN a THEN b ELSE 0 END
En :
f(x) = [1 - |x - a| / (|x - a| + 1)] * b


Petit test sur la table :

SQL> SELECT trunc(1 - abs(a - 5) / (abs(a - 5) + 1)) * 2 as nw,
  2     CASE a WHEN 5 THEN 2 ELSE 0 END as old,
  3     a.a
  4  FROM me_manque_une_case a
  5  WHERE a = 5 and b = 1 and c = 3 and d = 4
  6  /

        NW        OLD          A
---------- ---------- ----------
         2          2          5


2) La condition non exacte : CASE WHEN x >= a THEN y ELSE 0 END

A partir de maintenant, nous allons principalement nous ramener au cas basique étudié en 1)
La fonction créée se décompose tout naturellement en une fonction de sélection qui renvoit 1 ou 0, et une constante multiplicative.
Toujours la même démarche : essayer de ramener au cas le plus général en composant / substituant / translatant... puis résoudre !
x > a <=> x - a >= 0
Comment caractérise-t-on les nombres positifs ?
Ce sont tout ceux qui sont égaux à leur valeur absolue !
x >= 0 <=> x - |x| = 0

D'où la transformation finale :
f(x) = CASE WHEN x >= a THEN y ELSE 0 END
f(x) = x - a - |x-a|
f(x) = y * [1 - |x - a - |x-a|| / (|x - a - |x-a|| + 1)]

Petit exemple :

SQL> select * from (
  2  select 5 * trunc(1 - abs(a - 2 - abs(a-2)) / (abs(a - 2 - abs(a-2)) + 1)) as reecr,
  3  CASE WHEN a >= 2 THEN 5 ELSE 0 END as orig
  4  , a.*, row_number() over (partition by a order by null) rk
  5  from me_manque_une_case a
  6  where a in (1, 3, -1)
  7  )
  8  where rk = 1
  9  /

     REECR       ORIG          A          B          C          D         RK
---------- ---------- ---------- ---------- ---------- ---------- ----------
         0          0         -1         10         10         10          1
         0          0          1         10         10         10          1
         5          5          3         10         10         10          1


Nous savons maintenant écrire pour une condition élémentaire la fonction de sélection f :
f(x) = 1 si cond
       0 sinon
Tout naturellement, la fonction g associée à NOT(cond) est :     
g(x) = 1 - f(x)
(Je ne vous fais pas l'affront de vous expliquer pourquoi)

3) Composition des conditions unitaires : CASE WHEN x > a AND y > b THEN z ELSE 0 END

L'ensemble des conditions logiques, prédicats est "structuré", "algébrique".
(Vous noterez que j'utilise tout le temps des doubles quotes pour pallier mon manque de précision dans le vocabulaire :))
Je veux dire par là que la composition de prédicats par l'intermédiaire d'opérateurs logiques constitue également des prédicats, et que réciproquement, d'une certaine manière, tout prédicat peut être décomposé en prédicats simples reliés par ces opérateurs.
Ansi, si nous arrivons maintenant à formaliser ces opérations (c'est à dire écrire leur fonction Fop(c1, c2), nous pourrons considérer que l'objectif est atteint.

Soit f(x) et g(x) deux fonctions de sélection, telles que :
f1(x) = f(x) * y = CASE WHEN cond1(x) THEN y ELSE 0 END
g1(x) = g(x) * z = CASE WHEN cond2(x) THEN z ELSE 0 END

La fonction h de sélection pour les opérattions logiques basiques :
cond1 OR cond2 : h(x) = f(x) + g(x) - f(x) * g(x)
cond1 AND cond2 : h(x) = f(x) * g(x)
NOT cond1 : h(x) = 1 - f(x)

On peut étendre a présent les tests de base à IN, NOT IN et BETWEEN :
x IN (a1, a2, ..., an) <=> x = a1 OR x = a2 OR ...
x BETWEEN a AND b <=> x >= a AND x <= b

Allez, on applique tout ça !


select * from (
select trunc(1 - abs(a - 2 - abs(a-2)) / (abs(a - 2 - abs(a-2)) + 1)) *
(trunc(1 - abs(b - 6) / (abs(b - 6) + 1)) + trunc(1 - abs(c + 1) / (abs(c + 1 ) + 1)) - trunc(1 - abs(b - 6) / (abs(b - 6) + 1)) * trunc(1 - abs(c + 1) / (abs(c + 1 ) + 1))) *
(1 - trunc(1 - abs(d - 5) / (abs(d - 5) + 1))) * 5 as reecr,
CASE WHEN a >= 2 AND (b = 6 OR c = -1) AND d <> 5  THEN 5 ELSE 0 END as orig,
a.*
from me_manque_une_case a
)
where reecr <> orig
/


Aucun résultat : les deux écritures sont strictement équivalentes.

4) Branchement de diverses conditions : CASE WHEN Cond1 THEN y WHEN Cond2 THEN z ... END

Jusqu'à présent, nous n'avons réalisé qu'un seul branchement.
Allez, un dernier petit effort !
CASE WHEN cond1 THEN a WHEN cond2 THEN b ELSE c END
Pour implémenter une succession de branchements, nous devons retranscrire de la manière suivante :
Soient f et g les fonctions de sélectios de cond1 et cond2.
cond1
NOT cond1 AND cond2
NOT cond1 AND not cond2
Donc :
f(x) * a + (1 - f(x)) * g(x) * b + (1 - f(x)) * (1 - g(x)) * c

Et donc, la dernière illustration :

SQL> select * from (
  2  select trunc(1 - abs(a - 2 - abs(a-2)) / (abs(a - 2 - abs(a-2)) + 1))
  3  +
  4  (1 - trunc(1 - abs(a - 2 - abs(a-2)) / (abs(a - 2 - abs(a-2)) + 1))) *
  5  (trunc(1 - abs(b - 6) / (abs(b - 6) + 1)) + trunc(1 - abs(c + 1) / (abs(c + 1 ) + 1))
  6      - trunc(1 - abs(b - 6) / (abs(b - 6) + 1)) * trunc(1 - abs(c + 1) / (abs(c + 1 ) + 1))) * 2
  7  +
  8  (1 - trunc(1 - abs(a - 2 - abs(a-2)) / (abs(a - 2 - abs(a-2)) + 1))) *
  9  (1-(trunc(1 - abs(b - 6) / (abs(b - 6) + 1)) + trunc(1 - abs(c + 1) / (abs(c + 1 ) + 1)) -
 10        trunc(1 - abs(b - 6) / (abs(b - 6) + 1)) * trunc(1 - abs(c + 1) / (abs(c + 1 ) + 1)))) * 3
 11  as reecr,
 12  CASE WHEN a >= 2 THEN 1 WHEN (b = 6 OR c = -1) THEN 2 ELSE 3 END as orig,
 13  a.*
 14  from me_manque_une_case a
 15  order by dbms_random.random
 16  )
 17  --where reecr <> orig
 18  where rownum <= 20;

     REECR       ORIG          A          B          C          D
---------- ---------- ---------- ---------- ---------- ----------
         2          2         -7         -8         -1         -5
         1          1          9          4         -6          7
         1          1          8          7          7         -2
         3          3          0         -5         -7          1
         3          3         -2         -5          6         -4
         3          3         -8          5         -4         -6
         1          1          9         -7         -6         -8
         3          3          1        -10          6         -2
         3          3         -4         -8         -2         -9
         1          1          7         10         -9         -5
         1          1         10          6         -6         -3
         3          3         -7          3         -8         -4
         1          1          7          9          3         -8
         3          3          1          5         -2          2
         3          3         -8          8          2          1
         3          3         -1          3         -4          1
         3          3         -3         -2          5         -2
         3          3          0         -7          4          8
         1          1          2         -8          6         -8
         3          3         -3        -10        -10         -6

20 ligne(s) sÚlectionnÚe(s).



=> Sous performant, absolument illisible, inutile... Vive le Cosmetic SQL !
Published by Pacman - dans SQL cosmétique
commenter cet article
12 août 2009 3 12 /08 /août /2009 10:33
Nous inaugurons aujourd'hui cette section, dont l'objectif est... hmmm... aucun.

Nous allons d'ailleurs le prouver tout de suite, car le sujet du jour est : dessiner un coeur avec une requête SQL.
C'est dédié à mon ami Vodiem, un grand spécialiste du cosmetic SQL.

Bien sûr, il serait possible de faire des select de caractères ligne par ligne... mais on va se compliquer un peu la tâche, car c'est aussi cela le cosmetic SQL.
Donc en généralisant, il s'agit de dessiner une courbe quelconque en une requête.

Pour cela, on commence par décomposer la courbe en portions de courbes représentant des fonctions usuelles (Droite, Cosinus, Sinus, ...)
Dans le cas précis du coeur, on peut considérer par exemple qu'il s'agit de deux arcs de sinus / cosinus, plus deux segments de droite.

Il faut donc formaliser la représentation d'une fonction y = f(x) dans un repère orthonormé. Une fois cela fait, il faudra coller ensemble les morceaux (plus facile à dire qu'à faire...)

1) Représentation d'une fonction
Faisons le parallèle avec notre repère Euclidien habituel :
La coordonnée "y" est gérée par le numéro de ligne décroissant.
La coordonnée x est gérée par le positionnement d'un caractère grâce à LPAD.

Cependant, une différence notable : on génère ligne par ligne les "x", donc l'équation entrée dans le LPAD correspond à :
x = f(y)

Exemple :

select x
from (
select lpad('o', 2 * level) as x, level as y
from dual
connect by level <= 10
)
order by y desc
/


X
-----------------------
                   o
                 o
               o
             o
           o
         o
       o
     o
   o
 o

10 rows selected.


En somme, pour afficher la courbe correspond à l'équation y = f(x), il faut que f soit inversible.
Cependant, toutes les fonctions ne sont pas inversibles. Par contre, une grande partie d'entre elles sont décomposables en intervalles sur lesquels elles sont strictement monotones.
Par exemple,  f: x-> sinus(x) entre [0, pi/2] et [pi/2, pi].

with p as (
            select 'o' as pt, 20 as sc from dual)
,intv as (            
            select 1 as id, 5 as ymaxi, 4 as ymini from dual union all
            select 2 as id, 5 as ymaxi, 4 as ymini from dual
            )
,intvsc as (
            select pt, sc, sc * ymaxi as ymax, sc * ymini as ymin, ymini, ymaxi, id
            from p cross join intv)
, intvm as (
            select max(ymax) as ymax from intvsc)
,t as (
select ymax - level  as y --, ymax, level
from intvm
connect by level <= ymax
union all select ymax from intvm
)
, res1 as (
select case   when id = 1 then asin((y /*+ ymini-4*/- ymin)/sc) * sc                                                       
               when id = 2 then (3.141592  - asin((y - ymin)/sc)) * sc               
      end as x            
            , y
            , pt
            , id
from t join intvsc on t.y between intvsc.ymin and intvsc.ymax
)
, res2 as (
select x, y, coalesce(lag(x, 1) over(partition by y order by x, id), 0) as xprev, pt, id
, row_number() over(partition by y, x order by id) as dedoubl
from res1
)
select substr(REPLACE(REPLACE(XMLAgg(XMLElement("x", coalesce(lpad(pt, x - xprev), pt)) order by x),'</x>', ' '),'<x>',' '),2) AS x
from res2
where dedoubl = 1
group by y
order by y desc
;

X
------------------------------------------------------------------
                              o  o
                        o             o
                     o                   o
                   o                       o
                 o                          o
               o                             o
              o                                o
             o                                   o
           o                                      o
          o                                        o
         o                                          o
        o                                             o
       o                                               o
      o                                                 o
     o                                                   o
    o                                                     o
   o                                                       o
  o                                                         o
 o                                                           o
o                                                             o
o                                                               o

21 rows selected.


Les sections de la requête traitent les problématiques rencontrées :
- intv : décrit les intervalles sur lesquels les fonctions sont définies
- intvsc : il s'agit de recadrer l'intervalle.
    D'une part, on lève la difficulté de gérer des coordonnées négatives (soit en abscisse, soit en ordonnée) en réalisant une translation des bornes.
    D'autre, on applique un facteur d'échelle sc(ale) (dans cet exemple 20) : du fait que la granularité des coordonnées est forcément de "1" (soit le numéro de ligne en ordonnée, soit le décalage "LPAD" en abscisse), il faut pouvoir "zoomer".
- t : on traduit l'union des intervalles de définition par la génération "à la volée" de lignes par CONNECT BY LEVEL
- res1 : pour chaque intervalle et chaque fonction s'appliquant dessus, on génère la coordonnée x = f(y) correspondante
    A noter l'utilisation de arcsin, la fonction réciproque de sinus, ainsi que la composition x = arc(g(y)), qui matérialise les translations et homothéties décrites plus haut
- On arrive aux parties finales, les plus sensibles : recoller les morceaux ! (res2 et SELECT final)
    Pour cela, il faut aggréger les lignes qui correspondent à une même ordonnée y. Plusieurs méthodes sont possibles.
    Ici, la solution retenue (Merci Waldar qui me l'a fait découvrir !), est celle de l'aggrégation XML : on représente d'abord les différentes valeurs sous forme de feuilles XML (XMLElement), puis on les concatène (XMLAgg), puis on nettoie les balises dans le résultat.
    Deux problèmes supplémentaires sont traités par l'intermédiaire de res2 (et des fonctions analytiques) : le fait qu'il s'agit plus d'une juxtaposition que d'une aggrégation, dans la mesure où il faut retrancher les LPAD de l'élément précédent ; Et le fait que si deux points sont "confondus", l'aggrégation simple les affichera tous les deux...

Maintenant qu'on sait comment ça marche, il ne reste plus qu'à tracer le coeur en entier !

with p as (
            select chr(3) as pt, 10 as sc from dual)
,intv as (            
            select 1 as id, 5 as ymaxi, 4 as ymini from dual union all
            select 2 as id, 5 as ymaxi, 4 as ymini from dual union all
            select 3 as id, 5 as ymaxi, 4 as ymini from dual union all
            select 4 as id, 5 as ymaxi, 4 as ymini from dual union all
            select 5 as id, 4 as ymaxi, 0 as ymini from dual union all
            select 6 as id, 4 as ymaxi, 0 as ymini from dual
            )
,intvsc as (          
            select pt, sc, sc * ymaxi as ymax, sc * ymini as ymin, ymini, ymaxi, id
            from p cross join intv)
, intvm as (
            select max(ymax) as ymax from intvsc)
,t as (
select ymax - level  as y
from intvm
connect by level <= ymax
union all select ymax from intvm
)
, res1 as (
select case    when id = 1 then asin((y - ymin)/sc) * sc                                                       
               when id = 2 then (3.141592  - asin((y - ymin)/sc)) * sc
               when id = 3 then (3.141592  + asin((y - ymin)/sc)) * sc                                        
               when id = 4 then (6.283185  - asin((y - ymin)/sc)) * sc
               when id = 5 then (-3.141592 * y / 4  /sc + 3.141592  ) * sc
               when id = 6 then (3.141592 * y / 4  /sc + 3.141592  ) * sc               
      end as x            
            , y
            , pt
            , id
from t join intvsc on t.y between intvsc.ymin and intvsc.ymax
)
, res2 as (
select x, y, coalesce(lag(x, 1) over(partition by y order by x, id), 0) as xprev, pt, id
, row_number() over(partition by y, x order by id) as dedoubl
from res1
)
select substr(REPLACE(REPLACE(XMLAgg(XMLElement("x", coalesce(lpad(pt, x - xprev), pt)) order by x),'</x>', ' '),'<x>',' '),2) AS x
from res2
where dedoubl = 1
group by y
order by y desc


X
----------------------------------------------------------------------
              ♥  ♥                                ♥  ♥
          ♥          ♥                       ♥          ♥
        ♥             ♥                   ♥             ♥
      ♥                ♥                ♥                ♥
     ♥                   ♥             ♥                   ♥
    ♥                     ♥           ♥                     ♥
   ♥                        ♥         ♥                        ♥
  ♥                          ♥       ♥                          ♥
 ♥                            ♥     ♥                            ♥
♥                              ♥   ♥                              ♥
♥                                ♥                                ♥  ♥
♥                                                              ♥
♥                                                            ♥
 ♥                                                           ♥
  ♥                                                         ♥
  ♥                                                       ♥
   ♥                                                      ♥
    ♥                                                    ♥
     ♥                                                   ♥
      ♥                                                 ♥
      ♥                                                ♥
       ♥                                              ♥
        ♥                                            ♥
         ♥                                           ♥
         ♥                                         ♥
          ♥                                        ♥
           ♥                                      ♥
            ♥                                     ♥
             ♥                                   ♥
             ♥                                 ♥
              ♥                                ♥
               ♥                              ♥
                ♥                             ♥
                 ♥                           ♥
                 ♥                          ♥
                  ♥                        ♥
                   ♥                      ♥
                    ♥                     ♥
                    ♥                   ♥
                     ♥                  ♥
                      ♥                ♥
                       ♥               ♥
                        ♥             ♥
                        ♥           ♥
                         ♥          ♥
                          ♥        ♥
                           ♥       ♥
                            ♥     ♥
                            ♥    ♥
                             ♥  ♥
                              ♥


Voilà !
C'est très imparfait, mais ça s'explique :
- Il faut soigner les intersections entre les intervalles de définition
- Il faut ajuster la fonction d'aggrégation : on remarque en effet que plus on colle de morceaux, plus la courbe devient irrégulière.
En fait, lpad('o', f(y)) correspond à x = f(y) + 1/ sc...

Je vous laisse paufiner ces détails.
Quoi qu'il en soit, à présent, vous pourrez faire tomber le coeur de votre jolie DBA en lui envoyant cette requête !
(Petit conseil : faites un script qui balance la requête en boucle afin de faire clignoter ses écrans de surveillance. Au mieux, vous lancez en EXECUTE IMMEDIATE en faisant varier le paramètre sc pour faire craquer le shared pool :))
Published by Pacman - dans SQL cosmétique
commenter cet article