Debian-facile

Bienvenue sur Debian-Facile, site d'aide pour les nouveaux utilisateurs de Debian.

Vous n'êtes pas identifié(e).

#1 09-08-2013 16:48:54

Lunatic
Membre
Lieu : Lyon
Distrib. : Fedora 24
Noyau : Linux 4.6.5-300.fc24.x86_64
(G)UI : Gnome
Inscription : 03-08-2013
Site Web

[Résolu] Python et SQL : questions d'un novice

Salut à tou·te·s,

Depuis le début de la semaine, je m'amuse à apprendre Python. Pour vous donner une idée de mon (faible) niveau : je connais quelques rudiments de programmation pour avoir fait un peu de PHP/SQL voici 7 ou 8 ans, et là, j'en suis à la partie 3 (sur 5) du tuto du site du Zéro. Je ne suis pas encore au clair avec les questions de décorateurs et les subtilités de la POO… tongue

Bref, avant de me lancer dans la POO justement, j'ai voulu m'essayer à la mise en œuvre d'un petit truc que je voulais faire depuis longtemps. Alors avant de détailler, je précise : j'y suis parvenu, mais je ne suis pas certain que mon code soit le plus « intelligent » (/optimisé), et je serais donc curieux d'avoir les avis/suggestions de personnes plus spécialistes que moi pour me dire quelles pratiques je pourrais adopter.

Ce « petit truc » concerne Zotero. Pour ceux qui ne connaissent pas, Zotero est un logiciel de gestion de références bibliographiques, très utilisé dans le monde universitaire. Une des fonctions de Zotero, c'est les « connexes » : c'est un petit outil qui permet de dire « telle référence a un lien avec telle autre référence » (seul l'utilisateur décide quelle est la nature de ce lien. Par exemple, moi j'utilise ça quand un livre / un article cite un autre livre / article que j'ai dans ma base). Je trouve qu'il peut être utile de représenter graphiquement ces relations. Ça permet de mettre en évidence que telle référence est très liée à d'autres références, et ça peut donc illustrer le fait qu'elle est beaucoup citée. Or, cette représentation graphique, Zotero ne la propose pas, et c'est donc ça que je bidouille.

Pour détailler davantage : Zotero stocke ses données dans une base sqlite. Y'a trois tables qui nous intéressent ici :
- la table itemSeeAlso qui enregistre tous les « connexes ». Concrètement, c'est deux colonnes qui comportent l'ID des deux références « connectées »
- la table itemData : elle contient l'ID de tous les champs associés à une référence, et le type de champ. Elle a donc trois colonnes : l'ID de la référence bibliographique, l'ID d'un champ qui lui est associé, et le type de champ dont il s'agit. Par exemple, le champ de type « titre » sont identifiables par le type 110
- enfin la table itemDataValues, qui contient l'ID de tous les champs de toutes les références, et leur valeur (2 colonnes, donc).

J'ai procédé de la sorte (comme vous pouvez le voir dans le code ci-dessous) : je récupère dans la table itemSeeAlso les ID des références ayant des connexes. Je crée un dictionnaire ayant pour clé cet ID et pour valeur, rien (variable ref_dic). Le but étant qu'à terme, la valeur soit le titre de la référence. Ensuite, je récupère les ID de champ de type 110 (= le titre) de toutes les références de ma base. Je trouve ça un peu débile puisque du coup je récupère aussi ceux qui appartiennent à des références qui n'ont aucun « connexe » (et c'est la majorité) ! Je les place dans un dictionnaire ayant pour clé l'ID de la référence, et pour valeur l'ID du champ titre. Enfin, je parcours ce dictionnaire et lorsque je tombe sur un ID de référence qui est également une clé de ref_dic, alors ref_dic prend pour valeur l'ID du champ de titre. Et pour finir, à partir de ref_dic qui ressemble donc à ref_dic[ID de la référence] = ID du champ titre associé. J'effectue une nouvelle requête pour chercher le titre lui-même.

Voilà, merci d'avoir tout lu ! tongue Je pense que malgré la longueur du message vous voyez bien la logique du truc. Et donc ça ne me plaît qu'à moitié¹ puisque j'utilise pas moins de 5 boucles avant d'aboutir au résultat souhaité (l'association entre l'ID d'une référence et son titre), et qu'en plus je suis obligé de passer par la récupération de plein de données dont je n'ai pas besoin. Je suppute que l'optimisation peut se faire autant du côté de Python que du côté SQL…

(¹ à moitié seulement parce que ça reste très agréable de voir qu'en quelques jours, avec un minimum de connaissance, Python m'a permis d'aboutir à ce que je voulais faire…)

Le code en question :

#! usr/bin/python
# -*- coding: utf_8 -*-

import sqlite3
import pygraphviz

conn = sqlite3.connect("/tmp/zotero.sqlite")
c = conn.cursor()

relations_liste = list()
ref_dic = dict()
id_champ = dict()
l = list()

# Données de la table itemSeeAlso contenant les références connexes
# relations_liste est une liste de paires de tuple
for row in c.execute("SELECT * FROM itemSeeAlso"):
  relations_liste.append(row)

# Dictionnaire des références ayant des connexes
# L'id de la ref est la clé du dico
for ref1, ref2 in relations_liste:
  if ref1 not in ref_dic.keys():
    ref_dic[ref1] = ""
  if ref2 not in ref_dic.keys():
    ref_dic[ref2] = ""
   

# La table itemData contient les ID de tous les champs
# d'une référence et leur type. On ne s'intéresse qu'au type
# 110 qui correspond au titre de la référence
# id_champ a pour clef l'ID de la référence et pour valeur
# l'ID de son champ de titre
for row in c.execute("SELECT itemID, valueID \
FROM itemData WHERE fieldID=110"):
    id_champ[row[0]] = row[1]

# On complète notre dictionnaire de références pour y placer en valeur
# l'ID du champ de titre, provenant du dictonnaire id_champ
for cle in id_champ.keys():
  if cle in ref_dic.keys():
    ref_dic[cle] = id_champ[cle]

# Maintenant que ref_dic comporte l'ID de la référence (en clé) et l'ID
# du champ titre (en valeur), on récupère ce titre depuis la table
# itemDataValues
# Au final, ref_dic conserve l'ID de la référence en clé, son titre en
# valeur
for row in c.execute("SELECT valueID, value FROM itemDataValues"):
    for cle, valeur in ref_dic.items():
      if row[0] == valeur:
        ref_dic[cle] = row[1]

# On utilise notre liste des relations pour générer le code pour graphviz
graph = pygraphviz.AGraph()
for ref1, ref2 in relations_liste:
  titre_ref1 = ref_dic[ref1]
  titre_ref2 = ref_dic[ref2]
  graph.add_edge(titre_ref1, titre_ref2)

graph.layout()
graph.draw("/tmp/test.png")



Merci wink

Dernière modification par Lunatic (13-08-2013 10:58:28)


Je suis aussi sur Twitter et nouvellement sur Diaspora*
Mon blog de geekeries : HAL-9000

(J'applique la règle de proximité)

Hors ligne

#2 10-08-2013 09:21:49

paskal
autobahn
Lieu : ailleurs
Inscription : 14-06-2011
Site Web

Re : [Résolu] Python et SQL : questions d'un novice

Comme disait un mec avec qui je ne suis pas vraiment pote :

le mec en question a écrit :

Un bon croquis vaut mieux qu'un long discours


Pourrais-tu reproduire 2, 3 lignes de chaque table, avec les liens attendus si possible (tu comprends ... mes vieux neurones roll  )


I'd love to change the world
But I don't know what to do
So I'll leave it up to you...

logo-sur-fond.png

Hors ligne

#3 10-08-2013 10:42:09

Lunatic
Membre
Lieu : Lyon
Distrib. : Fedora 24
Noyau : Linux 4.6.5-300.fc24.x86_64
(G)UI : Gnome
Inscription : 03-08-2013
Site Web

Re : [Résolu] Python et SQL : questions d'un novice

Ouaip, j'espère qu'avec ces captures d'écran ce sera plus clair :

La table ItemSeeAlso : elle enregistre les références bibliographiques (plus précisément, leur ID) reliée. Par exemple on voit sur les deux premières lignes que ma référence portant l'ID 11 est « connexe » à la fois de la 13 et de la 14. C'est donc cette table qui comprend l'ensemble des références qui m'intéressent (= celles qui ont un « connexe »).

z6h.png

La table itemData : la première colonne désigne l'ID de toutes les références enregistrées dans ma base (donc y compris celles qui n'ont pas de « connexe »). La seconde colonne indique un type de champ : 110, c'est un champ de type « titre ». La troisième colonne enregistre l'ID du champ correspondant. On voit donc ici que ma référence 4 a un titre enregistré dans un champ portant pour ID le 11. De la même manière, ma référence 7 a pour titre la valeur d'un champ ayant pour ID 22.

b39a.png

Enfin, la table ItemDataValues. On y trouve la valeur de tous les champs de toutes les références. On voit donc que l'ID 11 (précédemment trouvé) a pour valeur « Médicaments et société » ; le 22, « Le corps et ses constructions symboliques ».

q0qt.png

L'idée est donc de partir des relations de la première table : (11, 13); (11, 14); (244, 99); etc. pour arriver à des relations affichant le titre ("Titre de la ref 11"; "Titre de la ref 13); ("Titre de la ref 11"; "Titre de la ref 14"), etc.

Si je ne suis pas suffisamment clair, dis-le moi.
Et merci de te pencher sur mon cas.


Edit : un petit schéma qui, tu m'en excuseras, ne respecte sûrement pas les conventions du genre !

1376125174.png

Dernière modification par Lunatic (10-08-2013 11:05:43)


Je suis aussi sur Twitter et nouvellement sur Diaspora*
Mon blog de geekeries : HAL-9000

(J'applique la règle de proximité)

Hors ligne

#4 10-08-2013 11:25:19

johan
Membre
Lieu : Montpellier
Distrib. : Debian Wheezy Vers 7.1
Noyau : Linux 3.2.0-4-amd64
(G)UI : Gnome
Inscription : 29-07-2013

Re : [Résolu] Python et SQL : questions d'un novice

Bonjour,

On regardant l'ensemble des informations que tu nous as fournis (Schéma très bien, voire très clair).

Pourquoi au niveau de ta requete SQL tu n'utilises pas des jointures pour afficher les valeurs et un GROUP BY pour le regroupement des titres (http://www.tutorialspoint.com/sqlite/sq … oup_by.htm) ?

Au final Python te servira pour l'affichage des informations.

++

Dernière modification par johan (10-08-2013 16:03:48)

Hors ligne

#5 10-08-2013 11:44:49

paskal
autobahn
Lieu : ailleurs
Inscription : 14-06-2011
Site Web

Re : [Résolu] Python et SQL : questions d'un novice

+1 pour la clarté et pour les jointures. smile

Par contre, pour que les jointures apportent un plus, il faut que la base s'y prête, c'est-à-dire qu'elle soit préparée pour, non ?

I'd love to change the world
But I don't know what to do
So I'll leave it up to you...

logo-sur-fond.png

Hors ligne

#6 10-08-2013 12:07:24

Lunatic
Membre
Lieu : Lyon
Distrib. : Fedora 24
Noyau : Linux 4.6.5-300.fc24.x86_64
(G)UI : Gnome
Inscription : 03-08-2013
Site Web

Re : [Résolu] Python et SQL : questions d'un novice

Merci pour les réponses smile

Bon, après quelques tests avec sqlitebrowser, j'obtiens ce que je veux avec cette requête smile

SELECT itemData.itemID, ItemDataValues.value FROM itemData jOIN itemSeeAlso ON (itemSeeAlso.itemID = itemData.itemID OR itemSeeAlso.linkedItemID = itemData.itemID) JOIN ItemDataValues ON (itemData.valueID = ItemDataValues.valueID) WHERE itemData.fieldID = 110 GROUP BY itemData.itemID



Ça va en effet alléger le code smile

***

Du coup, j'enchaîne immédiatement sur une autre question : conseillez-vous de réserver l'usage des classes pour les projets d'ampleur, ou au contraire de systématiser leur utilisation, même pour des petits projets ? Je lis des choses un peu différentes à ce sujet : d'un côté, il est dit qu'on peut très bien se passer de l'orienté objet et qu'il faudrait réserver ce dernier à des projets sur lesquels bossent plusieurs codeurs, de l'autre, que son utilisation est toujours profitable, ne serait-ce parce qu'on ne sait pas comment va évoluer le projet…

Bref, je serais curieux d'avoir vos avis là-dessus.


Je suis aussi sur Twitter et nouvellement sur Diaspora*
Mon blog de geekeries : HAL-9000

(J'applique la règle de proximité)

Hors ligne

#7 10-08-2013 12:45:36

paskal
autobahn
Lieu : ailleurs
Inscription : 14-06-2011
Site Web

Re : [Résolu] Python et SQL : questions d'un novice

L'avantage de python est que plusieurs styles de programmation sont possibles.

Je n'utilise pas la POO quand il s'agit d'un petit script : lancement d'une connexion etc.

Par contre, pour m'y initier, j'ai démarré sur un petit projet.
Et c'est vrai que ton projet peut évoluer.
Alors, pour la maintenance, rien de tel que l'héritage et l'encapsulation. smile

I'd love to change the world
But I don't know what to do
So I'll leave it up to you...

logo-sur-fond.png

Hors ligne

#8 10-08-2013 12:52:03

smolski
administrateur quasi...modo
Lieu : AIN
Distrib. : 8 (jessie) 64 bits + backports
Noyau : 4.6.0-0.bpo.1-amd64
(G)UI : gnome 3.14.1
Inscription : 21-10-2008

Re : [Résolu] Python et SQL : questions d'un novice

Marilou écrit :
j'enchaîne immédiatement sur une autre question


Ah mais non ah mais non, ouvre une nouvelle discussion (avec éventuellement un lien vers celle-ci...) et met celle-là en résolu dans l'titre !
Non meh oh ! wink
Malin c'est résolu... Y fô l'mettre dans le titre ! Regarde ici.


"Définition d'eric besson : S'il fallait en chier des tonnes pour devenir ministre, il aurait 2 trous du cul." - JP Douillon
"L'utopie ne signifie pas l'irréalisable, mais l'irréalisée." - T Monod (source :  La zone de Siné)
"Je peux rire de tout mais pas avec n'importe qui." - P Desproges
"saque eud dun" (patois chtimi : fonce dedans)

Hors ligne

#9 10-08-2013 13:48:51

Lunatic
Membre
Lieu : Lyon
Distrib. : Fedora 24
Noyau : Linux 4.6.5-300.fc24.x86_64
(G)UI : Gnome
Inscription : 03-08-2013
Site Web

Re : [Résolu] Python et SQL : questions d'un novice

smolski a écrit :

Marilou écrit :
j'enchaîne immédiatement sur une autre question


Ah mais non ah mais non, ouvre une nouvelle discussion (avec éventuellement un lien vers celle-ci...) et met celle-là en résolu dans l'titre !
Non meh oh ! wink
Malin c'est résolu... Y fô l'mettre dans le titre ! Regarde ici.



En fait j'avais imaginer utiliser ce fil pour poser des questions de débutant « au fil de l'eau » (d'où le titre), plutôt que de multiplier les sujets sur des « petits trucs » qui ne méritent justement pas un fil à eux seuls. Mais si cette façon de faire ne convient pas, je passe ce sujet en résolu.


Je suis aussi sur Twitter et nouvellement sur Diaspora*
Mon blog de geekeries : HAL-9000

(J'applique la règle de proximité)

Hors ligne

#10 10-08-2013 14:34:39

captnfab
Admin-Girafe
Lieu : /dev/random
Distrib. : Debian Stretch/Sid/Rc-Buggy
Noyau : Linux (≥ 4.3)
(G)UI : i3-wm (≥ 4.11)
Inscription : 07-07-2008
Site Web

Re : [Résolu] Python et SQL : questions d'un novice

L'un ou l'autre, c'est pas forcément très important. Mais si les sujets sont séparés et que le titre est explicite, il y a plus de chance pour que d'autres puissent en profiter smile Une histoire de référencement quoi.

captnfab,
Association Debian-Facile, bépo.
TheDoctor: Your wish is my command… But be careful what you wish for.

Hors ligne

#11 11-08-2013 00:39:38

Lunatic
Membre
Lieu : Lyon
Distrib. : Fedora 24
Noyau : Linux 4.6.5-300.fc24.x86_64
(G)UI : Gnome
Inscription : 03-08-2013
Site Web

Re : [Résolu] Python et SQL : questions d'un novice

Ok, je vais donc respecter la règle du 1 topic / 1 sujet. Du coup je change le titre de celui-ci… et ne passe pas en résolu parce que je suis confronté à un nouveau problème tongue

La jointure effectuée sur les conseils de Johan et Paskal fonctionnait bien, tant que je ne voulais récupérer qu'une ligne par table. Mais ça se complique car je souhaite récupérer plusieurs infos, pour une même référence, dans la table itemData.

Illustration sur deux tables :

Je pars toujours de ma table itemSeeAlso qui enregistre les références bibliographiques liées entre elles :

+--------+--------------+
| itemID | linkedItemID |
+========+==============+
| ...    | ...          |
+--------+--------------+
| 4      | 5            |
+--------+--------------+
| 7      | 4            |
+--------+--------------+
| ...    | ...          |
+--------+--------------+



Et j'ai ma table itemData qui comporte l'itemID qu'on retrouvait au-dessus (et qui va nous permettre de faire la jointure), le fieldId qui indique le type de champ (par exemple, 110, ça indique un champ de type « titre »), et valueID, numéro unique qui indique le numéro du champ en question.

+--------+---------+---------+
| itemID | fieldID | valueID |
+========+=========+=========+
| 4      | 11      | 10      |
+--------+---------+---------+
| 4      | 110     | 11      |
+--------+---------+---------+
| 4      | 14      | 12      |
+--------+---------+---------+
| 4      | 7       | 6       |
+--------+---------+---------+
| 4      | 8       | 13      |
+--------+---------+---------+
| 4      | 6       | 14      |
+--------+---------+---------+



Tant que je ne voulais qu'une info de cette table, tout allait bien. Par exemple, pour récupérer le « valueID » d'un champ de type titre (110), je fais :


SELECT itemSeeAlso.itemID, itemData.valueID FROM itemSeeAlso JOIN itemData ON (itemSeeAlso.itemID = itemData.itemID OR itemSeeAlso.itemID = itemData.itemID) WHERE itemData.fieldID = "110" GROUP BY itemSeeAlso.itemID



et j'obtiens quelque chose comme ça :

+--------+---------+
| itemID | valueID |
+========+=========+
| 4      | 11      |
+--------+---------+
| 5      | 161     |
+--------+---------+
| 7      | 456     |
+--------+---------+



Maintenant, donc, je veux aussi le valueID correspondant à un autre type de champ, disons le numéro 6, et plus seulement 110. Évidemment je me précipite pour ajouter une clause WHERE :


SELECT itemSeeAlso.itemID, itemData.valueID FROM itemSeeAlso JOIN itemData ON (itemSeeAlso.itemID = itemData.itemID OR itemSeeAlso.itemID = itemData.itemID) WHERE (itemData.fieldID = "110" OR itemData.fieldID = "6")



qui n'a pas du tout l'effet escompté, puisque je me retrouve avec un truc comme ça :

+--------+---------+
| itemID | valueID |
+========+=========+
| ...    | ...     |
+--------+---------+
| 4      | 11      |
+--------+---------+
| 4      | 11      |
+--------+---------+
| ...    | ...     |
+--------+---------+



Et donc là je ne sais pas quelle est la bonne stratégie (surtout qu'au final, je veux récupérer encore plus de champs) :

- est-ce que je modifie le code Python pour qu'il exécute plusieurs « petites » requêtes SQL avec autant de boucles… (ça marcherait et c'est simple à mettre en place, mais peu élégant me semble-t-il)
- est-ce qu'il y a un moyen de faire ça avec SQL ?

De surcroît ? mais je demande peut-être l'impossible ? il faudrait idéalement que je puisse grouper les résultats (une référence biblio = une ligne de résultat), alors que la table itemData possède plusieurs enregistrement pour 1 seule référence (comme on le voit au dessus). Aussi m'étais-je demandé s'il était possible de faire « comme si » on interrogeait plusieurs fois la même colonne (avec « as »), un truc du genre :

SELECT valueID as titre, valueID as truc FROM itemData WHERE (pour titre, fieldID = 110 // pour truc, fieldID = 6)



(J'ai fouillé dans la doc, mais vu que je ne sais même pas si c'est faisable…)

Merci à nouveau à ceux qui auront eu le courage de lire tongue

Dernière modification par Lunatic (11-08-2013 00:41:45)


Je suis aussi sur Twitter et nouvellement sur Diaspora*
Mon blog de geekeries : HAL-9000

(J'applique la règle de proximité)

Hors ligne

#12 11-08-2013 09:10:16

captnfab
Admin-Girafe
Lieu : /dev/random
Distrib. : Debian Stretch/Sid/Rc-Buggy
Noyau : Linux (≥ 4.3)
(G)UI : i3-wm (≥ 4.11)
Inscription : 07-07-2008
Site Web

Re : [Résolu] Python et SQL : questions d'un novice

Salut,

Pour les jointures dont tu parles plutôt que des « JOIN » il te faut utiliser des « LEFT JOIN » ou des « RIGHT JOIN ».

captnfab,
Association Debian-Facile, bépo.
TheDoctor: Your wish is my command… But be careful what you wish for.

Hors ligne

#13 11-08-2013 10:15:03

johan
Membre
Lieu : Montpellier
Distrib. : Debian Wheezy Vers 7.1
Noyau : Linux 3.2.0-4-amd64
(G)UI : Gnome
Inscription : 29-07-2013

Re : [Résolu] Python et SQL : questions d'un novice

Bonjour,

Pour éviter les doublons d'information, je te conseille de mettre un SELECT DISTINCT au niveau de ta requete SQL (Voyant que tu n'as plus le GROUP BY).

Marie-Lou a écrit :

il faudrait idéalement que je puisse grouper les résultats (une référence biblio = une ligne de résultat), alors que la table itemData possède plusieurs enregistrement pour 1 seule référence (comme on le voit au dessus).



Pourquoi ne pas effectuer une requête avec l'ensemble des informations que tu as besoin puis effectuer sur cette même requête un tri avec un ORDER BY sur ta référence (+ le fieldID si tu veux les récupérer dans le même ordre).
Ainsi dans ta boucle python, tu récupères toutes les informations de la première référence + puis la seconde référence + .....

Lien sur le ORDER BYhttp://www.tutorialspoint.com/sqlite/sq … der_by.htm

Il y a plusieurs stratégies pour traiter ta demande. A toi de voir celle qui te convient le mieux en fonction du besoin (Par exemple en effectuant du multi-requetes).

++

Dernière modification par johan (11-08-2013 10:21:40)

Hors ligne

#14 13-08-2013 10:58:10

Lunatic
Membre
Lieu : Lyon
Distrib. : Fedora 24
Noyau : Linux 4.6.5-300.fc24.x86_64
(G)UI : Gnome
Inscription : 03-08-2013
Site Web

Re : [Résolu] Python et SQL : questions d'un novice

Merci à nouveau pour vos conseils, je passe en « résolu » smile

Je suis aussi sur Twitter et nouvellement sur Diaspora*
Mon blog de geekeries : HAL-9000

(J'applique la règle de proximité)

Hors ligne

Pied de page des forums