+ Répondre à la discussion
Affichage des résultats 1 à 9 sur 9
  1. #1
    Membre du Club
    Profil pro
    Inscrit en
    septembre 2002
    Messages
    158
    Détails du profil
    Informations personnelles :
    Âge : 45
    Localisation : France, Indre et Loire (Centre)

    Informations forums :
    Inscription : septembre 2002
    Messages : 158
    Points : 60
    Points
    60

    Par défaut insert DBGrid avec TADOQuery et requete avec jointure

    Bonjour,

    J'ai un TAdoQuery avec une requete avec jointure.

    La tentative d'insertion échoue.
    Je peux prendre la mise à jour sur le beforePost et mettre à jour moi même les tables concernées et faire un Dataset.Cancel ou un Abort, mais même si je fais un Dataset.Refresh à la fin du BeforePost, la grille ne se met pas à jour.

    Quelle est la meilleure démarche afin d'insérer des données à partir d'une grille liée à une requête avec jointure ?
    Quel événement utiliser? Comment mettre à jour afin que cela soit transparent pour l'utilisateur ?

    Je sais que ADO se débrouille pas mal pour faire l'update sur une requete jointe en envoyant plusieurs update dans les tables qui vont biens.

    Merci de vos conseils

  2. #2
    Expert Confirmé Sénior Avatar de ShaiLeTroll
    Homme Profil pro
    Développeur C++\Delphi
    Inscrit en
    juillet 2006
    Messages
    10 086
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 33
    Localisation : France

    Informations professionnelles :
    Activité : Développeur C++\Delphi
    Secteur : High Tech - Électronique et micro-électronique

    Informations forums :
    Inscription : juillet 2006
    Messages : 10 086
    Points : 14 325
    Points
    14 325

    Par défaut

    Quel est le message d'erreur si il y en a un ?
    As-tu fournis dans la jointure toutes les clés primaires de chacune des tables ?
    Je ne pratique que très rarement la saisie directement dans les grilles car cela manque de confort pour la saisie en masse de donnée tout au clavier
    Lors que je l'ai fait, j'ai utilisé un TClientDataSet sur des MKQuery, ou via le TSimpleDataSet de DBExpress, les deux utilisant un cache qui me permettait de générer moi même les requêtes dans le BeforeApplyUpdates ou manuellement par l'échange du Data sous forme de Variant entre une DLL IHM et une DLL Métier

    Je ne comprend pas pourquoi en ADO ou DBExpress, il n'y existe pas un équivalent de TUpdateSQL qui permettait de définir précisément les requêtes surtout si la jointure récupère par exemple 3 tables et que l'on souhaite qu'en mettre qu'une ou deux à jour !
    Si quelqu'un connait la bonne pratique cela m'intéresse !
    Personnellement, j'ai abandonné, depuis 2004, le Insert()\Post() direct au profil de SQL généré à la volée par une couche d'objet persistant !
    Cela permet de maîtriser chaque requête SQL qui sont émises par le programme !
    Aide via F1 - FAQ - Guide du développeur Delphi devant un problème - Pensez-y !
    Attention Troll Méchant !
    "Quand un homme a faim, mieux vaut lui apprendre à pêcher que de lui donner un poisson" Confucius
    Mieux vaut se taire et paraître idiot, Que l'ouvrir et de le confirmer !
    L'ignorance n'excuse pas la médiocrité !

    L'expérience, c'est le nom que chacun donne à ses erreurs. (Oscar Wilde)
    Il faut avoir le courage de se tromper et d'apprendre de ses erreurs

  3. #3
    Membre du Club
    Profil pro
    Inscrit en
    septembre 2002
    Messages
    158
    Détails du profil
    Informations personnelles :
    Âge : 45
    Localisation : France, Indre et Loire (Centre)

    Informations forums :
    Inscription : septembre 2002
    Messages : 158
    Points : 60
    Points
    60

    Par défaut

    Voici le message d'erreur si je le laisse faire, ce qui est bien compréhensible car il ne sais pas ce que je dois faire.

    Impossible d'insérer la valeur null dans la colonne 'N_IDGAMME', table GAMMEMERE. Cette colonne n'accepte pas les valeurs NULL. Echec de INSERT
    Mon but est de faire comme dans le BeforeApplyUpdate mais avec les ADO.

    Dans mon exemple, j'ai 2 tables (.
    Une table GAMME avec IdGamme et LibGamme
    Une autre GAMMEMERE avec IdGamme, IdGammeMere et Ordre (Les 2 Id étant des FK de Gamme).
    Une gamme mère contient des gammes dans un ordre précisé.

    La requete étant
    Code :
    1
    2
    3
    4
    5
    6
    7
    8
    9
    select 
      GAMME.IdGamme,
      GAMME.LibGamme,  
      GAMMEMERE.ORDRE, 
      GAMMEMERE.idGAMMEMERE
    from GAMME
      join GAMMEMERE on GAMME.IDGAMME = GAMMEMERE.IDGAMME
    where GAMMEMERE.idGAMMEMERE = :Gamme
    order by ORDRE
    Mon but étant d'insérer des gammes pour la gamme mère passée en paramètre et donc 2 insert dans l'ordre gamme, gammemere.
    Dans événement NewRecord ou BeforePost, je met à jour le IdGammeMere.

    Code :
    1
    2
    3
    insert into gamme (LibGamme) values (AdoQLibGamme.Value)
    Récupérer Id de la séquence
    insert into gammeMere (IdGammeMere, IdGamme, ordre) values (AdoQIdGammeMere.Value, Id recup, ordre)
    Ce qu'il essai de faire
    insert into gamme (LibGamme) values (AdoQLibGamme.Value)
    insert into gammeMere (IdGammeMere, IdGamme, ordre) values (AdoQIdGammeMere.Value, NULL, ordre)
    ADO ne récupère pas l'Id pour le mettre dans l'insertion suivante.

    Je peux faire les insertions dans le beforePost mais il faut que j'annule les modification qu'il s’apprête à faire par un Dataset.Cancel et un Abort pour sortir. La grille se remet comme avant la saisie et ne reflète plus les modifications apportées dans la base.

    PS : la dbGrid est une QuantumGrid

  4. #4
    Expert Confirmé Sénior Avatar de ShaiLeTroll
    Homme Profil pro
    Développeur C++\Delphi
    Inscrit en
    juillet 2006
    Messages
    10 086
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 33
    Localisation : France

    Informations professionnelles :
    Activité : Développeur C++\Delphi
    Secteur : High Tech - Électronique et micro-électronique

    Informations forums :
    Inscription : juillet 2006
    Messages : 10 086
    Points : 14 325
    Points
    14 325

    Par défaut

    Je crois qu'il n'y arrive jamais !
    Comment veux-tu qu'il devine que tu veux utiliser la valeur du dernirer AutoInc de GAMME pour l'utiliser comme valeur pour GAMMEMERE !
    Certe les noms de champ sont les mêmes mais cela supposerait de la couche ADO une analyse de ton modèle de donnée, faut pas abuser !

    En plus, tu ne fournis pas "GAMMEMERE.IDGAMME" dans le SELECT, pour lui ce champ ne peut pas avoir de Valeur !
    Essaye de voir si en ajoutant ce champ dans le SELECT, ADO fasse un miracle, je n'y crois pas trop !

    Perso, je n'ai jamais fait confiance au mise à jour automatique de jointure !
    J'évite ce type de procédé, déjà que l'IDE cela rend flémard les développeurs mais là !
    De toute façon, c'est contraire une architecture MVC et une conception POO avec une couche Metier !

    A mon avis tu ne peux que le faire manuellement car il te faut intercepter la valeur entre deux requêtes !
    Aide via F1 - FAQ - Guide du développeur Delphi devant un problème - Pensez-y !
    Attention Troll Méchant !
    "Quand un homme a faim, mieux vaut lui apprendre à pêcher que de lui donner un poisson" Confucius
    Mieux vaut se taire et paraître idiot, Que l'ouvrir et de le confirmer !
    L'ignorance n'excuse pas la médiocrité !

    L'expérience, c'est le nom que chacun donne à ses erreurs. (Oscar Wilde)
    Il faut avoir le courage de se tromper et d'apprendre de ses erreurs

  5. #5
    Membre du Club
    Profil pro
    Inscrit en
    septembre 2002
    Messages
    158
    Détails du profil
    Informations personnelles :
    Âge : 45
    Localisation : France, Indre et Loire (Centre)

    Informations forums :
    Inscription : septembre 2002
    Messages : 158
    Points : 60
    Points
    60

    Par défaut

    Le fait de rajouter l'IdGamme de la GammeMere ne change rien.

    Il y a une intelligence dans l'ADO qui permet de mettre à jour plusieurs tables par l'envoi de plusieurs requêtes.
    Exemple de l'update (Code généré par l'ADO intercepté par profiler).
    Code :
    1
    2
    UPDATE "GAMME" SET "LIBGAMME"=@P1 WHERE "IdGamme"=@P2 AND "LIBGAMME"=@P3
    UPDATE "GAMMEMERE" SET "ORDRE"=@P1 WHERE "IdGamme"=@P2 AND "ORDRE"=@P3 AND "idGAMMEMERE"=@P4
    Ce lien montre l'utilisation un peu plus poussée de ADO
    Working with ADO

    Je suis d'accord avec toi qu'il ne faut pas trop lui en demander et c'est pourquoi je met à jour la base dans l'événement BeforePost et fais un Dataset.Cancel puis je réactualise avec Close, Open.
    C'est pas Top mais çà fonctionne.
    C'est surtout pour savoir si d'autre personnes font différemment. Depuis le temps que çà existe, d'autres personnes ont dû résoudre le problème. Toute suggestion est bonne à prendre

    Merci pour tes réponses.

  6. #6
    Expert Confirmé Sénior Avatar de ShaiLeTroll
    Homme Profil pro
    Développeur C++\Delphi
    Inscrit en
    juillet 2006
    Messages
    10 086
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 33
    Localisation : France

    Informations professionnelles :
    Activité : Développeur C++\Delphi
    Secteur : High Tech - Électronique et micro-électronique

    Informations forums :
    Inscription : juillet 2006
    Messages : 10 086
    Points : 14 325
    Points
    14 325

    Par défaut

    Ta méthode est un classique, je pense l'avoir fait plusieurs fois lors que j'ai maitenu des écrans existants !
    Sinon, j'utilisais massivement le TClientDataSet, pour présenter les données dans une TDBCtrlGrid ou TDBGrid pour l'utilisateur alors que le stockage était différent (DB orientée colonnes et non par ligne à cause d'une structure polymorphe des enregistrements !)

    Donc en utilisant TClientDataSet, tu as les données en mémoire, tu peux faire le Post() sans devoir faire un Refresh !
    Et tu gères le SQL à la main !
    Pour utiliser le TClientDataSet, tu as aussi ajouté un TDataSetProvider relié sur le ADOQuery !
    Dans TDataSetProvider.BeforeUpdateRecord, tu peux capturer l'envoie du Post, le faire toi même en SQL, en affectant true à Applied, cela considère que le Post a été effectué !

    C'est une méthode propre et classique de l'utilisation d'un cache !
    Le problème c'est que cela consomme plus de mémoire à l'ouverture de la Query et que c'est plus lent puisque cela copie les enregistrements dans le cache !
    Mais bon, cela reste supportable, surtout avec les machines que l'on a maintenant !
    Et puis, tu ne dois pas avoir un volume de données importants < 1000 !

    Tu noteras quand même que les requêtes d'ADO ne sont pas terrible non ?
    les WHERE des UPDATE utilisent TOUS Les champs du Select, alors qu'il suffit de IdGamme pour GAMME et idGAMMEMERE+IdGamme pour GAMMEMERE
    Si tu as 20 champs, va-t-il pondre un WHERE de 20 champs alors qu'il a les clés primaires uniques ?
    Je n'ai pas assez utilisé ADO pour le savoir mais je le crainds car je l'ai vu dans d'autres lib DB !

    Dans le genre "intelligence", cela reste faible, aucune analyse réel du modèle, juste, un algo qui reprend tous les champs en présence de chacune des tables !
    C'est d'ailleurs pour cela que cela fasse des erreurs genre "Erreur de mise à jour en plusieurs étapes" parce que le WHERE a renvoyé plusieurs enregistrements ou même parfois aucun, car un autre client concurrent à déjà modifier les valeurs !
    Aide via F1 - FAQ - Guide du développeur Delphi devant un problème - Pensez-y !
    Attention Troll Méchant !
    "Quand un homme a faim, mieux vaut lui apprendre à pêcher que de lui donner un poisson" Confucius
    Mieux vaut se taire et paraître idiot, Que l'ouvrir et de le confirmer !
    L'ignorance n'excuse pas la médiocrité !

    L'expérience, c'est le nom que chacun donne à ses erreurs. (Oscar Wilde)
    Il faut avoir le courage de se tromper et d'apprendre de ses erreurs

  7. #7
    Membre du Club
    Profil pro
    Inscrit en
    septembre 2002
    Messages
    158
    Détails du profil
    Informations personnelles :
    Âge : 45
    Localisation : France, Indre et Loire (Centre)

    Informations forums :
    Inscription : septembre 2002
    Messages : 158
    Points : 60
    Points
    60

    Par défaut

    L'utilisation du Provider et ClientDataset pourrait en effet me permettre de faire comme si nous étions dans une seule table et gérer l'envoi des requêtes comme je le fais dans le BeforePost/BeforeDelete. L'avantage est que je ne recharge pas le query à chaque insertion/suppression.
    Comme je n'ai pas beaucoup d'enregistrements enfants, le fait de recharger ne prend pas beaucoup de temps et permet de retrié suivant l'ordre.

    Le fait que l'ADO renvoi tous les champs dans la clause where est sensé permettre de détecter une modification par une autre personne de l'enregistrement entre temps. Le système de reconcile devrait permettre de demander à l'utilisateur ou suivant une règle, si on écrase les modifications ou autres. Dans le DataSetProvider, on peut définir s'il recherche la mise à jour sur tous les champs ou les champs clefs avec la propriété UpdateMode et les champs qui feront parti de la clause where avec le ProviderFlags.(Je n'ai jamais utilisé)

    Tout gérer à la main c'est mieux à condition de savoir ce qu'on fait et c'est pourquoi il vaux mieux être conseillé par ceux qui savent.
    Merci pour tes conseils.

  8. #8
    Candidat au titre de Membre du Club
    Inscrit en
    décembre 2009
    Messages
    14
    Détails du profil
    Informations forums :
    Inscription : décembre 2009
    Messages : 14
    Points : 13
    Points
    13

    Par défaut

    Bonjour;

    @ShaiLeTroll cette fois ci je n'est que quelques mois de retard

    La solution adopté par ADO est d'utiliser le paramètre 'Unique Table' pour spécifier la table dont vous voulez appliquer les modifs, exemple:
    J'ai une jointure entre deux tables: Produit et Facture, et je veux que les Modifs s'applique uniquement dans la table Produit:
    Code :
    1
    2
    3
    4
    procedure TForm1.ADODataSet1AfterOpen(DataSet: TDataSet);
    begin
      //Tell Recordset wich table need to focus
      TCustomADODataSet(DataSet).Properties['Unique Table'].Value := 'Produit';
    Cela permet au moteur (Engine) d'envoyer une seule requête de MAJ au lieu deux requêtes.

    Vous pouvez appliquer ce paramètre dans OnAfterOpen, mais vous serriez obliger de ré-exécuter le même code dans "avant suppression" OnBeforeDelete, puisque l'objet perd la valeur de ce paramètre lors la suppression.

    en savoir plus sur les propriétés dynamique ADO.

    NB.: Utiliser le composant TADODataSet au lieu TADOQuery et TADOTable, ces deux derniers ont été crées pour des raisons de compatibilité avec BDE selon la doc sur Embarcadero.

    Le composant TBetterADODataSet support les paramètres dynamiques ADO.

  9. #9
    Expert Confirmé Sénior Avatar de ShaiLeTroll
    Homme Profil pro
    Développeur C++\Delphi
    Inscrit en
    juillet 2006
    Messages
    10 086
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 33
    Localisation : France

    Informations professionnelles :
    Activité : Développeur C++\Delphi
    Secteur : High Tech - Électronique et micro-électronique

    Informations forums :
    Inscription : juillet 2006
    Messages : 10 086
    Points : 14 325
    Points
    14 325

    Par défaut

    Ce 'Unique Table' est mentionné dans l'article fourni par Gudul Working with ADO, j'ai écarté cette piste pensant que Gudul l'avait déjà exploité sans succès vu que le problème était la mise à jour de deux tables en séquence c'est à dire une première table "gamme" puis la mise à jour de "gammeMere" avec l'AutoInc de "gamme" et non la mise à jour d'une seule table de la jointure !

    En Sybase dans un seul TSQLQuery (DBExpress)

    Code :
    1
    2
    3
    INSERT INTO gamme (LibGamme) VALUES (AdoQLibGamme.Value)
    SELECT @@Identity as GammeIdentity; 
    INSERT INTO gammeMere (IdGammeMere, IdGamme, ordre) VALUES (AdoQIdGammeMere.Value, GammeIdentity, ordre);

    ADO, je l'ai utilisé une fois avec Oracle, cela ne m'a pas donné envie de poursuivre
    Je me suis fait un mini-explorateur SQL en utilisant justement ADO plus par curiosité et surtout pour comparer les performances avec DBExpress et MyDAC sur MySQL

    j'ai utilisé BDE+Paradox, IBX+IB, ApolloEngine+Dbase et surtout MyDac+MySQL, donc systématiquement une lib "dédiée" à la DB ciblée apportant de meilleure performance et un accès immédiat aux fonctionnalités du SGBD

    Si l'on ne prévoit pas un support de multi-DB, l'utilisation de provider générique me gêne un peu, surtout si le code ne prévoit pas une gestion dynamique du SQL (un standard dont chaque SGBD a son spécifique )

    J'utilise DBExpress et ASA Sybase depuis 2 ans, je suis content de n'avoir jamais eu besoin de fouiller dans les options pour le moment !
    Aide via F1 - FAQ - Guide du développeur Delphi devant un problème - Pensez-y !
    Attention Troll Méchant !
    "Quand un homme a faim, mieux vaut lui apprendre à pêcher que de lui donner un poisson" Confucius
    Mieux vaut se taire et paraître idiot, Que l'ouvrir et de le confirmer !
    L'ignorance n'excuse pas la médiocrité !

    L'expérience, c'est le nom que chacun donne à ses erreurs. (Oscar Wilde)
    Il faut avoir le courage de se tromper et d'apprendre de ses erreurs

Liens sociaux

Règles de messages

  • Vous ne pouvez pas créer de nouvelles discussions
  • Vous ne pouvez pas envoyer des réponses
  • Vous ne pouvez pas envoyer des pièces jointes
  • Vous ne pouvez pas modifier vos messages
  •