Publié le 28 Février 2012

C'est un problème qui a l'air simple mais qui devient vite complexe quand on essaie de le traduire en SQL.

Ce cas se présente à chaque fois qu'on veut récupérer le plus récent/ancien ou le min/max d'une valeur pour chaque groupe de données.

Par exemple la dernière commande de chaque client, la plus importante commande de chaque client, le dernier log de chaque module, etc.

Pour illustrer le principe je pars des données suivantes:

+-------------------------+------------------+------------+
| film                    | acteur           | sortie     |
+-------------------------+------------------+------------+
| Intouchables            | Omar Sy          | 2011-11-02 |
| Intouchables            | François Cluzet  | 2011-11-02 |
| Tellement proches       | Omar Sy          | 2009-06-17 |
| Tellement proches       | Isabelle Carré   | 2009-06-17 |
| Micmacs à tire-larigot  | Omar Sy          | 2009-10-28 |
| Micmacs à tire-larigot  | Dany Boon        | 2009-10-28 |
| Les petits mouchoirs    | François Cluzet  | 2010-10-20 |
| Les petits mouchoirs    | Benoît Magimel   | 2010-10-20 |
| Les petits mouchoirs    | Marion Cotillard | 2010-10-20 |
| Minuit à Paris          | Marion Cotillard | 2011-05-11 |
+-------------------------+------------------+------------+

Ce jeu de données est souvent obtenu à partir de jointures entre plusieurs tables mais pour ne pas ajouter de complexité je fais comme s'il s'agissait d'une seule table.

Ensuite je veux afficher uniquement le dernier film de chaque acteur, soit le résultat suivant:

+-------------------------+------------------+------------+
| film                    | acteur           | sortie     |
+-------------------------+------------------+------------+
| Intouchables            | Omar Sy          | 2011-11-02 |
| Intouchables            | François Cluzet  | 2011-11-02 |
| Tellement proches       | Isabelle Carré   | 2009-06-17 |
| Micmacs à tire-larigot  | Dany Boon        | 2009-10-28 |
| Les petits mouchoirs    | Benoît Magimel   | 2010-10-20 |
| Minuit à Paris          | Marion Cotillard | 2011-05-11 |
+-------------------------+------------------+------------+

La requête s'effectue en 2 étapes:

On cherche d'abord les dernierès sorties de chaque acteur avec la requête suivante:

SELECT acteur, MAX(sortie) AS derniere_sortie FROM films GROUP BY acteur;

+------------------+-----------------+
| acteur           | derniere_sortie |
+------------------+-----------------+
| Benoît Magimel   | 2010-10-20      |
| Dany Boon        | 2009-10-28      |
| François Cluzet  | 2011-11-02      |
| Isabelle Carré   | 2009-06-17      |
| Marion Cotillard | 2011-05-11      |
| Omar Sy          | 2011-11-02      |
+------------------+-----------------+

Ensuite il ne reste plus qu'à compléter avec le reste des infos. Pour cela on va faire une jointure entre la table films et la requête précédente:

SELECT f.*
FROM (
    SELECT acteur, MAX(sortie) AS derniere_sortie FROM films GROUP BY acteur
) d
INNER JOIN films f ON (d.acteur = f.acteur AND d.derniere_sortie = f.sortie); 

Voir les commentaires

Rédigé par Bliz

Publié dans #MySQL

Repost 0

Publié le 24 Février 2012

Pour commencer il n'est pas recommandé de mélanger les types génériques et les tableaux.

Les types génériques sont des informations sur les types disponibles au moment de la compilation mais qui disparaissent à l'exécution.

Par exemple:

List<Integer> maListe = new ArrayList<Integer>();

Une fois compilé correspond au byte code:

List maListe = new ArrayList();

L'information sur le type du contenu disparaît. C'est le compilateur qui s'assure que le code n'introduit que des Integer dans maListe.

On dit que les types génériques sont réifiés.

En revanche en ce qui concerne les tableaux:

Integer[] monTableau = new Integer[10];

reste un tableau d'Integer même après compilation (ce n'est pas un Object[]).

On ne peut donc pas écrire le code suivant:

public <T> T[] concat(T[] array1, T[] array2){
   T[] result = new T[array1.length + array2.length]; // erreur on ne peut pas générer le byte code car on ne connait pas T au moment de la compilation
   System.arraycopy(array1, 0, result, 0, array1.length);
   System.arraycopy(array2, 0, result, array1.length, array2.length);
   return result;
}

Il faut donc passer par la reflexion pour créer un tableau d'un type qu'on ne connait qu'à l'exécution:

public <T> T[] concat(T[] array1, T[] array2){
   @SuppressWarnings("unchecked")
   // warning sur le cast mais d'après la signature de la méthode on sait qu'on peut caster sans soucis
   T[] result = (T[]) Array.newInstance(array1.getClass().getComponentType(), array1.length, array2.length); 
   System.arraycopy(array1, 0, result, 0, array1.length);
   System.arraycopy(array2, 0, result, array1.length, array2.length);
   return result;

Voilà mais il est beaucoup plus simple et plus agréable de travailler sur des listes - qui d'ailleurs fournissent déjà la méthode addAll(). 

Voir les commentaires

Rédigé par Bliz

Publié dans #Java

Repost 0

Publié le 22 Février 2012

Lorsqu'on voulait créer une instance dynamiquement à partir d'un nom de class on utilisait le code suivant:

Class maClass = Class.forName("mon.package.MaClass");
MonInterface instance = (MonInterface) maClass.newInstance();

Sachant que la class "MaClass" implémente l'interface "MonInterface".

Depuis l'introduction des types génériques ce code génère l'avertissement suivant:

Class is a raw type. References to generic type Class<T> should be parameterized.

Pour éviter les avertissement le code s'écrit comme ceci:

Class<? extends MonInterface> maClass = Class.forName("mon.package.MaClass").asSubclass(MonInterface.class);
MonInterface instance = maClass.newInstance();

Voir les commentaires

Rédigé par Bliz

Publié dans #Java

Repost 0

Publié le 21 Février 2012

Voilà le problème:

J'effectue une requête SQL INSERT dans une table qui a un auto increment et j'ai besoin de récupérer l'id généré qui correspond à mon INSERT.

JDBC fournit la mécanique pour ça (après il faut que la base derrière le gère - avec MySQL pas de problème):

String query = "INSERT INTO ...";
Statement statement = null;
try{
   // Il faut récupérer une instance de connection - généralement ça passe par un pool
   // et il faut bien préciser le flag RETURN_GENERATED_KEYS

   statement = connection.prepareStatement(query, Statement.RETURN_GENERATED_KEYS);
   statement.execute();
   ResultSet rs = statement.getGeneratedKeys();
   if (rs != null && rs.first()) {
      // on récupère l'id généré
      long generatedId = rs.getLong(1);
   }
}
catch(SQLException e){
   e.printStackTrace();
}
finally{
   if(statement != null){
      statement.close();
   }
}
 

Voir les commentaires

Rédigé par Bliz

Publié dans #Java

Repost 0

Publié le 20 Février 2012

Sur mon projet nous utilisons Maven pour effectuer la compilation. Maven permet de télécharger automatiquement toutes les dépendances nécessaires à la compilation du projet.

Cependant aujourd'hui lors de l'ajout d'une nouvelle librairie le téléchargement automatique n'a pas fonctionné car la librairie en question n'est pas disponible en ligne.

Comme il s'agit d'une phase de test et d'investigation je ne souhaite pas télécharger la librairie sur le dépôt officiel du projet.

Il me reste une solution installé la libraire localement pour que Maven la trouve lors de la compilation.

Cette opération s'effectue avec Maven en tapant la commande suivante:

mvn install:install-file -Dfile=<lib.jar> -DgroupId=<groupId> -DartifactId=<artifactId> -Dversion=<version> -Dpackaging=<packaging>

Il faut remplacer chaque paramètre entre < et > par la valeur appropriée qui dépend de la lib à installer.

Voir les commentaires

Rédigé par Bliz

Publié dans #Maven

Repost 0

Publié le 16 Février 2012

Les raccourcis pour SVN (du genre CTRL+ALT+U pour update, CTRL+ALT+C pour commit) ne sont pas activés par défaut sous Eclipse.

Pour cela il faut se rendre dans le menu

Window / Customize Perspectives ...

puis dans l'onglet 

Command & Groups availability

il faut cocher la case "SVN".

Voilà c'est pas compliquer mais il fallait le trouver. D'ailleurs on peut en profiter pour activer d'autres raccourcis si besoin.

Voir les commentaires

Rédigé par Bliz

Publié dans #Eclipse

Repost 0

Publié le 15 Février 2012

J'ai un serveur FTP et je dois effectuer une action à chaque fichier téléchargé (par exemple envoyer une notification).

On pourrait éventuellement scanner le répertoire à intervalle régulier, mais une solution plus simple consiste à se "brancher" sur le fichier de log et de scruter les fichiers uploadés.

Grâce à la commande "tail -f" on peut écrire un petit script shel qui va exécuter une commande à chaque ligne écrite dans un fichier:

#!/bin/sh

tail -f $1 | while read LINE;
do
   NB=$((NB+1))
   echo "$NB: $LINE" 
done

Voilà pour le principe, bon ok, ici le script ne fait que réécrire la ligne du fichier en lui collant un numéro de ligne.

Dans le cas d'un fichier de log d'un server FTP on peut remplacer le corps de la boucle while par une extraction du nom du fichier téléchargé puis appeler un autre programme qui se chargera d'envoyer la notification.

Voir les commentaires

Rédigé par Bliz

Publié dans #Script

Repost 0

Publié le 13 Février 2012

XStream permet de sérialiser facilement un objet java en un fichier xml en recréant l'arborescence des attributs d'un objet java dans une structure XML.

Mais parfois on ne veut pas sérialiser tous les attributs d'un objet.

Par exemple j'ai un objet Article qui contient la liste des utilisateurs qui ont écrit un article. Les utilisateurs étant stockés ailleurs (et également modifiable par ailleurs) et donc je ne souhaite que stocket leur id dans le fichier XML. (avec l'id je peux toujours retrouver toutes les infos d'un utilisateur).

Pour ce cas XStream fournit l'annotation

@XStreamOmitField

Mais voilà je rajoute les annotations et je lance mon test et aucun effet!

Un petit coup d'oeil dans la doc d'XStream et effectivement il faut aussi appeler la méthode processAnnotation avant de générer le XML:

XStream xstream = new XStream(new DomDriver("UTF-8"));
xstream.processAnnotation(article.class);
xstream.toXML(mesArticles, new FileOutputStream("mes_articles.xml")); 

Voir les commentaires

Rédigé par Bliz

Publié dans #Java

Repost 0

Publié le 6 Février 2012

Aujourd'hui j'ai décidé d'utiliser GWT designer (juste pour tester la dernière version) mais impossible d'afficher l'onglet design.

Eclipse m'affiche une sombre erreur sur xulrunner:

eclipse-error-xulrunner.png

En cherchant un peu dans les logs d'eclipse je trouve:

GWT http-server started at 127.0.0.1:50737
'GDK_NATIVE_WINDOWS' set, won't use WebKit.

Hop, hop, hop! Un petit coup d'oeil dans /usr/bin/eclipse et bingo! ligne 6:

export GDK_NATIVE_WINDOWS=true

Je rajoute un petit # au début de la ligne pour la commenter, je relance eclipse et le tour est joué!!

Par contre je ne sais pas trop à quoi correspond cette variable 'GDK_NATIVE_WINDOWS' ... si quelqu'un a une explication, je suis preneur.

Voir les commentaires

Rédigé par Bliz

Publié dans #GWT

Repost 0

Publié le 3 Février 2012

Aujourd'hui lors d'un INSERT dans une table je rencontre l'erreur suivante:

ERROR 1136 (21S01): Column count doesn't match value count at row 1

Pourtant j'ai beau vérifier tous les champs de la requête : il n'y a aucun problème, ça correspond parfaitement aux champs de la table.

En fait il y avait un trigger de défini sur cette table qui allait automatiquement mettre à jour une autre table (sans rien dire à personne) et l'erreur se trouver dans cette requête cachée.

Enfin pour vérifier les triggers présent sur une table:

SHOW TRIGGERS LIKE 'ma_table'\G

Voir les commentaires

Rédigé par Bliz

Publié dans #MySQL

Repost 0