Quantcast

Dimanche 1 avril 7 01 /04 /Avr 14:02

Ne serait-ce que par esprit de contradiction, le premier avril est le parfait moment pour aborder le sujet le moins drôle de l'univers :voir les données avant modifications, et les comparer aux données présentes !

 

0) Un jeu d'essai simple :

 

SQL> CREATE TABLE testflash AS
  2  SELECT level id, mod(level, 5) n
  3  FROM DUAL
  4  CONNECT BY level <= 2000;

Table créée

 

... et quelques modifications :

 

SQL> UPDATE testflash
  2  SET n = 99
  3  WHERE id = 1000;

1 ligne mise à jour.


SQL> INSERT INTO testflash VALUES(2001, 99);

1 ligne créée.

SQL> DELETE FROM testflash WHERE id = 2000;

1 ligne supprimée.

SQL> commit;

Validation effectuée.

 

On vérifie que les modifications sont bien là :

 

SQL> SELECT *
  2  FROM testflash
  3  WHERE id IN (1000, 2000, 2001);

        ID          N
---------- ----------
      1000         99
      2001         99

 

 

1) AS OF TIMESTAMP :

 

Par principe, Oracle est capable de livrer une vue dans le passée des données, afin de garantir la consistence de la lecture. C'est fait de manière automatique, par exemple lorsque ta requête ultra longue ne voit pas les modifications qui sont commitées par ailleurs depuis le début de son executions.

 

En 9i FLASHBACK QUERY permet d'exploiter la mécanique pour demander explicitement, à un instant donné, de lire les données du passé. (Je passe les détails parce ça m'intéresse pas plus que ça)

 

Et en 9iR2, on va un peu plus loin en permettant, à l'intérieur d'une requête, de préciser quelles tables doivent être lues dans le passé. C'est la clause AS OF TIMESTAMP ajoutée après le nom de la table :

 

SQL> SELECT *
  2  FROM testflash AS OF TIMESTAMP sysdate - 1/24/12
  3  WHERE id IN (1000, 2000, 2001);

        ID          N
---------- ----------
      1000          0
      2000          0

 

Tadam ! Ce sont les données avant modifications.

1/24/12 pour dire que c'était "il y a 5 minutes", mais on peu bien sûr spécifier directement une vraie date (et c'est ce qu'on va faire dans la suite) :

 

SQL> SELECT *
  2  FROM testflash AS OF TIMESTAMP to_date('20120401 13:00', 'yyyymmdd hh24:mi')
  3  WHERE id IN (1000, 2000, 2001);

        ID          N
---------- ----------
      1000          0
      2000          0

 

2) Full outer Compare !

 

Et donc maintenant, pour voir exactement ce qui a changé, une jointure full externe sur la table avant / après permet de faire un diff sympa :


SQL> SELECT CASE WHEN a.id is NULL THEN 'added'
  2              WHEN b.id IS NULL THEN 'removed'
  3     ELSE 'modified' END status,
  4         a.id, a.n, b.id newid, b.n newn
  5  FROM testflash a
  6    FULL OUTER JOIN testflash AS OF TIMESTAMP to_date('20120401 13:00', 'yyyymmdd hh24:mi') b
  7     ON a.id = b.id
  8  WHERE a.id IS NULL OR b.id IS NULL OR a.n <> b.n;

STATUS           ID          N      NEWID       NEWN
-------- ---------- ---------- ---------- ----------
modified       1000         99       1000          0
removed        2001         99
added                                2000          0

 

3) Entre autres

 

Il y a quelques pré-requis :

- ëtre post 9.2 bien sûr

- l'utilisateur doit avoir le grant execute sur le package dbms_flashback

- les données doivent être disponibles, un peu dans l'esprit ORA-1555

- pas possible lorsqu'il y a eu un DDL entre temps sur la table (notamment TRUNCATE !)

 

TIens sinon, la semaine dernière chuis passé OCM !

(On n'échappe pas aux traditions stupides...)

 

Allez, zou, je vais aller m'occuper de ma gamine qui a bu entre temps toute l'eau de Javel...

(Pas la peine d'appeler la DASS, c'est encore un poisson, hein !)

Par Pacman - Publié dans : SQL
Ecrire un commentaire - Voir les 4 commentaires
Mardi 24 janvier 2 24 /01 /Jan 08:44

Hier c'étaient des replays de starcraft 2, une autre fois c'était Jack Bauer, ... Aujourd'hui au réveil, une soudaine envie de jouer avec des index.

Un index, c'est une structure arborescente qui, à une clef, potentiellement composite, associe une adresse (rowid) dans une table.


Le nombre d'étages de cet arbre, ou niveau de l'arbre, ou encore "b-tree level", dépend du nombre de blocs feuilles à référencer (le bloc de départ peut contenir n références vers des blocs au niveau inférieur, qui eux même peuvent contenir m références, ...), mais pas seulement !


 

tmp.jpg


1) Un petit exemple rigolo :

On va illustrer avec une table à deux colonnes : l'une constante, l'autre identifiant unique.  

CREATE TABLE t AS SELECT lpad('x', 255, 'X') c, level l FROM DUAL CONNECT BY level <= 1000;

On construit deux index composites sur ces deux colonnes en intervertissant l'ordre

CREATE INDEX idxt1 ON t(c, l) COMPUTE STATISTICS;

CREATE INDEX idxt2 ON t(l, c) COMPUTE STATISTICS;

Et là, on vérifie le niveau du b-tree index :

SELECT index_name, blevel
FROM dba_indexes
WHERE table_name = 'T';

INDEX_NAME                         BLEVEL
------------------------------ ----------
IDXT1                                   2
IDXT2                                   1

Quand on met la colonne la plus sélective en premier, ça baisse le niveau de l'index !

2) Ca marche aussi pour les index concaténés au lieu de composites

CREATE INDEX IDXT3 ON t(c||l) COMPUTE STATISTICS;

CREATE INDEX IDXT4 ON t(l||c) COMPUTE STATISTICS;

SELECT index_name, blevel
FROM dba_indexes
WHERE table_name = 'T';

INDEX_NAME                         BLEVEL
------------------------------ ----------
IDXT1                                   2
IDXT2                                   1
IDXT3                                   2
IDXT4                                   1

Même sentence.

L'idée en fait, c'est que pour référencer un niveau inférieur de l'arbre, vous n'avez pas besoin de toute la clef.


Supposez que dans le premier niveau d'indirection, vous référenciez un bloc feuille de la manière suivante :
Pour les valeurs entre '1;xxx...x' à '500;xxx...x', rendez-vous bloc 4233 !
... se reformule aisément en :
Pour les valeurs entre '1*' à '500*', rendez-vous bloc 4233 !
=> Les références sont beaucoup plus courtes, on peut en coincer beaucoup plus dans un bloc, et on a donc besoin de moins de niveaux d'indirection !

3) La même bien sûr sans concaténation de l'index

Cette fois, le pattern est présent naturellement dans la colonne.

CREATE TABLE u AS SELECT level || lpad('x', 255, 'X') c FROM DUAL CONNECT BY level <= 1000;

CREATE INDEX idxu1 ON u(c) COMPUTE STATISTICS;

CREATE TABLE W AS SELECT lpad('x', 255, 'X') ||level c FROM DUAL CONNECT BY level <= 1000;

CREATE INDEX idxw1 ON w(c) COMPUTE STATISTICS;

SELECT index_name, blevel
FROM dba_indexes
WHERE table_name IN ('U', 'W');

INDEX_NAME                         BLEVEL
------------------------------ ----------
IDXU1                                   1
IDXW1                                   2

Exactement la même... comme on pouvait s'y attendre.
C'est dans le mécanisme même de construction de l'index.

4) Juste pour déconner

Sur un index à forte redondance sur les premières positions de la clef composite, on peut (depuis 9i si je ne me trompe pas), compresser :

ALTER INDEX idxt1 REBUILD COMPRESS 1 COMPUTE STATISTICS;


SELECT index_name, blevel
FROM dba_indexes
WHERE table_name = 'T';

INDEX_NAME                         BLEVEL LEAF_BLOCKS
------------------------------ ---------- -----------
IDXT1                                   1           3
IDXT2                                   1          39
IDXT3                                   2          39
IDXT4                                   1          39

IDXT1 est devenu tout petit non seulement quant à son niveau, mais aussi pour les blocs feuilles qui sont elles aussi compressés.... c'est Imbattable :)

Par Pacman - Publié dans : SGBD
Ecrire un commentaire - Voir les 0 commentaires
Dimanche 22 janvier 7 22 /01 /Jan 14:23

Connaître la répartition des valeurs, c'est la clef pour briller en société et réussir ses projets.

 

Genre vous avez une ressource payante, et un résultat qui sera fonction croissante de la quantité de ressource que vous allouez. Ce qui vous intéresse, c'est bien entendu la gueule de la courbe de cette fonction, son côté linéaire, logarithmique ou exponentiel, ...

"En investissant 2M dans votre campagne, vous aurez un taux de conversion de 90%. En investissant 3M, vous atteindrez 91%"

 

Pour ce genre de problèmatique, on se tape un peu de connaître la moyenne ou l'écart type d'une population.

Moi ce qui m'excite, ce sont les "n-tile" !

 

En français, on appelle ça des quantiles, avec les exemples les plus connus : quartiles, déciles, centiles, ...

http://fr.wikipedia.org/wiki/Quantile

 

Exemple concret : en ces temps de crise, un pote partageait le lien suivant qui propose un certain nombre de statistiques sur l'assiduité des députés :

http://www.nosdeputes.fr/synthesetri/1

 

On intègre juste les données qui nous intéressent : les semaines d'activité

 

CREATE TABLE t (n number);

 

INSERT INTO t(n)
SELECT to_number(substr(lstval, case level when 1 then 0 else instr(lstval, ';', 1, level - 1) + 1 end, instr(lstval, ';',1 , level) - case level when 1 then 0 else instr(lstval, ';', 1, level - 1) end -1)) ext
FROM (
  SELECT
'42;40;40;39;39;39;39;39;39;39;39;39;38;38;38;38;38;38;38;38;38;38;38;37;37;37;37;37;37;37;37;37;37;37;37;37;37;36;'||
'36;36;36;36;36;36;36;36;36;36;36;36;36;36;36;36;36;36;36;36;36;36;36;35;35;35;35;35;35;35;35;35;35;35;35;35;35;35;'||
'35;34;34;34;34;34;34;34;34;34;34;34;34;34;34;34;34;34;34;34;34;34;34;34;34;34;34;34;34;34;34;34;34;34;34;33;33;33;'||
'33;33;33;33;33;33;33;33;33;33;33;33;33;33;33;33;33;33;33;33;33;33;33;33;33;33;33;33;33;33;33;33;32;32;32;32;32;32;'||
'32;32;32;32;32;32;32;32;32;32;32;32;32;32;32;32;32;32;32;32;32;32;32;32;32;32;32;32;32;32;32;32;32;32;32;32;32;31;'||
'31;31;31;31;31;31;31;31;31;31;31;31;31;31;31;31;31;31;31;31;31;31;31;31;31;31;31;31;31;31;31;31;30;30;30;30;30;30;'||
'30;30;30;30;30;30;30;30;30;30;30;30;30;30;30;30;30;30;30;30;30;30;30;30;30;30;29;29;29;29;29;29;29;29;29;29;29;29;'||
'29;29;29;29;29;29;29;29;29;29;29;29;29;29;29;29;29;29;29;29;29;28;28;28;28;28;28;28;28;28;28;28;28;28;28;28;28;28;'||
'28;28;28;28;28;28;28;28;28;28;28;28;28;28;28;28;28;28;28;28;28;28;28;28;28;28;27;27;27;27;27;27;27;27;27;27;27;27;'||
'27;27;27;27;27;27;27;27;27;27;27;27;27;27;27;26;26;26;26;26;26;26;26;26;26;26;26;26;26;26;26;26;26;26;26;26;26;26;'||
'26;26;26;26;26;26;26;26;26;25;25;25;25;25;25;25;25;25;25;25;25;25;25;25;25;25;25;25;24;24;24;24;24;24;24;24;24;24;'||
'24;24;24;24;24;24;24;24;24;23;23;23;23;23;23;23;23;23;23;23;23;23;23;23;22;22;22;22;22;22;22;22;22;22;22;22;22;22;'||
'22;21;21;21;21;21;21;21;21;21;21;21;21;21;21;21;21;21;21;20;20;20;20;20;20;20;20;19;19;19;19;19;19;19;18;18;18;18;'||
'18;18;18;17;17;17;17;17;17;17;16;16;16;16;16;15;15;15;15;15;15;15;15;15;14;14;13;13;12;12;12;11;11;11;11;11;11;10;'||
'10;10;9;9;8;8;8;5;5;5;4;3;3;2;1;0;0;' lstval
FROM DUAL
)
CONNECT BY level <= length(lstval) - length(replace(lstval,';',''))

 

Puis découper en déciles :

 

SQL> SELECT buk * 10 as pct, min(n) as nlow, max(n) as nhigh
  2  FROM (
  3     SELECT n, NTILE(10) OVER(ORDER BY n) buk
  4     FROM t
  5  )
  6  GROUP BY buk
  7  ORDER BY buk;

       PCT       NLOW      NHIGH
---------- ---------- ----------
        10          0         18
        20         18         23
        30         23         26
        40         26         28
        50         28         29
        60         29         31
        70         31         32
        80         32         34
        90         34         36
       100         36         42

 

=> NTILE(x) OVER (ORDER BY cols) est une fonction analytique qui pour chaque ligne, donne le x-quantile auquel la ligne appartient, la population étant partitionnée sur les critères "cols"

=> On regroupe les valeurs par quantile pour obtenir les bornes, car c'est ce qui donne de la visibilité sur la répartition.

 

Pensez-y en payant votre soda surtaxé !

Par Pacman - Publié dans : SQL
Ecrire un commentaire - Voir les 0 commentaires
Vendredi 23 décembre 5 23 /12 /Déc 11:48

A mes débuts dans Oracle, je lisais plein de conneries sur l'optimisation.
Parmi les gros mythes, il y avait : "rebuild un index améliore le clustering factor".

C'est bien entendu absurde :
- Le clustering factor se calcule comme le nombre de changement de bloc sur les lignes de la table que tu effectues en parcourant séquentiellement les feuilles de l'index.
- Les feuilles sont triées par valeur de la clef, donc le fait de le recontruire ne change pas l'ordre des feuilles

Puis je me disais : peut-être que pour les valeurs de clef ex-aequo l'ordre d'insertion pourrait augmenter artificiellement le nombre de changement de blocs, permettant ainsi au rebuild de faire décroître le clustering factor.

Essayons !

CREATE TABLE t (i NUMBER, j NUMBER);

CREATE INDEX idxt ON t(i);

INSERT INTO t
SELECT level, level
FROM DUAL
CONNECT BY level <= 1000;

commit;

SELECT dbms_rowid.rowid_block_number(rowid) blk, count(*) cnt, min(i) mini, max(i) maxi
FROM t
GROUP BY dbms_rowid.rowid_block_number(rowid);

       BLK        CNT       MINI       MAXI
---------- ---------- ---------- ----------
   1452237        574          1        574
   1452238        426        575       1000

De i = 1 à 574, on est dans le premier bloc. Les autres sont dans le deuxième bloc.

Un petit coup de stats...

exec dbms_stats.gather_table_stats(null, 'T', cascade => true)

SELECT clustering_factor
FROM dba_indexes
WHERE index_name = 'IDXT';

CLUSTERING_FACTOR
-----------------
                2

... et ça fait bien 2, qui correspond au passage de i=574 à 575 où on passe au deuxième bloc.

Maintenant, on va séquentiellement updater i = 1 dans un bloc, puis dans l'autre.

UPDATE t
SET i = 1
WHERE i = 575;

commit;

exec dbms_stats.gather_table_stats(null, 'T', cascade => true)

SELECT clustering_factor
FROM dba_indexes
WHERE index_name = 'IDXT';

CLUSTERING_FACTOR
-----------------
                4
 

 

=> Lors du parcours de l'index, pour i = 1, on switche sur le deuxième bloc, puis on revient au premier bloc pour i = 2 : ça augmente donc le clustering factor de 2
Jusque là, pas de surprise :)

UPDATE t
SET i = 1
WHERE i = 2;

commit;

exec dbms_stats.gather_table_stats(null, 'T', cascade => true)

SELECT clustering_factor
FROM dba_indexes
WHERE index_name = 'IDXT';

CLUSTERING_FACTOR
-----------------
                4

Cette fois, je me serais dit que j'insère une feuille i = 1 après celles qui existent, et donc je devrais ajouter 2 au clustering factor... mais non !

SELECT /*+INDEX(t idxt)*/ i, dbms_rowid.rowid_block_number(rowid)
FROM t
WHERE i = 1;

        I DBMS_ROWID.ROWID_BLOCK_NUMBER(ROWID)
--------- ------------------------------------
        1                              1452237
        1                              1452237
        1                              1452238
        1                              1452238

En parcourant l'index, on voit (même si cette requête n'est pas vraiment une preuve fiable) que les blocs sont bien lus "ensemble", et non pèle-mêle !

Ah ben ouais, les feuilles sont triées sur le "couple" (clefs, rowid) !

http://docs.oracle.com/cd/B19306_01/server.102/b14220/schema.htm#i5671

Leaf Blocks
All leaf blocks are at the same depth from the root branch block. Leaf blocks store the following:
    *      The complete key value for every row
    *      ROWIDs of the table rows
All key and ROWID pairs are linked to their left and right siblings. They are sorted by (key, ROWID).

La vie est belle... Bonne dinde à tous !

Par Pacman - Publié dans : SGBD
Ecrire un commentaire - Voir les 0 commentaires
Vendredi 28 octobre 5 28 /10 /Oct 16:23

Die Softwareentwickler mögen Schleife und Cursors über alles, und finden desshalb die unwahrscheinlichsten Rechtfertigungen um die globale DML query zu vermeiden.

Unter denen gibt's die Handlung der Fehlerfällen :

"Neee, die ganze Anfrage darf nicht Rollbacked werden, nur wegen einer verdammten Zeile"

 

Aber seit 10gR2 gilt dieses Argument (fast) nicht mehr, denn es gibt jetzt die ERROR LOGGING !

 

Versuchen wir's mal mit verschiedenen Fehler Gründen...

 

1) Die Testtabelle :


 CREATE TABLE testlog (

 id NUMBER UNIQUE,

 n NUMBER CHECK(n > 0),

 c VARCHAR2(10) NOT NULL);

 

2) Die Errorlogtabelle :

 

EXEC dbms_errlog.create_error_log('TESTLOG', 'TESTERR')

 

3) Einfügen

 

SQL> INSERT INTO testlog

  2  SELECT CASE level WHEN 2 THEN 1 ELSE level END, --die zweite Zeile wird unique contraint verletzen

  3         CASE level WHEN 4 THEN -1 ELSE level END, --die vierte verletzt die positiv check bedingung

  4         CASE level WHEN 6 THEN null WHEN 8 THEN lpad('x', 12, 'x') ELSE 'd' END --die sechte die NOT NULL bedingung und die achte überschreitet die maximale Länge

  5  FROM DUAL

  6  CONNECT BY level <= 12

  7  LOG ERRORS INTO TESTERR ('instest') REJECT LIMIT UNLIMITED;

 

8 ligne(s) créée(s).

 

SQL> SELECT *

  2  FROM testlog

  3  /

 

  ID    N C

---- ---- --------------------

   1    1 d

   3    3 d

   5    5 d

   7    7 d

   9    9 d

  10   10 d

  11   11 d

  12   12 d

 

=> 8 der 12 zeilen sind erfolgreich hinzufügen worden !

 

SQL> SELECT * FROM testerr;


  PrtScr capture 3(1)

 

Und natürlich klappt  es auch mit UPDATE, MERGE UND DELETE !

 

SQL> MERGE INTO testlog a

  2  USING (SELECT level l,

  3             CASE level WHEN 4 THEN -1 ELSE level + 1 END n,

  4             CASE level WHEN 5 THEN null ELSE 'c' END c

  5         FROM DUAL CONNECT BY LEVEL <= 13) t

  6  ON (a.id = t.l)

  7  WHEN MATCHED THEN UPDATE SET a.n = t.n, a.c = t.c

  8  WHEN NOT MATCHED THEN INSERT (a.id, a.n, a.c) VALUES(t.l, t.n, t.c)

  9  LOG ERRORS INTO TESTERR('testmerge') REJECT LIMIT UNLIMITED;

 

11 lignes fusionnées.

 

SQL> SELECT * FROM testlog;

 

  ID    N C

---- ---- --------------------

   1    2 c

   3    4 c

   5    5 d

   7    8 c

   9   10 c

  10   11 c

  11   12 c

  12   13 c

   2    3 c

   6    7 c

   8    9 c

  13   14 c

 

SQL> SELECT ORA_ERR_MESG$, ORA_ERR_ROWID$, ORA_ERR_TAG$

  2  FROM testerr;

 

PrtScr-capture_3-1--copie-1.jpg

 

 

4) Trotzdem ein Paar Schertze

 

Manchmal werden die unique contraints nur "am Ende" überprüft : in diese Fälle scheitert die ganze Anfrage.

Zum beispiel den direct path load :

 

SQL> INSERT INTO testlog

  2  SELECT /*+APPEND*/ CASE level WHEN 10 THEN 1 ELSE level END, level, 'c'

  3  FROM DUAL

  4  CONNECT BY level <= 10

  5  LOG ERRORS INTO testerr ('rarghl') REJECT LIMIT UNLIMITED;

INSERT INTO testlog

*

ERREUR à la ligne 1 :

ORA-00001: unique constraint (EDGE_ADM.SYS_C00356572) violated

 

Ohne den append hint klappt's prima :

 

SQL> INSERT INTO testlog

  2  SELECT CASE level WHEN 10 THEN 1 ELSE level END, level, 'c'

  3  FROM DUAL

  4  CONNECT BY level <= 10

  5  LOG ERRORS INTO testerr ('rarghl') REJECT LIMIT UNLIMITED;

 

1 ligne créée.

Par Pacman - Publié dans : SQL
Ecrire un commentaire - Voir les 0 commentaires

Catégories

Recherche

Calendrier

Mai 2013
L M M J V S D
    1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30 31    
<< < > >>

Présentation

  • : Le blog de Pacman
  • Le blog de Pacman
  • : Yet Another Stupid Oracle Blog
  • Contact
Créer un blog gratuit sur over-blog.com - Contact - C.G.U. - Rémunération en droits d'auteur - Signaler un abus - Articles les plus commentés