IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

Bases de données Delphi Discussion :

Drôle de problème avec SQLite et AsFloat


Sujet :

Bases de données Delphi

  1. #1
    Membre éclairé
    Profil pro
    Inscrit en
    Octobre 2002
    Messages
    707
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Octobre 2002
    Messages : 707
    Points : 777
    Points
    777
    Par défaut Drôle de problème avec SQLite et AsFloat
    Bonjour,

    J'ai une BDD SQLite3 sur laquelle je fais une requête qui doit me retourner une valeur réelle (champ "Rating2" dans les ex. ci-dessous).

    Si je fais une requête comme ça, tout va bien:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    with qry do begin
      SQL.Text := 'SELECT Rating2 FROM Matchs WHERE (Date < ''1912-04-01'') AND (Team1 = 9740 OR Team2 = 9740) ORDER BY DATE DESC LIMIT 1';
      Open;
     
      f := Fields[0].AsFloat;
      AddToLog(Format('Rating: %g, [f]));
    end;
    Résultat ok, affiché:
    Rating: 950,640991210938
    Mais si je fais comme ça...

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    with qry do begin
      SQL.Clear;
      SQL.Add('SELECT CASE');
      SQL.Add(' WHEN Team1 = 9740 THEN Rating1');
      SQL.Add(' WHEN Team2 = 9740 THEN Rating2');
      SQL.Add('END');
      SQL.Add('FROM Matchs WHERE (Date < ''1912-04-01'') AND (Team1 = 9740 OR Team2 = 9740)');
      SQL.Add('ORDER BY Date DESC LIMIT 1;');
      Open;
     
      f := Fields[0].AsFloat;
      AddToLog(Format('Rating: %g', [f]));
    end;
    ...j'ai une exception:
    Exception class EConvertError with message ''950.640991210938' is not a valid floating point value'.
    !?!?

    Dans les deux cas, c'est la même valeur de la même ligne qui est recherchée (950.64...), pourquoi est-ce que ça provoque une exception dans le second ? J'ai également essayé avec:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    f := StrToFloat(Fields[0].AsString);
    Pareil

  2. #2
    Expert éminent sénior
    Avatar de Paul TOTH
    Homme Profil pro
    Freelance
    Inscrit en
    Novembre 2002
    Messages
    8 964
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 54
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : Freelance
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Novembre 2002
    Messages : 8 964
    Points : 28 445
    Points
    28 445
    Par défaut
    à mon avis
    Rating1 = 950,640991210938
    Rating2 = 950.640991210938
    
    SQLite3 ne se préoccupant du type des colonnes, tu as du stocker une chaîne contenant une virgule à un moment donné.
    Developpez.com: Mes articles, forum FlashPascal
    Entreprise: Execute SARL
    Le Store Excute Store

  3. #3
    Membre éclairé
    Profil pro
    Inscrit en
    Octobre 2002
    Messages
    707
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Octobre 2002
    Messages : 707
    Points : 777
    Points
    777
    Par défaut
    Justement non !?
    - D'une part mes valeurs sont bien stockées en "AsFloat" dans la table (ce sont des Single à l'origine sous Delphi).
    - Et d'autre part les deux requêtes présentées ci-dessus retournent exactement le même champ de la même ligne !
    Par contre j'ai l'impression que la deuxième requête retourne une chaîne peut-être parce que le "SELECT CASE" introduit une ambiguïté quant au type de données retournées ? (le champ retourné pouvant varier d'une requête à l'autre)
    Une autre preuve que ce n'est pas un problème de virgule/point, c'est que si je fais:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    f := StrToFloat(Fields[0].AsString);
    Ça plante !
    Mais si je fais:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    f := StrToFloat(Copy(Fields[0].AsString, Length(Fields[0].AsString) - 2));
    Ça marche !

    Je vais partir sur la supposition que l'ambiguïté force le retour d'un champ de type STRING et que la conversion ne passe pas parce que la chaîne est trop longue !? Et je vais limiter mes valeurs à 2-3 chiffres après la virgule parce que de toutes façon je n'ai pas besoin de plus.

    Mais si quelqu'un avait une explication, peut-être que je mourrai moins idiot...

  4. #4
    Rédacteur/Modérateur

    Avatar de SergioMaster
    Homme Profil pro
    Développeur informatique retraité
    Inscrit en
    Janvier 2007
    Messages
    15 046
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 67
    Localisation : France, Loire Atlantique (Pays de la Loire)

    Informations professionnelles :
    Activité : Développeur informatique retraité
    Secteur : Industrie

    Informations forums :
    Inscription : Janvier 2007
    Messages : 15 046
    Points : 40 962
    Points
    40 962
    Billets dans le blog
    62
    Par défaut
    je ne connais pas cette BDD mais ? si tu forçais avec un CAST la valeur de retour ?

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    SELECT 
     CAST(CASE 
              WHEN Team1 = 9740 THEN Rating1
              WHEN Team2 = 9740 THEN Rating2
             END 
           AS FLOAT) AS RATING;
    .....
    ou 
    CASE 
      WHEN TEAM1=9740 THEN CAST(rating1 AS FLOAT)
      WHEN TEAM2=9740 THEN CAST(rating2 AS FLOAT)
    END
    bien sur a tester et si le CASTING existe
    MVP Embarcadero
    Delphi installés : D3,D7,D2010,XE4,XE7,D10 (Rio, Sidney), D11 (Alexandria), D12 (Athènes)
    SGBD : Firebird 2.5, 3, SQLite
    générateurs États : FastReport, Rave, QuickReport
    OS : Window Vista, Windows 10, Windows 11, Ubuntu, Androïd

  5. #5
    Membre éclairé
    Profil pro
    Inscrit en
    Octobre 2002
    Messages
    707
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Octobre 2002
    Messages : 707
    Points : 777
    Points
    777
    Par défaut
    CAST existe, les deux formes de la requête fonctionnent (même si je pense que le deuxième reste ambiguë), mais ça plante toujours au moment de la récupération dans Delphi (f := Fields[0].AsFloat). J'ai aussi essayé avec CAST (x AS REAL)

    Ceci ne fonctionne pas non plus:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    SELECT CASE
    WHEN Team1 = 9740 THEN Rating1
    WHEN Team2 = 9740 THEN Rating2
    END * 1.0
    FROM Matchs WHERE (Date < '1912-04-01') AND (Team1 = 9740 OR Team2 = 9740)
    ORDER BY Date DESC LIMIT 1;
    Ni la variante combinée CAST et * 1.0

    J'ai aussi essayé avec ROUND(X, Y) sans plus de succès. Et puis... j'ai réalisé que mon Copy(...) était foireux, j'ai oublié un paramètre, résultat il me coupait le '.' et non la fin de la chaîne comme je le croyait ! Du coup... je m'excuse Paul Toth !!! mais c'est bien toi qui avais raison Et désolé aussi SergioMaster pour t'avoir fait perdre du temps pour rien (enfin j'aurais quand même appris quelque chose avec le CAST).
    Un petit DecimalSeparator := '.' et c'est réglé.
    Problème résolu.

  6. #6
    Membre éclairé
    Profil pro
    Inscrit en
    Octobre 2002
    Messages
    707
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Octobre 2002
    Messages : 707
    Points : 777
    Points
    777
    Par défaut
    Cela dit en passant... ça règle le problème de conversion string -> float, mais ça n'explique pas pourquoi dans un cas SQLite me renvoie une valeur de type float et dans l'autre une valeur de type string. Même quand j'essaye de forcer le type.

  7. #7
    Expert éminent sénior
    Avatar de Paul TOTH
    Homme Profil pro
    Freelance
    Inscrit en
    Novembre 2002
    Messages
    8 964
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 54
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : Freelance
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Novembre 2002
    Messages : 8 964
    Points : 28 445
    Points
    28 445
    Par défaut
    A mon humble avis, vu que Delphi te remonte une erreur de conversion avec une virgule, ta base doit contenir cette virgule.

    Et de toute façon, si tu fais select cast('3.14' as float), cast('3,14' as float) tu obtiens 3.14 et 3.0
    Developpez.com: Mes articles, forum FlashPascal
    Entreprise: Execute SARL
    Le Store Excute Store

  8. #8
    Membre éclairé
    Profil pro
    Inscrit en
    Octobre 2002
    Messages
    707
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Octobre 2002
    Messages : 707
    Points : 777
    Points
    777
    Par défaut
    Non, encore une fois les deux requêtes récupèrent exactement la même valeur, dans la même BDD, dans la même table, la même ligne, la même colonne ! Avec la première requête, pas de problème, je récupère la valeur en AsFloat, quelque soit la valeur de DecimalSeparator. La deuxième provoque une exception.
    D'ailleurs l'exception le montre bien:
    Exception class EConvertError with message ''950.640991210938' is not a valid floating point value'.
    De même quand je fais un CAST avec la deuxième requête, ça plante, alors que ça passe avec la première !
    D'ailleurs quand je visualise mes données avec un outil externe (SQLiteSpy, Navicat), ils m'affichent bien des valeurs avec des points, pas de virgule.
    Enfin dans mon code, je n'ai qu'une procédure qui touche à ces colonnes (Rating1 et Rating2), et je passe bien mes paramètres comme des réels, le code en question:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
     
      with qry do begin
        SQL.Clear;
        SQL.Add('UPDATE Matchs SET');
        SQL.Add('Rating1 = CASE WHEN Team1 = :p0 THEN :p1 ELSE Rating1 END,');
        SQL.Add('Rating2 = CASE WHEN Team2 = :p2 THEN :p3 ELSE Rating2 END');
        SQL.Add('WHERE ID IN (');
        SQL.Add(' SELECT ID');
        SQL.Add(' FROM Matchs');
        SQL.Add(' WHERE (Date <= :p4) AND (Team1 = :p5 OR Team2 = :p6)');
        SQL.Add(' ORDER BY Date DESC LIMIT 1');
        SQL.Add(');');
        StartTransaction;
        for Team in Teams do begin
          Params[0].AsInteger := Team.Id;
          Params[1].AsFloat := Team.Rating;
          Params[2].AsInteger := Team.Id;
          Params[3].AsFloat := Team.Rating;
          Params[4].AsDate := EndDate;
          Params[5].AsInteger := Team.Id;
          Params[6].AsInteger := Team.Id;
          ExecSQL;
        end;
        Commit;
      end;

  9. #9
    Expert éminent sénior
    Avatar de Paul TOTH
    Homme Profil pro
    Freelance
    Inscrit en
    Novembre 2002
    Messages
    8 964
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 54
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : Freelance
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Novembre 2002
    Messages : 8 964
    Points : 28 445
    Points
    28 445
    Par défaut
    oui remarque je me basais sur la , de ton premier résultat alors qu'elle provient de la fonction Format() de Delphi

    et que donne un Fields[0].AsString dans chaque cas ?

    ensuite c'est peut-être un problème au niveau du composant, SQLite propose la fonction sqlite3_column_double() qui est peut-être utilisée dans le premier cas alors que c'est Delphi qui s'occupe de la conversion dans le second cas.
    Developpez.com: Mes articles, forum FlashPascal
    Entreprise: Execute SARL
    Le Store Excute Store

  10. #10
    Membre éclairé
    Profil pro
    Inscrit en
    Octobre 2002
    Messages
    707
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Octobre 2002
    Messages : 707
    Points : 777
    Points
    777
    Par défaut
    Citation Envoyé par Paul TOTH Voir le message
    et que donne un Fields[0].AsString dans chaque cas ?
    Comme tu l'as dit, SQLite ne se préoccupe pas du type de données, en interne en fait tout est sous forme de texte, donc AsString fonctionne dans tous les cas, quelque soit le type "déclaré" de la colonne.

    ensuite c'est peut-être un problème au niveau du composant, SQLite propose la fonction sqlite3_column_double() qui est peut-être utilisée dans le premier cas alors que c'est Delphi qui s'occupe de la conversion dans le second cas.
    Là franchement je ne sais pas. J'utilise les composants Aducom, je vais poser la question sur leur forum.
    Ce qui est ballot finalement c'est que le "bon" programmeur, celui qui pensera à déclarer le DecimalSeparator et les autres paramètres spécifiques à la localisation, ne se rendra pas compte du bug

  11. #11
    Membre émérite Avatar de edam
    Homme Profil pro
    Développeur Delphi/c++/Omnis
    Inscrit en
    Décembre 2003
    Messages
    1 894
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : Maroc

    Informations professionnelles :
    Activité : Développeur Delphi/c++/Omnis
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Décembre 2003
    Messages : 1 894
    Points : 2 771
    Points
    2 771
    Par défaut
    et avec round
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    SELECT 
    CASE 
              WHEN Team1 = 9740 THEN round(Rating1)
              WHEN Team2 = 9740 THEN round(Rating2)
             END 
            AS RATING;
    simplement pour tester
    PAS DE DESTIN, C'EST CE QUE NOUS FAISONS

  12. #12
    Membre éclairé
    Profil pro
    Inscrit en
    Octobre 2002
    Messages
    707
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Octobre 2002
    Messages : 707
    Points : 777
    Points
    777
    Par défaut
    Déjà essayé.

    Que ce soit...
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    SQL.Clear;
    SQL.Add('SELECT Round(CASE');
    SQL.Add(' WHEN Team1 = 9740 THEN Rating1');
    SQL.Add(' WHEN Team2 = 9740 THEN Rating2');
    SQL.Add('END)');
    SQL.Add('FROM Matchs WHERE (Date < ''1912-04-01'') AND (Team1 = 9740 OR Team2 = 9740)');
    SQL.Add('ORDER BY Date DESC LIMIT 1;');
    ou...
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    SQL.Clear;
    SQL.Add('SELECT CASE');
    SQL.Add(' WHEN Team1 = 9740 THEN Round(Rating1)');
    SQL.Add(' WHEN Team2 = 9740 THEN Round(Rating2)');
    SQL.Add('END');
    SQL.Add('FROM Matchs WHERE (Date < ''1912-04-01'') AND (Team1 = 9740 OR Team2 = 9740)');
    SQL.Add('ORDER BY Date DESC LIMIT 1;');
    -> Exception sur "Fields[0].AsFloat" !

  13. #13
    Expert éminent sénior
    Avatar de Paul TOTH
    Homme Profil pro
    Freelance
    Inscrit en
    Novembre 2002
    Messages
    8 964
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 54
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : Freelance
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Novembre 2002
    Messages : 8 964
    Points : 28 445
    Points
    28 445
    Par défaut
    d'où la question, que retourne FieldS[0].AsString dans les deux cas, avec un point ou une virgule ?

    et que retourne Fields[0].ClassName ? tu verras bien si tu as un TFloadField d'un côté et un TStringField de l'autre.
    Developpez.com: Mes articles, forum FlashPascal
    Entreprise: Execute SARL
    Le Store Excute Store

  14. #14
    Membre éclairé
    Profil pro
    Inscrit en
    Octobre 2002
    Messages
    707
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Octobre 2002
    Messages : 707
    Points : 777
    Points
    777
    Par défaut
    Fields[0].AsString retourne une chaîne avec un point dans les deux cas (la valeur a changé depuis le lancement du sujet, mais le problème reste le même).
    AddToLog(Fields[0].AsString) -> 938.568237304688
    Fields[0].ClassName retourne un TFloatField dans le premier cas, un TStringField dans le deuxième cas, quelque soit la "transformation" faite sur le SELECT (CAST, ROUND, *1.0).

  15. #15
    Expert éminent sénior
    Avatar de Paul TOTH
    Homme Profil pro
    Freelance
    Inscrit en
    Novembre 2002
    Messages
    8 964
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 54
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : Freelance
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Novembre 2002
    Messages : 8 964
    Points : 28 445
    Points
    28 445
    Par défaut
    Citation Envoyé par GoustiFruit Voir le message
    Fields[0].AsString retourne une chaîne avec un point dans les deux cas (la valeur a changé depuis le lancement du sujet, mais le problème reste le même).


    Fields[0].ClassName retourne un TFloatField dans le premier cas, un TStringField dans le deuxième cas, quelque soit la "transformation" faite sur le SELECT (CAST, ROUND, *1.0).
    et bien tu sais maintenant pourquoi ça plante dans le second cas, TStringField fait un StrToFloat qui utilise le DecimalSeparator alors que TFloatField récupère directement la valeur retournée par SQLite.

    reste à savoir pourquoi dans le second cas le champ est détecté comme un String...et j'ai la réponse

    The sqlite3_column_type() routine returns the datatype code for the initial data type of the result column. The returned value is one of SQLITE_INTEGER, SQLITE_FLOAT, SQLITE_TEXT, SQLITE_BLOB, or SQLITE_NULL. The value returned by sqlite3_column_type() is only meaningful if no type conversions have occurred as described below. After a type conversion, the value returned by sqlite3_column_type() is undefined. Future versions of SQLite may change the behavior of sqlite3_column_type() following a type conversion.
    ça veux dire que SQLite ne donne aucune information sur le type de données retournée, et Delphi se contente du type String.
    Developpez.com: Mes articles, forum FlashPascal
    Entreprise: Execute SARL
    Le Store Excute Store

  16. #16
    Membre éclairé
    Profil pro
    Inscrit en
    Octobre 2002
    Messages
    707
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Octobre 2002
    Messages : 707
    Points : 777
    Points
    777
    Par défaut
    Citation Envoyé par Paul TOTH Voir le message
    et bien tu sais maintenant pourquoi ça plante dans le second cas, TStringField fait un StrToFloat qui utilise le DecimalSeparator alors que TFloatField récupère directement la valeur retournée par SQLite.
    Ça je l'avais déjà bien compris, restait à comprendre pourquoi dans un cas et pas dans l'autre...

    reste à savoir pourquoi dans le second cas le champ est détecté comme un String...et j'ai la réponse
    Il reste une part d'inexpliqué, puisque quand je ne fais aucune conversion (aucune fonction additionnelle sur le SELECT, uniquement utilisation du "CASE") ça me retourne quand même un type undefined. Ça me paraît aller à l'encontre de ça:
    The sqlite3_column_type() routine returns the datatype code for the initial data type of the result column.

  17. #17
    Expert éminent sénior
    Avatar de Paul TOTH
    Homme Profil pro
    Freelance
    Inscrit en
    Novembre 2002
    Messages
    8 964
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 54
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : Freelance
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Novembre 2002
    Messages : 8 964
    Points : 28 445
    Points
    28 445
    Par défaut
    vu que le case peut retourner différentes colonnes de différents type, il y a potentiellement une conversion...ça ne me choque pas que le "undefined" se présente également dans ce cas.
    Developpez.com: Mes articles, forum FlashPascal
    Entreprise: Execute SARL
    Le Store Excute Store

  18. #18
    Membre éclairé
    Profil pro
    Inscrit en
    Octobre 2002
    Messages
    707
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Octobre 2002
    Messages : 707
    Points : 777
    Points
    777
    Par défaut
    Allez, résolu définitivement.

+ Répondre à la discussion
Cette discussion est résolue.

Discussions similaires

  1. problème avec sqlite
    Par nagca dans le forum Android
    Réponses: 1
    Dernier message: 08/06/2011, 10h08
  2. Réponses: 4
    Dernier message: 25/06/2010, 17h05
  3. Problème avec Sqlite lors de la compilation
    Par Jiyuu dans le forum Déploiement/Installation
    Réponses: 6
    Dernier message: 28/11/2009, 18h32
  4. [C#]problème avec SqLite
    Par ClaudeBg dans le forum Linq
    Réponses: 8
    Dernier message: 18/06/2009, 16h17
  5. Problème avec SQLITE
    Par Jiyuu dans le forum Django
    Réponses: 2
    Dernier message: 12/03/2009, 07h07

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo