11 mars 2014

SQL : Gestion d'erreur

Un petit exemple de gestion d'erreur dans SQL SERVER quand on a des procédures qui appellent des procédures !
Deux procédures pour l'exemple :

CREATE PROCEDURE PLANTE
AS
BEGIN
 PRINT 'Je plante de temps en temps...';
 IF (RAND() > 0.6)
   PRINT 1/0;
 ELSE
   PRINT 'Pas cette fois !';
END
;

CREATE PROCEDURE Test_Erreur
AS
BEGIN
  BEGIN TRY
    PRINT 'Travail en cours...';
    EXEC PLANTE;
    IF (RAND() > 0.6)
      PRINT 1/0;
    ELSE
      PRINT 'Pas cette fois !';
    END
    PRINT 'Fini !';
  END TRY
  BEGIN CATCH
   DECLARE @msg VARCHAR(MAX), @proc VARCHAR(200);
   DECLARE @sev INT, @state INT;
   SET @proc = ERROR_PROCEDURE();

   SELECT @msg = 'Erreur' + CASE WHEN @proc = 'Test_Erreur' THEN '' ELSE ' dans ' + @PROC END + ' Ligne ' + CONVERT(VARCHAR(10), ERROR_LINE()) + ' : ' + ERROR_MESSAGE()
     , @sev = ERROR_SEVERITY(), @state = ERROR_STATE();

   RAISERROR (@msg, @sev, @state);
  END CATCH

END

Lorsqu'on lance la procédure Test_Erreur, Si erreur à lieu, le message indique s'il a eu lieu dans la procédure Test_Erreur ou dans PLANTE.

Les ingrédients intéressants :

  1. BEGIN TRY / END TRY / BEGIN CATCH / END CATCH : Qui permettent de déclarer un bloc de code qui sera en mesure d’attraper les erreurs.
  2. Le contenu du bloc Catch : Qui construit un message personnalisé en fonction d'ou est apparue l'erreur :
Notez l'utilisation des procédures :
  •  ERROR_PROCEDURE() Qui renvoie le nom de la procédure en erreur.
  •  ERROR_LINE() : Qui renvoie le numéro de la ligne qui a généré l'erreur dans la procédure.
  •  ERROR_MESSAGE() : Qui renvoie le message d'erreur
  •  ERROR_SEVERITY() : Qui renvoie le niveau ce criticité de l'erreur
  •  ERROR_STATE() : Qui renvoie le statut de l'erreur.

L'erreur est ensuite propagée à l'extérieur avec un RAISERROR qui reprend les informations de l'erreur originale (sévérité et statut) et un message adaptée à l'erreur.


31 janvier 2014

SQL : Identity dans les tables temporaires

Une Astuce de DOMINIQUE : que je reprend ici en "l'état"


Désolé pour ceux qui peuvent connaitre, mais je viens de le découvrir et ça me manquait notamment pour bosser avec des tables temporaires...

Problème :

  Lors de la création d'un table par
SELECT (...) INTO #foo

  #foo n'a pas d'id créé automatiquement.

Solution :

 SELECT IDENTITY(INT, 1, 1) AS foo_id, (...) INTO #foo

c# Améliorer la visualisation d'une classe dans le debugger

Afin d'améliorer la vision d'un objet dans le débogueur VISUAL STUDIO, il existe un attribut qui peut s'avérer bien pratique : DebuggerDisplay qui prend en paramètre une chaîne de formatage des propriétés de l'objet à afficher.

Exemple :
  [DebuggerDisplay("Rubrique ={Code} = {Valeur}")]
  public class FicheSimple
  {
    /// <summary>
    /// Initialise une nouvelle instance de la classe <see cref="FicheSimple"/>
    /// </summary>
    /// <param name="rub">Le code de la rubrique</param>
    public FicheSimple(string rub)
    {
      this.Code = rub.ToLower();
      this.Valeur = "";
    }

    #region Properties
    /// <summary>
    /// Le code de la rubrique (forcé en minuscule)
    /// </summary>
    public string Code { get; private set; }

    /// <summary>
    /// La valeur mémorisée
    /// </summary>
    public string Valeur { get; set; }
    #endregion
  }

La chaîne de format, prend entre accolades les propriétés à afficher.
Cela peut être pratique pour certain débogage un peu longs.

Ah : Penser à ajouter le bon using : using System.Diagnostics;


Voir la doc pour plus de détails ;-)

Enjoy

16 janvier 2014

Afficher en mode console un compteurs de progression

Pour debugger des programmes de type Batch, on utilise souvent la console. Quand on veut savoir ou en est un programme cela peut être utile. Quand ce programme boucle, afficher la progression ligne à ligne devient vite illisible.

Voici un exemple c# de code pour mettre en place un compteur de type : Traitement en cours 44 sur 123 et de faire bouger la valeur sans "polluer" l'affichage de la console.

Tout l'intérêt du truc est dans la méthode et les propriétés suivantes de l'objet Console :

  •    Console.SetCursorPosition(x, y) : Place le curseur en X, Y sur la console
  •   Console.CursorLeft : Renvoie la position X du curseur de la console
  •   Console.CursorTop : Renvoie la position Y du curseur de la console

Pour que cela fonctionne bien il faut s'assurer que la console ne scrolle pas = que l'affichage ne dépasse pas le nombre de lignes du buffer de la console. Dans ce cas, une solution est d'effacer la console avant d'utiliser le truc. Avec ce bout de code le tour est joué :
if (Console.CursorTop > Console.BufferHeight)
{
  Console.Clear();
}


Enjoy

 using System;

namespace ConsoleApplication1
{
  /// <summary>
  /// Programme de démo : pour afficher en mode console un ou plusieurs compteurs
  /// </summary>
  public class Program
  {
    /// <summary>
    /// Point d'entré du programme
    /// </summary>
    /// <param name="args">Les arguments de la ligne de commande</param>
    public static void Main(string[] args)
    {
      int max = 100000;
      Console.WriteLine("Programme de tests : pour afficher en mode console un ou plusieurs compteurs");
      Console.WriteLine();
      InfoCompteur pt = PrintCompteur("En cours : ", 0, max);
      Console.WriteLine();
      InfoCompteur pt2 = PrintCompteur("Traité : ", 0, max);
      Console.WriteLine();

      for (int i = 1; i <= max; i++)
      {
        PrintXY(pt, i);
        PrintXY(pt2, i);
       
        if ((i % 5000) == 0)
        { // Afficher du texte arbitrairement tout au long de la boucle
          Console.WriteLine("Coucou " + i.ToString());
        }
      }

      Console.WriteLine("--- Appuyez sur une touche pour quitter ---");
      Console.ReadKey();
    }

    /// <summary>
    /// Démarre un compteur de type : [txt] xxx sur yyy
    /// </summary>
    /// <param name="txt">Texte à afficher avant le compteur</param>
    /// <param name="value">Valeur initiale du compteur</param>
    /// <param name="count">Nombre d'éléments total</param>
    /// <returns>Les informations sur le compteur en cours à utiliser pour les prochains appels</returns>
    private static InfoCompteur PrintCompteur(string txt, int value, int count)
    {
      Console.Write(txt);
      int x = Console.CursorLeft;
      int y = Console.CursorTop;
      int le = count.ToString().Length;
      string fmt = string.Format("{{0,{0}}}", le);
      Console.Write(fmt, value);
      Console.WriteLine(" sur " + count.ToString());
      return new InfoCompteur(x, y, le);
    }

    /// <summary>
    /// Continue = affiche une nouvelle valeur pour un compteur de type : [txt] xxx sur yyy
    /// </summary>
    /// <param name="p">Les informations sur le compteur en cours</param>
    /// <param name="value">La nouvelle valeur</param>
    private static void PrintXY(InfoCompteur p, int value)
    {
      int x = Console.CursorLeft;
      int y = Console.CursorTop;

      Console.SetCursorPosition(p.X, p.Y);
      string fmt = string.Format("{{0,{0}}}", p.Size);
      Console.WriteLine(fmt, value);
      Console.SetCursorPosition(x, y);
    }

    /// <summary>
    /// Infos sur un compteur
    /// </summary>
    private class InfoCompteur
    {
      /// <summary>
      /// Initialise une nouvelle instance de la classe <see cref="InfoCompteur" />.
      /// </summary>
      /// <param name="x">Coordonnée X du curseur</param>
      /// <param name="y">Coordonnée Y du curseur</param>
      /// <param name="size">Taille du champ compteur</param>
      public InfoCompteur(int x, int y, int size)
      {
        this.X = x;
        this.Y = y;
        this.Size = size;
      }

      /// <summary>
      /// Coordonnée X du curseur
      /// </summary>
      public int X { get; set; }

      /// <summary>
      /// Coordonnée Y du curseur
      /// </summary>
      public int Y { get; set; }

      /// <summary>
      /// Taille du champ compteur
      /// </summary>
      public int Size { get; set; }
    }
  }
}

14 décembre 2013

HashCode indépendant de la plateforme 32 / 64 bit et du FrameWork

Vous savez que les hashCode du .Net Framework sont dépendant de la plateforme 32 ou 64 bit et de la version du .Net Framework.

Après s'être fait avoir, on cherche une solution :

Celle Proposée sur ce CodeProject me semble intéressante à Tester "Convert String to 64Bit Integer"

Ci dessous un programme de test, les résultats doivent être comparés sur plusieurs machines 32 & 64 bit avec plusieurs versions du framework (3.5 et plus; en dessous ça compile plus)

using System;
using System.Text;

namespace ConsoleApplication1
{
  /// <summary>
  /// Test program
  /// </summary>
  public class Program
  {
    /// <summary>
    /// Programm de tests
    /// </summary>
    /// <param name="args">arguments de la ligne de commande</param>
    public static void Main(string[] args)
    {
      Print("Hello world");
      Print("Hello world2");
      Print("456TY9=1");
      Print(@"Bonjour
je vais bien tout va bien");
      Print("1");
      Print("2");
      Print(string.Empty);
      Print(null);
      Print("123");

      Console.WriteLine();
      Console.WriteLine("-- press a key to quit --");
      Console.ReadKey();
    }

    /// <summary>
    /// Renvoie un code unique en fonction de la chaine fournie
    /// </summary>
    /// <param name="strText">LA chaine à encoder</param>
    /// <returns>Le hash code</returns>
    private static long GetInt64HashCode(string strText)
    {
      long hashCode = 0;
      if (!string.IsNullOrEmpty(strText))
      { // Unicode Encode Covering all characterset
        byte[] byteContents = Encoding.Unicode.GetBytes(strText);
        System.Security.Cryptography.SHA256 hash = new System.Security.Cryptography.SHA256CryptoServiceProvider();
        byte[] hashText = hash.ComputeHash(byteContents);

        // 32Byte hashText separate
        // hashCodeStart = 0~7  8Byte
        // hashCodeMedium = 8~23  8Byte
        // hashCodeEnd = 24~31  8Byte
        // and Fold
        long hashCodeStart = BitConverter.ToInt64(hashText, 0);
        long hashCodeMedium = BitConverter.ToInt64(hashText, 8);
        long hashCodeEnd = BitConverter.ToInt64(hashText, 24);
        hashCode = hashCodeStart ^ hashCodeMedium ^ hashCodeEnd;
      }

      return hashCode;
    }

    /// <summary>
    /// Pour afficher un hash et sons texte
    /// </summary>
    /// <param name="msg">Le texte à afficher</param>
    private static void Print(string msg)
    {
      Console.WriteLine("{0} : {1}", GetInt64HashCode(msg), msg);
    }
  }
}





Résultats des tests sur ma machine Win8.1 32Bits
V4.5

6918273045165231812 : Hello world
8573843817085531220 : Hello world2
1902744953957170490 : 456TY9=1
7665625602362524172 : Bonjour
je vais bien tout va bien
-207971209471392083 : 1
-4968569628414376572 : 2
0 :
0 :
-3845492681683628692 : 123



V4.0

6918273045165231812 : Hello world
8573843817085531220 : Hello world2
1902744953957170490 : 456TY9=1
7665625602362524172 : Bonjour
je vais bien tout va bien
-207971209471392083 : 1
-4968569628414376572 : 2
0 :
0 :
-3845492681683628692 : 123


V3.5

6918273045165231812 : Hello world
8573843817085531220 : Hello world2
1902744953957170490 : 456TY9=1
7665625602362524172 : Bonjour
je vais bien tout va bien
-207971209471392083 : 1
-4968569628414376572 : 2
0 :
0 :
-3845492681683628692 : 123


V3.0
Compile plus !!


Testé sur une machine 64 bits : Mêmes résultats !!
Pour moi cela fonctionne.

05 décembre 2013

Les littéraux C#

EN C# pour écrire un littéral de type decimal il faut ajouter un M majuscule.

Exemple :
decimal v = 3M;


Pour le type double est un D

Exemple :
double d = 5D;


Enjoy

20 novembre 2013

SQL : Changer l'ordre d'éléments trié (avec des trous)

Mon besoin :
Je pars d'une liste d'éléments triés, mais je n'en n'affiche qu'une partie (les visibles) je veux faire monter ou descendre un élément dans la liste sans perdre l'ordre avec ceux non affichés.

Un exemple complet en SQL :

CREATE TABLE dbo.trimoi (
     tri int not null,
     nom varchar(50) not null,
     visible bit not null)

La colonne tri sert a trier la liste, la colonne nom identifie un élément de la liste et la colonne visible simule mon filtre d'affichage des éléments.

Un petit jeu de tests :
INSERT INTO dbo.trimoi (tri, nom, visible) 
     VALUES (1, 'Trois', 1),
            (2, 'second', 0),
            (3, 'premier', 1),
            (4, 'quatre', 1),
            (5, 'cinq', 1)

Les données en liste filtrée donnent :
SELECT * FROM dbo.trimoi WHERE visible = 1 ORDER BY tri

donne :
tri nom visible
1 Trois 1
3 premier 1
4 quatre 1
5 cinq 1

Imaginons que je veuille "monter" l'élément cinq d'un cran. Dans ce cas, il doit passer avant quatre.

Voici la procédure que je propose pour "monter un élément"
DECLARE @nom VARCHAR(50) = 'cinq'

DECLARE @pos INT
SELECT TOP 1 @pos = tri FROM dbo.trimoi WHERE visible = 1 
                                          AND tri < (SELECT tri FROM dbo.trimoi WHERE nom = @nom) ORDER BY tri DESC

IF (@pos IS NOT NULL)
BEGIN
  UPDATE dbo.trimoi SET tri = tri + 1 
  FROM dbo.trimoi
  WHERE tri >= @pos
    AND tri < (SELECT tri FROM dbo.trimoi WHERE nom = @nom)

  UPDATE dbo.trimoi SET tri = @pos WHERE nom = @nom
END 

L'exécution donne le bon résultat.
Si l'on réitère plusieurs fois l'exécution l'élément cinq monte progressivement, en sautant bien l'élément qui est masqué : second.

Quelques commentaires :
Le premier SELECT permet de trouver et d'affecter à la variable @pos la position finale de l'élément à monter.
On applique dans la requête le filtre qui nous intéresse (ici : visible = 1) ainsi que la recherche de l'élément tri le plus grand en dessous de l'élément 'cinq' (TOP 1 + ORDER BY tri DESC)

Si on a trouvé une position (cas ou cinq est en tête de liste) alors on fait les 2 updates :
1) Ajouter 1 à la colonne tri de tous les éléments entre @pos inclus et la position de l'élément cinq
2) La colonne tri de l'élément cinq prend la position @pos.

Pour être complet voici la procédure qui permet de redescendre 'cinq'
DECLARE @nomDesc VARCHAR(50) = 'cinq'

DECLARE @posDesc INT
SELECT TOP 1 @posDesc = tri FROM dbo.trimoi WHERE visible = 1 
                                              AND tri > (SELECT tri FROM dbo.trimoi WHERE nom = @nomDesc) ORDER BY tri ASC

IF (@posDesc IS NOT NULL)
BEGIN
  UPDATE dbo.trimoi SET tri = tri - 1 
  FROM dbo.trimoi
  WHERE tri <= @posDesc
    AND tri > (SELECT tri FROM dbo.trimoi WHERE nom = @nomDesc)

  UPDATE dbo.trimoi SET tri = @posDesc WHERE nom = @nomDesc
END 

Enjoy