vendredi 25 juin 2010

Common Table Expression (CTE)

Les CTE (common Table Expression) peuvent être vu comme des results set temporaires.
Les CTE sont plus facile à utiliser que les tables temporaires et permettent une utilisation récursive (voir article Recursive Queries using CTE sur MSDN).
La durée de vie d'une CTE est limitée à l'exécution de la requête.
Pour plus d'information, voir l'article Using Common Table Expressions sur MSDN.

Les CTE deviennent utiles lorsque l'on doit appliquer une where clause sur un champ du query dont la valeur est non deterministe.
A titre d'exemple, un query dont le champs txt varchar(80) est calculé via une fonction utilisant un curseur sql (cela peut arriver si l'on doit faire des manipulations complexes sur les données pour composer le texte).
Si l'on désire n'afficher que lignes ayant plus d'un caractère, un select standard ne peut pas convenir.

Exemple:

En déclarant la fonction suivante (utilisée dans l'exemple ci-après):
Create function dbo.ChangeText( @txtIn varchar(50) )
returns Varchar(50) 
AS
BEGIN
  Return @TxtIn + '~Test~'
END

Il n'est pas possible d'exécuté la requête suivante:
select NameFr, dbo.ChangeText( NameFr ) as txt
FROM Apb.COUNTRY
where LEN( txt ) > 1

Erreur Sql:
Msg 207, Level 16, State 1, Line 3
Invalid column name 'txt'.

Par contre, l'usage d'un Common Table Expression d'atteindre l'objectif en stockant temporairement le dataset dans cte_Names.
Par la suite, le contenu de cte_Names est utilisé pour atteindre le résultat souhaité (à l'aide d'une seconde requête sql).
with cte_Names ( NameFr, txt )
AS (
   select NameFr, dbo.ChangeText( NameFr )
   FROM Apb.COUNTRY)
  
select NameFr, txt
FROM cte_Names
where LEN( txt ) > 1

Plus d'information

vendredi 18 juin 2010

Documentation C#: LinQ, Serialization, Generics et File Processing

Bien que LinQ soit déjà utilisé dans les exemples C# que j'ai publié, rien ne faut une excellente référence avec exemples concrets.
C'est ainsi qu'en plus d'un préambule sommaire sur LinQ,  l'on apprendra à utiliser plusieurs listes, à les ordonnés, à utiliser les prédicats, à recréer de nouvelles listes (grâce aux types anonymes), etc.

C# - Lesson 36 Querying a List disponible sur FunctionX.

Ce site propose d'autres articles intéressants:

Noces de mariage

Voici une liste des noces de mariage assez complète... au moins il y a du plaisir pour les premières années si on ne manque pas d'inspiration.


Années de mariage - Noces de
1:    Coton
2:    Cuir
3:    Froment
4:    Cire
5:    Bois
6:    Cypre
7:    Laine
8:    Coquelicot
9:    Faïence
10:    Etain
11:    Corail
12:    Soie
13:    Muguet
14:    Plomb
15:    Cristal
16:    Saphir
17:    Rose
18:    Turquoise
19:    Cretonne
20:    Porcelaine
21:    Opale
22:    Bronze
23:    Béryl
24:    Satin
25:    Argent
26:    Jade
27:    Acajou
28:    Nickel
29:    Velours
30:    Perle
31:    Basane
32:    Cuivre
33:    Porphyre
34:    Ambre
35:    Rubis
36:    Mousseline
37:    Papier
38:    Mercure
39:    Crêpe
40:    Emeraude
41:    Fer
42:    Nacre
43:    Flanelle
44:    Topaze
45:    Vermeil
46:    Lavande
47:    Cachemire
48:    Améthyste
49:    Cèdre
50:    Or
60:    Diamant
70:    Platine
75:    Albâtre
80:    Chêne

Source: cet article sur "les noces de mariages"

samedi 29 mai 2010

Visual Studio: Blocage de fichier durant la compilation de solution

Tavailler avec plusieurs sessions de Visual Studio peut quelques fois se montrer pénible.
Il arrive, de temps à autre, qu'il devienne impossible de compiler une solution parce que un fichier est bloqué par un autre processus.

J'ai déjà eu souvent l'occasion de voir ce message d'erreur durant mes phases de compilation:
Unable to copy de file XXX. The process cannot access the file xxx because it is being used by another process.

Difficile de trouver de la documentation sur ce point, mais Keyvan Nayyeri apporte une réponse satisfaisante dans son article "File Lock Issue in Visual Studio When Building a Project".

Cette erreur se produit lorsque Visual Studio rencontre un fichier ".locked" qu'il ne peut pas effacer car il est détenu par une autre instance de Visual Studio.
Dans ce cas, il ne peut produire un nouvel assembly (d'ou l'erreur).
Cela peut arriver assez facilement si l'on travaille sur de très gros projet... dans mon cas, je soupçonne la compilation de la documentation du code (dans des fichier XML) d'être à l'origine de mon problème.

SqlServer - Cross Apply

A quoi sert Cross Apply
Cross Apply est une nouvelle fonctionnalité T-SQL apparue avec SQL 205 (si je ne me trompe pas).
Cross Apply est une alternative permettant de contourner les limitations du cross join.

En effet, il n'est pas possible d'écrire la requête suivante car A.Val est hors du scope de la requête principale.
select A.*, b.X
from A
cross join (select B.X from B where B.Val=A.Val) b -- Invalid!

La seule façon d'arriver au résultat attendu est de rejeter le test "B.Val=A.Val" hors du scope du cross join.
Mais cela à une lourde conséquence car le travail de jointure est très couteux en ressource.
select A.*, b.X
from A
cross join (select * from B) b
where A.Val = b.Val -- Correct!

 C'est là qu'intervient le Cross Apply car l'instruction permet de faire le test de jointure dans le scope du Cross Apply.
select A.*, b.X
from A
cross apply (select B.X from B where B.Val=A.Val) b



Exemples
-- Cross Apply avec une sous requête
select o.*, rs.RunningSum, rs.SameCode
from Order o
cross apply
 (
   select
     sum(Amount) as RunningSum,
     sum(case when p.OrderCode = o.OrderCode then Amount else 0 end) as SameCode
   from Order P
   where P.OrderDate <= O.OrderDate
 ) rs

-- Cross Apply pour retrouver l'élément précédent
select o.*, prev.*
from Order o
cross apply
 (
   select top 1 *
   from Order P where P.OrderDate < O.OrderDate
   order by OrderDate DESC
 ) prev

Lectures
  •  "Taking a look at CROSS APPLY" est un excellent article avec plein de cas pratiques du Cross Apply. Les exemples sont d'ailleurs issus de cet article.
  • "Using CROSS APPLY" est un article de SqlTeam.
    Visiblement intéressant mais je n'ai pas poussé la lecture sur ce dernier. A noter qu'il utilise l'instruction ROW_NUMBER() sur les partitions SQL.

mercredi 26 mai 2010

LINQ: Placer 8 reines sur un échiquier (solution)

Faisant suite au précédent article concernant la résolution de puzzle et problèmes combinatoires, je me suis demandé comment résoudre le problème des 8 reines à l'aide de LinQ.

Le puzzle consiste à placer 8 reines sur un échiquier de telle façon qu'aucune d'entre-elles ne puisse être en position d'en capturer une autre.
Pour rappel, une reine peut se déplacer d'autant de case qu'elle le désire dans l'une des directions suivantes:

    * Horizontale
    * Verticale
    * Diagonales (les deux sens)


Pour résoudre le problème, il faut qu'une nouvelle reine à placer sur l'échiquier n'occupe pas une case sur la même ligne, même colonne, même diagonale qu'une des autres reines placées sur l'échiquier.

Principe de résolution
Pour résourdre ce problème il faut:
  1. Identifier, de façon simple, la position d'une reine à l'aide sur l'échiquier.
  2. Trouver un processus mathématique qui vérifie si deux reines sont sur une même ligne (ou colonne).
    Problème trivial.
  3. Trouve un processus mathématique qui vérifie si deux reines partages une des diagonales.
Pourquoi des processus mathématique?
Ayant la volonté de résoudre ce problème avec LinQ, l'utilisation de fonction mathématique correspond mieux à la philosophie de résolution (évitant ainsi l'utilisation de boucles/itérations ou tests en cascade).

Identification de la position d'une reine
Bien qu'il soit possible d'identifier la position d'une reine à l'aide d'une structure, cette option n'est pas très efficace car elle nécessite la manipulation d'une structure et la mise en place de double boucles, de vérifications plus poussées, etc.
Le code repris ci-dessous repris à titre de support ne sera donc pas utilisé.
struct QueenPos
{
    public int row; // 1 à 8
    public int column; // de 1 à 8
  
    public QueenPos( int iRow, int iCol ){
        row = iRow; column = iCol;
    }
};

public static void RunSnippet()
{
     QueenPos Queen1 = new QueenPos( 1, 5 );
}

Une alternative plus intéressante sur le plan mathématique étant la numérotation des cases (de 1 à 64) de l'échiquier. La position d'une reine est alors identifié à l'aide d'une valeur numérique UNIQUE de 1 à 64.
Puisqu'un échiquier est un carré de 8 par 8, il est possible de déduire la ligne et la colonne d'une reine à partir de sa position. Il est même possible de déduire la diagonale sur laquelle elle se trouve (mais on reparlera de cela plus loin).
Echiquer où chacune des cases est numérotée

Une reine placée en 3ième lignes, 4ième colonne portera la position 20.


Les positions possibles d'une reine (iQueenPos) peut donc être pris en charge par une simple boucle "For" ou une énumération de 1 à 64. Une approche idéale pour un traitement LinQ.

Position de la reine (ligne et colonne)
Puisque la position d'une reine(iQueenPos) varie de 1 à 64, il faut être capable de transformer cette valeur numérique en ligne et colonne.
Puisque l'échiquier est un carré parfait, la ligne et la colonne peuvent être retrouvé à l'aide de fonctions mathématiques.
/// <summary>
/// Return the Column where the Queen is placed
/// </summary>
/// <param name="QueenPos">The current position of the queen in the GameBoard (Cell 1 to 64)</param>
/// <returns>The column number. Value between 1 and 8.</returns>private int QueenCol(int QueenPos) {           
     return ((QueenPos - 1) % 8) + 1;
}

/// <summary>
/// Return the Row where the Queen is placed
/// </summary>
/// <param name="QueenPos">The current position of the queen in the GameBoard (Cell 1 to 64)</param>
/// <returns>The Row number. Value between 1 and 8.</returns>
private int QueenRow(int QueenPos)
{
    return ((QueenPos - 1) / 8) + 1;
}

Le test de ces fonctions produit le résultat suivant (avec le code de test repris ci-après).


// Code affichant le résultat des colonnes et lignes
// calculées pour chacune des positions possibles 
// d'une reine
//
//lst is a ListBoxControl
lst.Items.Add("--- Rows ------------------------");
for (int i = 1; i <= 64; i++)
{
    int iRow = QueenRow(i);
    s = s + iRow.ToString().PadLeft(2) + ",";
    if ((i % 8) == 0)
    {
        lst.Items.Add(s);
        s = "";
    }
}
lst.Items.Add(s);

lst.Items.Add("--- Columns --------------------");
for (int i = 1; i <= 64; i++)
{
    int iCol = QueenCol(i);

    s = s + iCol.ToString().PadLeft(2) + ",";
    if ((i % 8) == 0)
    {
        lst.Items.Add(s);
        s = "";
    }
}
lst.Items.Add(s);


Position de la reine (les diagonales)
Il est possible d'identifier la diagonale sur laquelle se trouve une reine à l'aide d'une simple opération mathématique.
/// <summary>
/// Identify the Diagonal (/) on which the Queen is Placed
/// </summary>
/// <param name="QueenPos">The current position of the queen in the GameBoard (Cell 1 to 64)</param>
/// <returns>The diagonal number on which the queen is placed. Each diagonal has a unique number.</returns></returns>private int QueenDiag(int QueenPos) {
    return QueenRow(QueenPos) + QueenCol(QueenPos);
}

/// <summary>
/// Identify the Anti-Diagonal (\) on which the Queen is Placed
/// </summary>
/// <param name="QueenPos">The current position of the queen in the GameBoard (Cell 1 to 64)</param>
/// <returns>The anti-diagonal number on which the queen is placed. Each anti-diagonal has a unique number.</returns></returns>
private int QueenAntiDiag(int QueenPos)
{
    return QueenRow(QueenPos) - QueenCol(QueenPos);       
}

En utilisant une code similaire à "position Lignes/Colonnes de la reine" il est possible d'évaluer la valeur des diagonales (et anti-diagonales) pour chacune des positions possible d'une reine.

Ainsi donc, les reines en positions 15 et 57 se gênent l'une l'autre puisqu'elles partagent la même diagonal (la diagonale n° 9).

Quand deux reines se gênent t-elles?
Le test devient alors relativement simple.
Deux reines (Reine 1 en position newQueenPos, Reine 2 en position otherQueenPos ) se gênent si elles partagent la même colonne ou la même ligne ou la même diagonale ou la même anti-diagonale.
Ce qui produit le code suivant (ainsi que la routine de test pour N reines):
/// <summary>
/// Test if 2 queens will be killing each oter because of their positions on the Chessgame
/// </summary>
/// <param name="NewQueenPos">Position of the new Queen placed on the game board (Position to be tested against another queen)</param>
/// <param name="OtherQueenPos">Position of an existing queen.</param>
/// <returns>True if the 2 Queens kill each other.</returns>private bool KillingPosition(int newQueenPos, int otherQueenPos)
{
    // 2 Queens cannot be on the same row
    int Q1Row = QueenRow( newQueenPos );
    int Q2Row = QueenRow( otherQueenPos );
    if( Q1Row == Q2Row)
        return true;

    // 2 Queens Cannot be on the same column    
    int Q1Col = QueenCol( newQueenPos );
    int Q2Col = QueenCol( otherQueenPos );
    if( Q1Col == Q2Col )
        return true;

    // 2 Queens Cannot be on the same "/" diagonal 
    //    ((Q1Col + Q1Row) == (Q2Col + Q2Row))
if( QueenDiag( newQueenPos ) == QueenDiag( otherQueenPos ) ) 
return true;

    // 2 Queens Cannot be on the same "\" diagonal
    //   ((Q1Row - Q1Col) == (Q2Row - Q2Col))
if( QueenAntiDiag( newQueenPos ) == QueenAntiDiag( otherQueenPos ) ) 
return true;

    // The 2 queens will not kill each other.
    return false;
}

/// <summary>
/// Test a new queen placed on the board to check if she is about to be killed by any of the other queens already placed on the GameBoard
/// </summary>
/// <param name="newQueenPos">The Position of the New Queen to be placed on GameBoard</param>
/// <param name="otherQueensPos">The position of all the other queens already placed on the GameBoard</param>
/// <returns>True if the new queen placed at newQueenPos is killed by an existing queen placed on the GameBoard</returns>
private bool KillingPosition(int newQueenPos, int[] otherQueensPos) {
    foreach (int queenOnGameBoard in otherQueensPos)
        if( KillingPosition( newQueenPos, queenOnGameBoard ) )
            return true;
    return false;

}

Résolution du poblème?
Armé de tout ces éléments il est maintenant possible de résoudre ce problème à l'aide de LinQ.
var Solutions = 
    from Q1 in Enumerable.Range( 1, 64 )
    from Q2 in Enumerable.Range( 1, 64 ).Where( q2Value => !KillingPosition( q2Value, Q1 ) )
    from Q3 in Enumerable.Range(1, 64).Where(q3Value => !KillingPosition(q3Value, new int[] {Q1, Q2} ))
    from Q4 in Enumerable.Range(1, 64).Where(q4Value => !KillingPosition(q4Value, new int[] { Q1, Q2, Q3 }))
    from Q5 in Enumerable.Range(1, 64).Where(q5Value => !KillingPosition(q5Value, new int[] { Q1, Q2, Q3, Q4 }))
    from Q6 in Enumerable.Range(1, 64).Where(q6Value => !KillingPosition(q6Value, new int[] { Q1, Q2, Q3, Q4, Q5 }))
    from Q7 in Enumerable.Range(1, 64).Where(q7Value => !KillingPosition(q7Value, new int[] { Q1, Q2, Q3, Q4, Q5, Q6 }))
    from Q8 in Enumerable.Range(1, 64)
    where !KillingPosition( Q8, new int[]{Q1, Q2, Q3, Q4, Q5, Q6, Q7})
    select new int[]{ Q1, Q2, Q3, Q4, Q5, Q6, Q7, Q8 };

Par contre, contrairement à ce que je pensais, il n'y a pas que quelques solutions mes plusieurs dizaines de milliers (sans distinction des doublons).
J'ai donc interrompu l'exécution après 87.000 solutions et bricolé à la va-vite un petit code d'affichage.
A préciser, quand même, que je ne teste pas les doublons.
// LinQ expression (déjà présenté ci-dessus)
var Solutions = 
    from Q1 in Enumerable.Range( 1, 64 )
 ... ;

int iSolutionCount = 0;
// lst is a ListBoxControl display control
//   Each Solution is composed of an Array of int
foreach( var Solution in Solutions ){
    iSolutionCount++;
    // Display a solution every 500 solutions
    if (iSolutionCount > 10 )
        break;

    lst.Items.Add( String.Format("--- Solution #{0}", iSolutionCount ) );
    // Display the positsion of Queens
    foreach( int QueenPos in Solution.ToList() ){
        lst.Items.Add( String.Format( "   R: {0}, C: {1}. Cell: {2}", QueenRow( QueenPos ), QueenCol( QueenPos ), QueenPos ) );
    }
    // Display in a ChessBoard
    lst.Items.Add("Queens on GameBoard:");
    for (int row = 1; row <= 8; row++)
    {
        string s = row.ToString().PadLeft(2) + ": ";
        for (int col = 1; col <= 8; col++)
        {
            int iCurrentCell = (row-1)*8+col;
            if (Solution.Contains(iCurrentCell))
                s = s + " Q,";
            else
                s = s + "  ,";
        }
        lst.Items.Add(s);

    }
}

Il est intéressant de noter que le code d'affichage des solutions est plus long que l'expression LinQ permettant de les identifier :-)

mardi 25 mai 2010

Nouveauté du Débugger de Visual Studio 2010

Très chouette article de ScottGu's a propos des nouveautés apparues dans le Debugger de VS2010.
Hormis le "Pinned Data Tips" qui permet de garder visible les "Data Tips" durant une session de debbuging (Plus sympa que les watchs), je trouve particulièrement intéressant que ces informations soient disponibles après la session de debugging (la dernière valeur connue est consultable depuis l'éditeur après la session de débug :-) ).
Plus intéressant encore cette possibilité de sauvegarder les "Pinned Data Tips" (ex: pour les attacher à un Bug Report ou les passer à un collègue).

A noter également, la possibilité de regrouper des BreakPoints sous un même libellé.
Cela permet le filtrage et l'activation/désactivation rapide d'un groupe entier de BreakPoint.
Tout aussi intéressant que pour les "pinned data tips", il est également possible de sauver une liste de breakpoints dans un fichier xml. Option bien pratique pour documenter un bug report ou passer facilement le relai à un collègue.