vendredi 30 janvier 2015

Pourquoi opter pour le développement informatique en offshore ?

Il va de soi que la principale raison est d’ordre financier. 

En France, même en faisant appel à un freelance – ce qui suppose l’absence de charges salariales – la facture est toujours salée.

Mais au-delà de la considération financière, il y a aussi la recherche de compétences et de spécialités. Nous « baignons » au milieu d’outils technologiques performants, eux ont fait table rase de la notion même de frontière.

Quelques chiffres à titre de comparatif de prix

Ceci est une ébauche de comparatif des prix d’une prestation de développement informatique. On notera surtout qu’en France, les prix varient considérablement en fonction des villes et des régions, Paris étant toujours au sommet du podium.

En embauchant un développeur débutant, une entreprise devra débourser au minimum 200 € par jour de salaire. À cela s’ajoutent les charges sociales et fiscales.

Dans certains domaines, le recours à un freelance est une option rentable. Le domaine de l’informatique, et surtout du développement, échappe à cette réalité.

En moyenne, un développeur français facture sa prestation entre 300 et 500 € par jour. Dans certaines régions, les prix peuvent descendre en dessous de 300 €. Il faut aussi savoir que les tarifs dépendent du diplôme et de l’expérience.

En revanche, un développeur offshore facture sa prestation en moyenne à 140 € par jour. Ça descend à 70-80 € par jour dans certains pays asiatiques et africains notamment.

La
création de site internet pas cher, c’est bien beau. Mais qu’en est-il de la qualité du résultat ?

Le développeur et le client sont coresponsables de la qualité du résultat

On sait tous que le développeur offshore doit respecter à la lettre les consignes du cahier des charges. Il déploiera son expérience pour exploiter les meilleurs outils adéquats aux attentes de ses clients.

Mais le développeur offshore n’est pas à blâmer si les consignes du cahier de charges présentent des ambigüités. Le premier responsable de la qualité du résultat, c’est le client lui-même.

Élaborer un cahier des charges est assez complexe. Le client doit indiquer toutes les contraintes avec lesquelles le développeur devra travailler.

Ces contraintes, ce sont la technologie, le design, la durée de la prestation, les fonctionnalités à inclure et tout un tas de paramètres techniques. Bref, le cahier des charges est un document qui se doit d’être très précis.

L’indécision du cahier de charge aura un effet boomerang contre le client lui-même. Car le développeur ne considérera que les informations qu’il contient, et fera le travail de la manière qu’il juge la plus adaptée ou la plus rapide.

L’offshoring, c’est une affaire financièrement intéressante. Mais l’entreprise doit scruter au préalable la meilleure destination offshore pour une prestation donnée.

Par exemple, l’Afrique francophone du Maghreb, Madagascar et Maurice sont parmi les destinations prisées des Français pour la
rédaction web offshore. En revanche, il n’est pas rare que les connaisseurs de l’offshoring conseillent aux Français de se tourner plus vers l’Asie et l’Europe de l’Est.
  

vendredi 23 janvier 2015

Conversion Pixels / Inches

Une image / photo peut être affichée, scannée ou imprimée.

La taille réelle de cette image / photo va sensiblement varier selon le <device> utilisé.

L'unité utilisée (pixels, inches) pour exprimer la taille de l'image va aussi dépendre du <device> sur lequel l'image sera traitée.

En tant que développeur nous devons quelque fois faire des conversions sur des images (surtout si nous devons "propager" ces images sur différents <devices>) afin d'obtenir le meilleur rendu possible.

Vous trouverez cet outil très pratique :

Cliquez ici

Convertisseur décimal, hexadécimal, binaire

Qui n'a pas eu besoin un jour de faire une conversion en décimale, binaire ou hexadécimale ?

En tant que développeur, nous nous sommes tous confrontés à ce problème.

Voici un outil très utile.



jeudi 22 janvier 2015

Formater votre SQL !

Voici une astuce pour afficher du SQL correctement indenté, bref bien formaté.

Cliquez ici

Une fois votre code SQL saisi, cliquez sur le bouton <Format SQL>.

Votre code SQL est affiché dans une fenêtre parfaitement formaté, prêt à l'emploi.

Vous avez aussi la possibilité de copier le SQL formaté dans le presse-papier ou de l'imprimer.

Une astuce bien pratique.

mercredi 21 janvier 2015

JavaFX / ANT / MSI : le trio gagnant !

Pour pouvoir générer un fichier MSI (Windows Installer), il faut d'abord installer Wix Toolset.

Wix est un ensemble d'outils qui permettent de créer le <Windows Installer> de votre application (.MSI) à partir de documents XML.

Ces documents XML (.WXS, .WXL par exemple) vont "décrire" l'installation de votre application.

Une fois Wix installé, intéressons - nous au fichier <BUILD.XML> de notre application JavaFX.

Pour rappel, ce fichier va "builder" votre application et générer dans notre cas le fichier MSI.

L'exécution de ce fichier se fait par ANT.

Voici un extrait d'un fichier <BUILD.XML> type :

<?xml version="1.0" encoding="UTF-8"?>
<project name="mon application" default="do-deploy" basedir="." xmlns:fx="javafx:com.sun.javafx.tools.ant">
<property name="classpath" location="chemin JDK\jre\lib\ext\jfxrt.jar;" />

<target name="init-fx-tasks">
<path id="fxant">
<filelist>
<file name="${basedir}" />
</filelist>
</path>

<taskdef resource="com/sun/javafx/tools/ant/antlib.xml" uri="javafx:com.sun.javafx.tools.ant" classpathref="fxant" classpath="chemin JDK\lib\ant-javafx.jar" />
</target>

<target name="setup-staging-area">
<delete dir="externalLibs" />
<delete dir="project" />
<delete dir="projectRefs" />

<mkdir dir="externalLibs" />

<copy todir="externalLibs">
<fileset dir="chemin mon projet\lib">
<include name="*" />
</fileset>
</copy>

<mkdir dir="project" />

<copy todir="project">
<fileset dir="chemin mon projet">
<include name="src/**" />
</fileset>
</copy>

<mkdir dir="projectRefs" />

</target>

<target name='do-compile'>
<delete dir="build" />
<mkdir dir="build/src" />
<mkdir dir="build/lib" />
<mkdir dir="build/classes" />

<copy todir="build/lib">
<fileset dir="externalLibs">
</fileset>
</copy>

<copy todir="build/src">
<fileset dir="project/src">
<include name="**/*" />
</fileset>
</copy>

<javac includeantruntime="false" source="1.8" target="1.8" srcdir="build/src" destdir="build/classes" encoding="Cp1252">
<classpath>
<fileset dir="build/lib">
<include name="*" />
</fileset>
</classpath>
</javac>

<copy todir="build/classes">
<fileset dir="project/src">
<exclude name="**/*.java" />
</fileset>
</copy>
</target>

<target name="do-deploy" depends="setup-staging-area, do-compile, init-fx-tasks">
<delete file="dist" />
<delete file="deploy" />

<mkdir dir="dist" />
<mkdir dir="dist/lib" />

<copy todir="dist/lib">
<fileset dir="externalLibs">
<include name="*" />
</fileset>
</copy>

<mkdir dir="dist/resources" />

<copy todir="dist/resources">
<fileset dir="../resources" />
</copy>

<mkdir dir="package" />

<fx:resources id="appRes">
<fx:fileset dir="dist" includes="mon_application.jar" />
<fx:fileset dir="dist" includes="lib/*" />
<fx:fileset dir="dist" includes="resources/**" />
</fx:resources>

<fx:application id="fxApplication" name="MonApplication" mainClass="application.Main" version="x.x.x" />

<mkdir dir="build/classes/META-INF" />

<fx:jar destfile="dist/mon_application.jar">
<fx:application refid="fxApplication" />
<fileset dir="build/classes" />
<fx:resources refid="appRes" />

<manifest>
<attribute name="Implementation-Vendor" value="Mon Entreprise" />
<attribute name="Implementation-Title" value="MonApplication" />
<attribute name="Implementation-Version" value="x.x.x" />
<attribute name="JavaFX-Feature-Proxy" value="None" />
</manifest>
</fx:jar>

<mkdir dir="deploy" />

<fx:deploy verbose="true" embedJNLP="false" extension="false" includeDT="false" offlineAllowed="true" outdir="${basedir}/deploy" outfile="mon_application" nativeBundles="msi" updatemode="background">
<fx:info title="mon application" vendor="mon entreprise" />
<fx:application refId="fxApplication" />

<fx:resources refid="appRes" />

<fx:info vendor="mon entreprise" description="deploiement de mon application" />

</fx:deploy>

</target>*
</project>

Nous ne rentrerons pas dans le détail de ce fichier mais vous noterez que la balise <fx:deploy> permet de spécifier le type de "bundle" à savoir dans notre cas le type "MSI".

Exécutez ce fichier <BUILD.XML> (après quelques customisations bien sûr) : votre fichier MSI est généré !

samedi 17 janvier 2015

Java / Log4J : Console & fichier LOG

LOG4J n'est plus à présenter : API puissante et indispensable pour tout développeur Java qui souhaite tracer certaines informations.

Nous allons nous attarder sur la façon "d'orienter" les traces vers la console et vers un fichier LOG.

Exemple d'un fichier log4j.properties.

Pour rappel ce fichier est le principal fichier pour le paramétrage de LOG4J.

log4j.rootLogger= , A1, A2

log4j.appender.A1=org.apache.log4j.ConsoleAppender
log4j.appender.A1.Threshold=INFO

log4j.appender.A1.layout=org.apache.log4j.PatternLayout
log4j.appender.A1.layout.ConversionPattern=%d %-5p [%t] %-17c{2} (%13F:%L) %3x - %m%n

log4j.appender.A2=org.apache.log4j.RollingFileAppender
log4j.appender.A2.Threshold=INFO
log4j.appender.A2.File= ${user.home}/mon_fichier.log

log4j.appender.A2.MaxFileSize=5000KB
log4j.appender.A2.MaxBackupIndex=1

log4j.appender.A2.layout=org.apache.log4j.PatternLayout
log4j.appender.A2.layout.ConversionPattern=%d %-5p [%t] %-17c{2} %3x - %m%n

Dans cet exemple nous utilisons 2 "loggers" (autrement dit 2 "canaux" pour tracer les informations).

Le premier logger (A1) est la console.

Toutes les informations de type "INFO" seront tracées dans la console avec un format particulier (ConversionPattern).

Le deuxième logger (A2) : il va permettre de tracer les informations de type "INFO" dans un fichier LOG (mon_fichier.log).

A noter l'utilisation de ${user.home} qui spécifie le répertoire "HOME" de l'utilisateur.

Ce fichier est de type "RollingFile" : lorsque sa taille sera de 5000 KB (MaxFileSize) il sera automatiquement "vidé".

Le gros avantage de cette méthode est de pouvoir contrôler la taille des fichiers LOG.

L'inconvénient est de bien définir la taille du fichier selon nos besoins.

LOG4J permet aussi d'archiver ce type de fichier lorsqu'il a atteint la taille maximale. La propriété <MaxBackupIndex> définie le nombre de fichiers "archives" souhaité.

Les informations tracées dans le fichier sont formatées (ConversionPattern).

Java / Design Pattern Stratégie

Le pattern stratégie est utile pour des situations où il est nécessaire de choisir dynamiquement un algorithme à exécuter.

Vous mettez en place une stratégie dans l'exécution de votre code selon le contexte.

Par exemple : vous souhaitez gérer la première exécution d'une application nouvellement installée.

Lors de la première exécution, il est souvent demandé de saisir certaines informations utiles à l'application.

Il peut arriver que vous stoppiez pour différentes raisons cette saisie d'informations.

Lors d'une prochaine exécution de votre application, il vous sera demandé de compléter cette saisie d'informations inachevée.

Le pattern stratégie peut être utile pour définir quelles sont les données restantes à saisir.

Voici un exemple d'implémentation en Java.

Définissons d'abord notre <Strategy Manager>, c'est notre point d'entrée.

public class StrategyManager {
private static StrategyManager instance = null;

// contexte d'exécution
private StrategyContext ctx;

/**
* @return instance StrategyManager

* @throws Exception
*/
public static StrategyManager getInstance() throws Exception {
if (instance == null)
instance = new StrategyManager();

return instance;
}

/**
* exécuter stratégie de lancement
*
* @throws Exception
*/
public void execCtx() throws Exception {
ctx.exec();
}

/**
* @throws Exception
*/
private StrategyManager()
throws Exception {
ctx = new StrategyContext();

  boolean etat = getEtat();

if (etat == 1) 
ctx.setStrategy(new Launch1());
else if (etat == 2)
ctx.setStrategy(new Launch2());
 else
// erreur
}

private int getEtat() {
   int etat = 0;

   // définir l'état qui correspond au contexte que vous souhaitez traiter

  return etat;


}

Vous avez noté que nous avons utilisé un autre pattern, le pattern Singleton. De cette façon nous manipulons qu'une seule instance de notre manager.

Définissons la gestion du contexte <Strategy Context>.

public class StrategyContext {
private IStrategy strategy;

/**
* fixe la stratégie

* @param strategy
*            : stratégie à appliquer
*/
public void setStrategy(IStrategy strategy) {
this.strategy = strategy;
}

/**
* exécuter stratégie sélectionnée

         * @return 

* @throws Exception
*/
public boolean exec() throws Exception {
return strategy.go();
}
}

public interface IStrategy {

/**
* exécuter le code propre à la stratégie choisie

* @throws Exception
*/
public boolean go() throws Exception;
}

Définissons maintenant les différents algorithmes qui peuvent être utilisés selon le contexte.

public class Launch1 implements IStrategy {
public Launch1() {
super();
}

/**
* exécuter stratégie lancement de type 1
*/
@Override
public boolean go() throws Exception {
           boolean resp = true;

           // votre code...

          return resp;
        }
}

public class Launch2 implements IStrategy {
public Launch2() {
super();
}

/**
 * exécuter stratégie lancement de type 2
 */
@Override
public boolean go() throws Exception {
           boolean resp = true;

           // votre code...

          return resp;
        }
}

La dernière étape : comment utiliser notre pattern stratégie ?

StrategyManager instance = StrategyManager.getInstance();

boolean resp = instance.execCtx();

if (!resp) {
  // erreur
}

Un des avantages de ce pattern est la possibilité d'ajouter ou de modifier un algorithme facilement sans réellement toucher au "corps" du traitement.

mercredi 14 janvier 2015

Comment passer en mode Développeur sur votre appareil ANDROID ?

Vous souhaitez tester votre application sur votre appareil ANDROID version >= 4.0 ?

Pour cela il est nécessaire que votre appareil soit en mode "Développeur".

Sinon votre appareil ne sera pas "reconnu" par votre IDE préféré.

Hors par défaut ce "mode" n'est pas actif sur votre appareil.

Pour l'activer, dans l'application "Paramètres" / onglet "Général" / option "A propos de l'appareil" :

Tapez 7 fois sur le "numéro de version" (build) -> vous êtes en mode "Développeur".

Revenez sur l'onglet "général" : une nouvelle option est apparue  -> "Options de développement".

Cliquez sur cette option : activer le Débogage USB.

Une fois votre appareil en mode "Développeur", branchez votre appareil (via le cable USB) sur votre PC / MAC.

Sous Windows, veuillez à installer le driver USB Google (via le SDK manager).

Lancez par exemple Android Studio.

Dans la liste des "DEVICES" doit apparaître votre appareil.

Exécutez votre application : option "Choose a running device" (par défaut votre appareil est sélectionné sinon veuillez en choisir un dans la liste proposée).

Votre application est automatiquement exécutée sur votre appareil !

Evidemment cette "facilité" ne doit pas supplantée l'exécution de votre application sur l'émulateur qui reste importante.

vendredi 9 janvier 2015

Rechercher une chaîne de caractères dans une base SQLSERVER

Il peut être utile de rechercher une chaîne de caractères dans tous les composants d'une base SQLSERVER.

Nous allons dans un premier temps déclarer une procédure stockée <sp_Find> qui sera en charge de cette recherche.

Voici le script.

USE [ma_base]
Go

If Exists ( Select
From sys.objects 
Where object_id = object_id(N'dbo.sp_Find') 
And Type = 'P')
Drop Procedure dbo.sp_Find;
Go

Create Procedure [dbo].[sp_Find] 
 @SearchText varchar(8000)
, @DBName sysname = Null
, @PreviewTextSize int = 100
, @SearchDBsFlag char(1) = 'Y'
, @SearchJobsFlag char(1) = 'Y'
, @SearchSSISFlag char(1) = 'Y'
As

Set Transaction Isolation Level Read Uncommitted;
Set Nocount On;

Create Table #FoundObject (
 DatabaseName sysname
, ObjectName sysname
, ObjectTypeDesc nvarchar(60)
, PreviewText varchar(max))

Declare @SQL as nvarchar(max);

Select 'Chercher pour : ''' + @SearchText + '''' As CurrentSearch;

/* rechercher dans les bases utilisateur */

If @SearchDBsFlag = 'Y'
Begin
If @DBName Is Null 
Begin
Declare ObjCursor Cursor Local Fast_Forward For 
Select [Name]
From Master.sys.Databases
Where [Name] Not In ('AdventureWorks', 'AdventureWorksDW', 'Distribution', 'Master', 'MSDB', 'Model', 'TempDB');

Open ObjCursor;

Fetch Next From ObjCursor Into @DBName;
While @@Fetch_Status = 0
Begin
Select @SQL = '
Use [' + @DBName + ']

Insert Into #FoundObject (
 DatabaseName
, ObjectName
, ObjectTypeDesc
, PreviewText)
Select Distinct
 ''' + @DBName + '''
, sch.[Name] + ''.'' + obj.[Name] as ObjectName
, obj.Type_Desc
, Replace(Replace(SubString(mod.Definition, CharIndex(''' + @SearchText + ''', mod.Definition) - ' + Cast(@PreviewTextSize / 2 As varchar) + ', ' + 
Cast(@PreviewTextSize As varchar) + '), char(13) + char(10), ''''), ''' + @SearchText + ''', ''***' + @SearchText + '***'')
From sys.objects obj 
Inner Join sys.SQL_Modules mod On obj.Object_Id = mod.Object_Id
Inner Join sys.Schemas sch On obj.Schema_Id = sch.Schema_Id
Where mod.Definition Like ''%' + @SearchText + '%'' 
Order By ObjectName';

Exec dbo.sp_executesql @SQL;

Fetch Next From ObjCursor Into @DBName;
End;

Close ObjCursor;

Deallocate ObjCursor;
End
Else
Begin
Select @SQL = '
Use [' + @DBName + ']

Insert Into #FoundObject (
 DatabaseName
, ObjectName
, ObjectTypeDesc
, PreviewText)
Select Distinct
 ''' + @DBName + '''
, sch.[Name] + ''.'' + obj.[Name] as ObjectName
, obj.Type_Desc
, Replace(Replace(SubString(mod.Definition, CharIndex(''' + @SearchText + ''', mod.Definition) - ' + Cast(@PreviewTextSize / 2 As varchar) + ', ' + 
Cast(@PreviewTextSize As varchar) + '), char(13) + char(10), ''''), ''' + @SearchText + ''', ''***' + @SearchText + '***'')
From sys.objects obj 
Inner Join sys.SQL_Modules mod On obj.Object_Id = mod.Object_Id
Inner Join sys.Schemas sch On obj.Schema_Id = sch.Schema_Id
Where mod.Definition Like ''%' + @SearchText + '%'' 
Order By ObjectName';

Exec dbo.sp_ExecuteSQL @SQL;
End;

Select 'Database Objects' As SearchType;

Select
 DatabaseName
, ObjectName
, ObjectTypeDesc As ObjectType
, PreviewText
From #FoundObject
Order By DatabaseName, ObjectName;
End

/*  Rechercher dans les Jobs */

If @SearchJobsFlag = 'Y'
Begin
Select 'Job Steps' As SearchType;


Select  j.[Name] As [Job Name]
, s.Step_Id As [Step #]
, Replace(Replace(SubString(s.Command, CharIndex(@SearchText, s.Command) - @PreviewTextSize / 2, @PreviewTextSize), char(13) + char(10), ''), @SearchText, '***' + @SearchText + '***') As Command
From MSDB.dbo.sysJobs j
Inner Join MSDB.dbo.sysJobSteps s On j.Job_Id = s.Job_Id 
Where s.Command Like '%' + @SearchText + '%';
End

/*  Rechercher dans SSIS (packages) */

If @SearchSSISFlag = 'Y'
Begin
Select 'SSIS Packages' As SearchType;

Select  [Name] As [SSIS Name]
, Replace(Replace(SubString(Cast(Cast(PackageData As varbinary(Max)) As varchar(Max)), CharIndex(@SearchText, Cast(Cast(PackageData As varbinary(Max)) As varchar(Max))) -
@PreviewTextSize / 2, @PreviewTextSize), char(13) + char(10), ''), @SearchText, '***' + @SearchText + '***') As [SSIS XML]
From MSDB.dbo.sysDTSPackages90
Where Cast(Cast(PackageData As varbinary(Max)) As varchar(Max)) Like '%' + @SearchText + '%';
End

Go

Comment utiliser cette procédure ?

EXEC dbo.sp_Find 'votre_chaine_a_rechercher', DBName, PreviewTextSize, SearchDBsFlag, SearchJobsFlag, SearchSSISFlag
avec
<DBName> nom d'une base spécifique pour la recherche - par défaut toutes les bases
<PreviewTextSize> nombre de caractères entourant la chaîne de caractères  - à inclure dans le résultat de la recherche
<SearchDBsFlag> recherche dans les bases ? Y ou N
<SearchJobsFlag> recherche dans les Jobs ? Y ou N
<SearchSSISFlag> recherche dans les packages SSIS ? Y ou N

Lire le contenu d'un fichier Texte en VBS

Comment lire le contenu d'un fichier Texte en VBS ?

Voici le script.

Function ReadTextFile(FileName, CharSet)
  Const adTypeText = 2
  
  ' créer l'object Stream
  Dim BinaryStream
  Set BinaryStream = CreateObject("ADODB.Stream")
  
  ' spécifier le type de stream  - dans notre exemple nous voulons lire des données en mode texte
  BinaryStream.Type = adTypeText
  
  ' spécifier le charset à utiliser pour les données
  If Len(CharSet) > 0 Then
    BinaryStream.CharSet = CharSet
  End If
  
  ' ouvrir le stream
  BinaryStream.Open
  
  ' charger les données du disque vers l'object stream
  BinaryStream.LoadFromFile FileName
  
  ' ouvrir le stream et récupérer les données à partir de l'object
  ReadTextFile = BinaryStream.ReadText
End Function

Exemple d'utilisation :

Dim strData

strData = ReadTextFile("mon_fichier", "us-ascii")

Enregistrement d'un fichier avec du contenu binaire en VBS

Comment enregistrer du contenu binaire dans un fichier en VBS ?

Voici le script.

Function SaveBinaryData(FileName, ByteArray)
  Const adTypeBinary = 1
  Const adSaveCreateOverWrite = 2
  
  'création object Stream
  Dim BinaryStream
  Set BinaryStream = CreateObject("ADODB.Stream")
  
  'spécifier type stream - dans notre exemple nous voulons sauvegarder des données binaires.
  BinaryStream.Type = adTypeBinary
  
  'Ouvrir le stream et écrire les données binaires dans l'object
  BinaryStream.Open
  BinaryStream.Write ByteArray
  
  'Sauvegarder les données binaires sur disque
  BinaryStream.SaveToFile FileName, adSaveCreateOverWrite
End Function

Exemple d'utilisation de cette fonction :

// byteData contient mes données binaires
SaveBinaryData "mon_fichier", byteData

jeudi 8 janvier 2015

Passer une base SQL SERVER du mode SUSPECT au mode NORMAL

Il peut arriver qu'une base se retrouve dans un état "SUSPECT".

Les causes sont multiples mais en général nous sommes dans le cas d'une base "corrompue".

Et lorsque cela arrive, l'accès à cette base est vraiment limité. En clair vous ne pouvez plus l'utiliser correctement.

Comment remédier à ce problème ?

Vous trouverez ci-joint une séquence de commandes à exécuter.

Nous réinitialisons le statut de la base.

EXEC sp_resetstatus 'nom_de_votre_base'
GO

Nous passons la base en mode "réparation" et faisons un "état des lieux"

ALTER DATABASE nom_de_votre_base SET EMERGENCY
DBCC checkdb('nom_de_votre_base')

Nous supprimons les anomalies détectées (ce qui peut provoquer la perte de données)

ALTER DATABASE nom_de_votre_base SET SINGLE_USER WITH ROLLBACK IMMEDIATE
DBCC checkdb('nom_de_votre_base', REPAIR_ALLOW_DATA_LOSS)

Nous repassons la base en mode normal : elle est à nouveau accessible

ALTER DATABASE nom_de_votre_base SET MULTI-USER WITH ROLLBACK IMMEDIATE
GO

Quelques outils pour comprendre la cause du problème

  • Un CHECKDISK sur la partition où se trouve la base
Cela permet de vérifier si le problème est un problème matériel.


  • Vous pouvez consulter les logs.
Exemple :

EXEC sp_readerrorlog 0,1,'2015-01-01'

0 -> log courant, 1 -> error log, '2015-01-01' -> chaine à rechercher dans log, dans notre cas la date du jour par exemple

  • Si dans les erreurs générées par DBCC checkdb('nom_de_ma_base') se trouve des erreurs du type "INDEX"
Nous avons probablement des index incorrects.

Pour lister ces index :

EXEC sp_HelpIndex 'nom_de_ma_table'

  • Vérifier le contenu de la table <msdb.suspect_pages>
Elle vous renseigne sur les pages de données corrompues.

Comment "cacher" un répertoire en VBS ?

Vous trouverez ci-joint un script bien pratique en VBS pour "cacher" un répertoire.

Script

Set objFSO = CreateObject("Scripting.FileSystemObject")

Set objFolder = objFSO.GetFolder("chemin_de_mon_repertoire")

If objFolder.Attributes = objFolder.Attributes AND 2 Then
    objFolder.Attributes = objFolder.Attributes XOR 2 
End If

mardi 6 janvier 2015

Défragmenter les index de vos bases SQL SERVER

Comment défragmenter les index de vos bases SQL SERVER ?

Voici un script TRANSACT / SQL fort intéressant et surtout bien pratique.

Dans un premier temps "déclarons" la procédure stockée <dbo.dba_indexDefrag_sp>.

Nous verrons ensuite comment l'utiliser.

Quelques recommandations : soyez attentifs à la taille des TRANSACTION LOG durant le traitement et bien sûr évitez d'exécuter ce traitement durant les heures de travail (consommateur en ressources et en temps CPU).

Script

USE [ma_base]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO

ALTER Procedure [dbo].[dba_indexDefrag_sp]

    /* Déclarer les paramètres */

      @minFragmentation     float           = 10.0
        /* en pourcentage, si la valeur est inférieure à 10.0 -> aucune défragmentation */

    , @rebuildThreshold     float           = 30.0
        /* en pourcentage, une valeur plus importante va résulter plus d'un REBUILD que d'une réorganisation */

    , @executeSQL           bit             = 1    
        /* 1 = exécuter traitement ; 0 = afficher commandes seulement */

    , @defragOrderColumn    nvarchar(20)    = 'range_scan_count'
        /* options valides : range_scan_count, fragmentation, page_count 

Permet de définir les priorités dans l'ordre des defrags.  Utilisé uniquement si @executeSQL = 1.

range_scan_count = en général, c'est le plus profitable pour la défragmentation
fragmentation  = valeur de la fragmentation dans l'index;
                                               plus la valeur est haute, plus la fragmentation est importante
page_count  = nombre de pages dans l'index ; effet sur la durée du defrag de l'index
*/

    , @defragSortOrder      nvarchar(4)     = 'DESC'
        /* options valides : ASC, DESC */

    , @timeLimit            int             = 720
        /* limitation de temps optionnelle : exprimée en minutes */

    , @database             varchar(128)    = N'ma_base'
        /* option pour spécifier un nom de base ; null correspond à toutes les bases */

    , @tableName            varchar(4000)   = Null 
        /* option pour spécifier une table ; null correspond à toutes les tables */

    , @forceRescan          bit             = 0
        /* Pour forcer ou pas un rescan des index ; 1 = force, 0 = utilise scan existant si disponible */

    , @scanMode             varchar(10)     = N'LIMITED'
        /* Options sont LIMITED (le plus rapide, seul le plus haut niveau <parent> est scanné), SAMPLED (un % des pages DATA), et DETAILED (toutes les pages DATA) */

    , @minPageCount         int             = 8
        /*  Minimum de pages pour déclencher le traitement - Microsoft recommende > 1 extent (8 pages) */

    , @maxPageCount         int             = Null
        /* Maximum de pages pour déclencher le traitement - NULL = pas de limite */

    , @excludeMaxPartition  bit             = 0
        /* 1 = exclure partition la plus "populée" ;  0 = ne pas exclure */

    , @onlineRebuild        bit             = 1  
        /* 1 = rebuild online ; 0 = rebuild offline seulement pour l'édition Entreprise */

    , @sortInTempDB         bit             = 1
        /* 1 = exécute opération de tri dans TempDB ; 0 = exécute opération de tri dans la base */

    , @maxDopRestriction    tinyint         = Null
        /* option pour réduite le nombre de processeurs pour l'opération seulement dans l'édition Entreprise */

    , @printCommands        bit             = 0  
        /* 1 = afficher commandes à l'écran ; 0 = ne pas afficher commandes */

    , @printFragmentation   bit             = 0
        /* 1 = afficher fragmentation à l'écran;
           0 = ne pas imprimer */

    , @defragDelay          char(8)         = '00:00:05'
        /* durée d'attente entre 2 commandes defrag */

    , @debugMode            bit             = 0
        /* 1 : afficher commentaires debug ; 0 : ne pas afficher commentaires debug */

As

Set NoCount On;
Set XACT_Abort On;
Set Quoted_Identifier On;

Begin

    Begin Try

        If @minFragmentation Is Null
            Or @minFragmentation Not Between 0.00 And 100.0
                Set @minFragmentation = 10.0;

        If @rebuildThreshold Is Null
            Or @rebuildThreshold Not Between 0.00 And 100.0
                Set @rebuildThreshold = 30.0;

        If @defragDelay Not Like '00:[0-5][0-9]:[0-5][0-9]'
            Set @defragDelay = '00:00:05';

        If @defragOrderColumn Is Null
            Or @defragOrderColumn Not In ('range_scan_count', 'fragmentation', 'page_count')
                Set @defragOrderColumn = 'range_scan_count';

        If @defragSortOrder Is Null
            Or @defragSortOrder Not In ('ASC', 'DESC')
                Set @defragSortOrder = 'DESC';

        If @scanMode Not In ('LIMITED', 'SAMPLED', 'DETAILED')
            Set @scanMode = 'LIMITED';

        If @debugMode Is Null
            Set @debugMode = 0;

        If @forceRescan Is Null
            Set @forceRescan = 0;

        If @sortInTempDB Is Null
            Set @sortInTempDB = 1;

        If @debugMode = 1 RaisError('Initialisation et démarrage traitement...', 0, 42) With NoWait;

        Declare   @objectID                 int
                , @databaseID               int
                , @databaseName             nvarchar(128)
                , @indexID                  int
                , @partitionCount           bigint
                , @schemaName               nvarchar(128)
                , @objectName               nvarchar(128)
                , @indexName                nvarchar(128)
                , @partitionNumber          smallint
                , @fragmentation            float
                , @pageCount                int
                , @sqlCommand               nvarchar(4000)
                , @rebuildCommand           nvarchar(200)
                , @dateTimeStart            datetime
                , @dateTimeEnd              datetime
                , @containsLOB              bit
                , @editionCheck             bit
                , @debugMessage             nvarchar(4000)
                , @updateSQL                nvarchar(4000)
                , @partitionSQL             nvarchar(4000)
                , @partitionSQL_Param       nvarchar(1000)
                , @LOB_SQL                  nvarchar(4000)
                , @LOB_SQL_Param            nvarchar(1000)
                , @indexDefrag_id           int
                , @startDateTime            datetime
                , @endDateTime              datetime
                , @getIndexSQL              nvarchar(4000)
                , @getIndexSQL_Param        nvarchar(4000)
                , @allowPageLockSQL         nvarchar(4000)
                , @allowPageLockSQL_Param   nvarchar(4000)
                , @allowPageLocks           int
                , @excludeMaxPartitionSQL   nvarchar(4000);

        Select @startDateTime = GetDate()
            , @endDateTime = DateAdd(minute, @timeLimit, GetDate());

        Create Table #databaseList
        (
              databaseID        int
            , databaseName      varchar(128)
            , scanStatus        bit
        );

        Create Table #processor
        (
              [index]           int
            , Name              varchar(128)
            , Internal_Value    int
            , Character_Value   int
        );

        Create Table #maxPartitionList
        (
              databaseID        int
            , objectID          int
            , indexID           int
            , maxPartition      int
        );

        If @debugMode = 1 RaisError('Début validation...', 0, 42) With NoWait;

        Insert Into #processor
        Execute xp_msver 'ProcessorCount';

        If @maxDopRestriction Is Not Null And @maxDopRestriction > (Select Internal_Value From #processor)
            Select @maxDopRestriction = Internal_Value
            From #processor;

        /* Vérifions version server ; 1804890536 = Entreprise, 610778273 = Entreprise Evaluation, -2117995310 = Développeur */
        If (Select ServerProperty('EditionID')) In (1804890536, 610778273, -2117995310)
            Set @editionCheck = 1 
        Else
            Set @editionCheck = 0; 

/* liste des tables à traiter */
        Insert Into #databaseList
        Select database_id
            , name
            , 0 
        From sys.databases
        Where name = IsNull(@database, name)
            And [name] Not In ('master', 'tempdb')
            And [state] = 0
            And is_read_only = 0;

        If Not Exists(Select Top 1 * From dbo.dba_indexDefragStatus Where defragDate Is Null)
            Or @forceRescan = 1
        Begin

            Truncate Table dbo.dba_indexDefragStatus;

// traiter les tables
            While (Select Count(*) From #databaseList Where scanStatus = 0) > 0
            Begin

                Select Top 1 @databaseID = databaseID
                From #databaseList
                Where scanStatus = 0;

                Select @debugMessage = '  traitement table ' + DB_Name(@databaseID) + '...';

                If @debugMode = 1
                    RaisError(@debugMessage, 0, 42) With NoWait;

                Insert Into dbo.dba_indexDefragStatus
                (
                      databaseID
                    , databaseName
                    , objectID
                    , indexID
                    , partitionNumber
                    , fragmentation
                    , page_count
                    , range_scan_count
                    , scanDate
                )
                Select
                      ps.database_id As 'databaseID'
                    , QuoteName(DB_Name(ps.database_id)) As 'databaseName'
                    , ps.object_id As 'objectID'
                    , ps.index_id As 'indexID'
                    , ps.partition_number As 'partitionNumber'
                    , Sum(ps.avg_fragmentation_in_percent) As 'fragmentation'
                    , Sum(ps.page_count) As 'page_count'
                    , os.range_scan_count
                    , GetDate() As 'scanDate'
                From sys.dm_db_index_physical_stats(@databaseID, Object_Id(@tableName), Null , Null, @scanMode) As ps
                Join sys.dm_db_index_operational_stats(@databaseID, Object_Id(@tableName), Null , Null) as os
                    On ps.database_id = os.database_id
                    And ps.object_id = os.object_id
                    and ps.index_id = os.index_id
                    And ps.partition_number = os.partition_number
                Where avg_fragmentation_in_percent >= @minFragmentation
                    And ps.index_id > 0
                    And ps.page_count > @minPageCount
                    And ps.index_level = 0
                Group By ps.database_id
                    , QuoteName(DB_Name(ps.database_id))
                    , ps.object_id
                    , ps.index_id
                    , ps.partition_number
                    , os.range_scan_count
                Option (MaxDop 2);

                If @excludeMaxPartition = 1
                Begin

                    Set @excludeMaxPartitionSQL = '
                        Select ' + Cast(@databaseID As varchar(10)) + ' As [databaseID]
                            , [object_id]
                            , index_id
                            , Max(partition_number) As [maxPartition]
                        From ' + DB_Name(@databaseID) + '.sys.partitions
                        Where partition_number > 1
                            And [rows] > 0
                        Group By object_id
                            , index_id;';

                    Insert Into #maxPartitionList
                    Execute sp_executesql @excludeMaxPartitionSQL;

                End;
             
                Update #databaseList
                Set scanStatus = 1
                Where databaseID = @databaseID;

            End

            If @excludeMaxPartition = 1
            Begin

                Delete ids
                From dbo.dba_indexDefragStatus As ids
                Join #maxPartitionList As mpl
                    On ids.databaseID = mpl.databaseID
                    And ids.objectID = mpl.objectID
                    And ids.indexID = mpl.indexID
                    And ids.partitionNumber = mpl.maxPartition;

            End;

            Update ids
            Set ids.exclusionMask = ide.exclusionMask
            From dbo.dba_indexDefragStatus As ids
            Join dbo.dba_indexDefragExclusion As ide
                On ids.databaseID = ide.databaseID
                And ids.objectID = ide.objectID
                And ids.indexID = ide.indexID;
       
        End

// les index à défragmenter
        Select @debugMessage = 'Nous avons  ' + Cast(Count(*) As varchar(10)) + '  index à défragmenter !'
        From dbo.dba_indexDefragStatus
        Where defragDate Is Null
            And page_count Between @minPageCount And IsNull(@maxPageCount, page_count);

        If @debugMode = 1 RaisError(@debugMessage, 0, 42) With NoWait;

        While (Select Count(*)
               From dbo.dba_indexDefragStatus
               Where (
                           (@executeSQL = 1 And defragDate Is Null)
                        Or (@executeSQL = 0 And defragDate Is Null And printStatus = 0)
                     )
                And exclusionMask & Power(2, DatePart(weekday, GetDate())-1) = 0
                And page_count Between @minPageCount And IsNull(@maxPageCount, page_count)) > 0
        Begin

            If IsNull(@endDateTime, GetDate()) < GetDate()
            Begin
                RaisError('Limite temps dépassé !', 11, 42) With NoWait;
            End;

            Set @getIndexSQL = N'
            Select Top 1
                  @objectID_Out         = objectID
                , @indexID_Out          = indexID
                , @databaseID_Out       = databaseID
                , @databaseName_Out     = databaseName
                , @fragmentation_Out    = fragmentation
                , @partitionNumber_Out  = partitionNumber
                , @pageCount_Out        = page_count
            From dbo.dba_indexDefragStatus
            Where defragDate Is Null '
                + Case When @executeSQL = 0 Then 'And printStatus = 0' Else '' End + '
                And exclusionMask & Power(2, DatePart(weekday, GetDate())-1) = 0
                And page_count Between @p_minPageCount and IsNull(@p_maxPageCount, page_count)
            Order By + ' + @defragOrderColumn + ' ' + @defragSortOrder;
                     
            Set @getIndexSQL_Param = N'@objectID_Out        int OutPut
                                     , @indexID_Out         int OutPut
                                     , @databaseID_Out      int OutPut
                                     , @databaseName_Out    nvarchar(128) OutPut
                                     , @fragmentation_Out   int OutPut
                                     , @partitionNumber_Out int OutPut
                                     , @pageCount_Out       int OutPut
                                     , @p_minPageCount      int
                                     , @p_maxPageCount      int';

            Execute sp_executesql @getIndexSQL
                , @getIndexSQL_Param
                , @p_minPageCount       = @minPageCount
                , @p_maxPageCount       = @maxPageCount
                , @objectID_Out         = @objectID OutPut
                , @indexID_Out          = @indexID OutPut
                , @databaseID_Out       = @databaseID OutPut
                , @databaseName_Out     = @databaseName OutPut
                , @fragmentation_Out    = @fragmentation OutPut
                , @partitionNumber_Out  = @partitionNumber OutPut
                , @pageCount_Out        = @pageCount OutPut;

            Select @updateSQL = N'Update ids
                Set schemaName = QuoteName(s.name)
                    , objectName = QuoteName(o.name)
                    , indexName = QuoteName(i.name)
                From dbo.dba_indexDefragStatus As ids
                Inner Join ' + @databaseName + '.sys.objects As o
                    On ids.objectID = o.object_id
                Inner Join ' + @databaseName + '.sys.indexes As i
                    On o.object_id = i.object_id
                    And ids.indexID = i.index_id
                Inner Join ' + @databaseName + '.sys.schemas As s
                    On o.schema_id = s.schema_id
                Where o.object_id = ' + Cast(@objectID As varchar(10)) + '
                    And i.index_id = ' + Cast(@indexID As varchar(10)) + '
                    And i.type > 0
                    And ids.databaseID = ' + Cast(@databaseID As varchar(10));

            Execute sp_executesql @updateSQL;

            Select @objectName  = objectName
                , @schemaName   = schemaName
                , @indexName    = indexName
            From dbo.dba_indexDefragStatus
            Where objectID = @objectID
                And indexID = @indexID
                And databaseID = @databaseID;

            Select @partitionSQL = 'Select @partitionCount_OUT = Count(*)
                                        From ' + @databaseName + '.sys.partitions
                                        Where object_id = ' + Cast(@objectID As varchar(10)) + '
                                            And index_id = ' + Cast(@indexID As varchar(10)) + ';'
                , @partitionSQL_Param = '@partitionCount_OUT int OutPut';

            Execute sp_executesql @partitionSQL, @partitionSQL_Param, @partitionCount_OUT = @partitionCount OutPut;
   
            /* Déterminer si la table contient des LOB (Large Objects)
            Select @LOB_SQL = ' Select @containsLOB_OUT = Count(*)
                                From ' + @databaseName + '.sys.columns With (NoLock)
                                Where [object_id] = ' + Cast(@objectID As varchar(10)) + '
                                   And (system_type_id In (34, 35, 99)
                                            Or max_length = -1);'
                                /*  system_type_id --> 34 = image, 35 = text, 99 = ntext
                                    max_length = -1 --> varbinary(max), varchar(max), nvarchar(max), xml */
                    , @LOB_SQL_Param = '@containsLOB_OUT int OutPut';

            Execute sp_executesql @LOB_SQL, @LOB_SQL_Param, @containsLOB_OUT = @containsLOB OutPut;

            /* Déterminer si verrous sur page sont autorisés ; pour de tels index nou avons toujours besoin d'effectuer un REBUILD */
            Select @allowPageLockSQL = 'Select @allowPageLocks_OUT = Count(*)
                                        From ' + @databaseName + '.sys.indexes
                                        Where object_id = ' + Cast(@objectID As varchar(10)) + '
                                            And index_id = ' + Cast(@indexID As varchar(10)) + '
                                            And Allow_Page_Locks = 0;'
                , @allowPageLockSQL_Param = '@allowPageLocks_OUT int OutPut';

            Execute sp_executesql @allowPageLockSQL, @allowPageLockSQL_Param, @allowPageLocks_OUT = @allowPageLocks OutPut;

            /* S'il y a un taux élevé de fragmentation ou si nous avons un LOB, nous devons réorganiser */
            If (@fragmentation < @rebuildThreshold Or @containsLOB >= 1 Or @partitionCount > 1)
                And @allowPageLocks = 0
            Begin
         
                Set @sqlCommand = N'Alter Index ' + @indexName + N' On ' + @databaseName + N'.'
                                    + @schemaName + N'.' + @objectName + N' ReOrganize';

                /* si notre index est partitionné, nous devons toujours réorganiser */
                If @partitionCount > 1
                    Set @sqlCommand = @sqlCommand + N' Partition = '
                                    + Cast(@partitionNumber As nvarchar(10));

            End

            /* Si l'index est très fragmenté et ne contient pas plusieurs partitions ou ne contient pas de LOB,
               ou si l'index n'autorise pas les verrous sur page => REBUILD pour index */
            Else If (@fragmentation >= @rebuildThreshold Or @allowPageLocks <> 0)
                And IsNull(@containsLOB, 0) != 1 And @partitionCount <= 1
            Begin

                If @onlineRebuild = 1 And @editionCheck = 1
                    Set @rebuildCommand = N' Rebuild With (Online = On';
                Else
                    Set @rebuildCommand = N' Rebuild With (Online = Off';
             
                If @sortInTempDB = 1
                    Set @rebuildCommand = @rebuildCommand + N', Sort_In_TempDB = On';
                Else
                    Set @rebuildCommand = @rebuildCommand + N', Sort_In_TempDB = Off';

                   If @maxDopRestriction Is Not Null And @editionCheck = 1
                    Set @rebuildCommand = @rebuildCommand + N', MaxDop = ' + Cast(@maxDopRestriction As varchar(2)) + N')';
                Else
                    Set @rebuildCommand = @rebuildCommand + N')';

                Set @sqlCommand = N'Alter Index ' + @indexName + N' On ' + @databaseName + N'.'
                                + @schemaName + N'.' + @objectName + @rebuildCommand;

            End
            Else
                If @printCommands = 1 Or @debugMode = 1
                    RaisError('Impossible de défragmenter cet index !', 0, 42) With NoWait;

            If @executeSQL = 1
            Begin

                Set @debugMessage = 'Exécuter : ' + @sqlCommand;
             
                If @printCommands = 1 Or @debugMode = 1
                    RaisError(@debugMessage, 0, 42) With NoWait;

               Set @dateTimeStart  = GetDate();

                Insert Into dbo.dba_indexDefragLog
                (
                      databaseID
                    , databaseName
                    , objectID
                    , objectName
                    , indexID
                    , indexName
                    , partitionNumber
                    , fragmentation
                    , page_count
                    , dateTimeStart
                    , sqlStatement
                )
                Select
                      @databaseID
                    , @databaseName
                    , @objectID
                    , @objectName
                    , @indexID
                    , @indexName
                    , @partitionNumber
                    , @fragmentation
                    , @pageCount
                    , @dateTimeStart
                    , @sqlCommand;

                Set @indexDefrag_id = Scope_Identity();

                Begin Try

                    Execute sp_executesql @sqlCommand;
                    Set @dateTimeEnd = GetDate();
                 
                    Update dbo.dba_indexDefragLog
                    Set dateTimeEnd = @dateTimeEnd
                        , durationSeconds = DateDiff(second, @dateTimeStart, @dateTimeEnd)
                    Where indexDefrag_id = @indexDefrag_id;

                End Try
                Begin Catch

                    Update dbo.dba_indexDefragLog
                    Set dateTimeEnd = GetDate()
                        , durationSeconds = -1
                        , errorMessage = Error_Message()
                    Where indexDefrag_id = @indexDefrag_id;

                    If @debugMode = 1
                        RaisError('Erreur lors de l'exécution de cette commande - consulter la table dba_indexDefragLog pour plus de détails', 0, 42) With NoWait;

                End Catch

                WaitFor Delay @defragDelay;

                Update dbo.dba_indexDefragStatus
                Set defragDate = GetDate()
                    , printStatus = 1
                Where databaseID       = @databaseID
                  And objectID         = @objectID
                  And indexID          = @indexID
                  And partitionNumber  = @partitionNumber;

            End
            Else
   
            Begin
               
                If @printCommands = 1 Or @debugMode = 1
                    Print IsNull(@sqlCommand, 'erreur !');

                Update dbo.dba_indexDefragStatus
                Set printStatus = 1
                Where databaseID       = @databaseID
                  And objectID         = @objectID
                  And indexID          = @indexID
                  And partitionNumber  = @partitionNumber;
            End

        End

        /* résumé du traitement */
        If @printFragmentation = 1
        Begin

            If @debugMode = 1 RaisError(' Afficher résumé traitement...', 0, 42) With NoWait;

            Select databaseID
                , databaseName
                , objectID
                , objectName
                , indexID
                , indexName
                , partitionNumber
                , fragmentation
                , page_count
                , range_scan_count
            From dbo.dba_indexDefragStatus
            Where defragDate >= @startDateTime
            Order By defragDate;

        End;

    End Try
    Begin Catch

        Set @debugMessage = Error_Message() + ' (Numéro de ligne : ' + Cast(Error_Line() As varchar(10)) + ')';
        Print @debugMessage;

    End Catch;

    Drop Table #databaseList;
    Drop Table #processor;
    Drop Table #maxPartitionList;

    Set NoCount Off;
    Return 0

End

Comment utiliser cette procédure stockée ?

Voici un exemple d'utilisation :

Exec dbo.dba_indexDefrag_sp
              @executeSQL           = 1
            , @printCommands        = 1
            , @debugMode            = 1
            , @printFragmentation   = 1
            , @forceRescan          = 1
            , @maxDopRestriction    = 1
            , @minPageCount         = 8
            , @maxPageCount         = Null
            , @minFragmentation     = 1
            , @rebuildThreshold     = 30
            , @defragDelay          = '00:00:05'
            , @defragOrderColumn    = 'page_count'
            , @defragSortOrder      = 'DESC'
            , @excludeMaxPartition  = 1
            , @timeLimit            = Null;

Nous vous conseillons de "packager" cet exemple dans un JOB (SQL Agent JOB) ou de l'exécuter en mode commande.