SELECT les enregistrements qui contiennent une valeur puissance de 2

Eléphant du PHP | 246 Messages

20 janv. 2008, 12:21

Salut tout le monde!

Désolé pour le titre, il n'est pas très clair. Je m'explique :

J'ai une table tb_langue qui contient des langage. Chacun de ces languages ont une valeur qui est une puissance de 2.

Image

J'ai une table tb_jeux ou j'y ajoute des fiches de jeux. Il y a un champs "langage" et dedans j'y met la valeur de la langue.

Si le jeu est en Français, j'y met 1
Si le jeu est en Anglais, j'y met 2
Si le jeu est en Français et en Anglais, j'y met 3 (1+2=3).

Maintenant j'aimerais pouvoir sélectionner tous les jeux en Français. Mais je peux pas faire :

Code : Tout sélectionner

SELECT * FROM tb_jeux WHERE langage = 1
Puisque les jeux qui sont en Français et en Anglais ont la valeurs 3. Alors ce qu'il faudrait c'est pouvoir séléctionner les jeux qui contiennent la valeur 1 par exemple, ainsi 3 serait aussi sélectionner. Je sais pas si j'arrive à me faire comprendre ^^

J'ai trouvé une solution mais c'est du gros bricolage je trouve. En clair je sélectionne tous les enregistrements, et avec PHP je décompose la valeur de sa langue, et si c'est bien la langue recherché, j'affiche l'enregistrement. Vous voyez quoi ^^

Je sais que j'aurais pu faire autrement. Faire une table associative. Mais tb_jeux et tb_langue c'est une table que j'ai télécharger sur internet et qui est déjà toute remplie. Donc ça me prendrais du temps je pense si je devais tout refaire.

Voilà...si quelqu'un a une solution à me proposer... :D

Merci d'avance!

Avatar du membre
Modérateur PHPfrance
Modérateur PHPfrance | 10684 Messages

20 janv. 2008, 12:41

Je sais que j'aurais pu faire autrement. Faire une table associative. Mais tb_jeux et tb_langue c'est une table que j'ai télécharger sur internet et qui est déjà toute remplie. Donc ça me prendrais du temps je pense si je devais tout refaire.
Sans doute, mais probablement moins que d'essayer de te dépétrer de ce truc... ça serait non seulement plus facile pour faire ce que tu souhaites, mais ca sera également beaucoup plus simple par la suite à maintenir où à faire évoluer...

Je doute qu'une fonction sql te permette de faire ça.. au mieux avec une division par 2 tu peux retrouver ceux qui sont en français, mais j'ai bien peur que cela s'arrête là. La solution php est sans doute ce que tu peux faire de mieux vu le contexte, mais c'est franchement pas la meilleure si tu comptes ajouter des jeux régulièrement...

Très sincèrement, le temps "perdu" à mettre en place une table de liaison serait très vite amorti (et un gain inconsidérable par la suite, parce que le jour où tu rajoute une langue, tu vas te prendre la tête pour mettre les jeux à jour). Par ailleurs tu as à priori déjà le script php pour lire les données de ta table de jeux et décomposer le nombre pour savoir à quelle langue ils sont associées, tu pourrais très bien l'utiliser et ainsi remplir ta table de liaison en quelques secondes :)
Ce n'est pas en améliorant la bougie que l'on a inventé l'ampoule...

Eléphant du PHP | 246 Messages

20 janv. 2008, 17:46

Je vais essayé. J'ai déjà un peu réfléchi au problème et je sent qu'à un moment je vais être coincé. Je m'explique.

Dans ma table tb_jeux j'ai plein de jeux. La plus part des jeux ont une ou plusieurs langues, mais certains n'en ont pas encore, car je n'ai pas encore eu le temps de les ajouté.

Image

Pour l'exemple on va dire que simplement "Nom du 1er jeu" a des langues, et que les autres n'ont pas encore été spécifié.

"Nom du 1er jeu" est en Anglais, Danois et Néerlandais.

Jusque là on est d'accord ? (Enfin j'espère ^^)

Maintenteant j'aimerais pouvoir afficher tous les jeux, et si ils ont une langue, l'afficher, et si ils en ont pas, bah ne rien affiché.

Si je fais :

Code : Tout sélectionner

SELECT * FROM tb_jeux, tb_relation, tb_langue WHERE tb_jeux.id_jeu = tb_relation.id_jeu AND tb_relation.id_langue = tb_langue.id_langue
Ca va m'afficher seulement "Nom du 1er jeu" puisque c'est le seule à avoir été trouvé dans la tb_relation.

Alors que j'aimerais que ça m'affiche tous les jeux, ainsi que leur langues si il y en a une.

Donc comment faire ?

PS : J'ai jamais eu de cours spéciaux sur comment bien faire les choses. J'ai appris par moi-même sur internet ou en me débrouillant en cherchant une solution qui marcherait. Donc merci de me recadrer et me dire comment faire juste ^^

Merci ^^

ViPHP
ViPHP | 2287 Messages

20 janv. 2008, 18:30

Bonjour Nico128,

Ryle a raison de te souligner plus haut que ce n'est pas la façon la plus "relationnelle" de traiter le problème.

Mais ta demande reste réalisable : c'est d'ailleurs avec ce genre de système, et l'utilisation d'opérateurs logiques, qu'on réalise des associations dans beaucoup de langages bas niveau :-)

Voici comment t'en sortir avec ta table d'origine :

Code : Tout sélectionner

SELECT tb_jeux.id, tb_jeux.nom FROM `tb_jeux` JOIN language ON language.id_language & tb_jeux.language !=0 WHERE language.language = "Français"
Cette requête te retournera tous les couples id-nom des jeux en Français.

Si tu as d'autres opérations à faire sur ces tables et que tu ne t'en sors pas, demande-le dans le forum qui va bien.
Modifié en dernier par Calimero le 21 janv. 2008, 05:15, modifié 1 fois.
if(!@work()){ Nespresso(); } else { what(); }
______________________________

Eléphant du PHP | 246 Messages

20 janv. 2008, 21:34

Salut Calimero et merci pour ta réponse.

Je suis pas sûr d'avoir compris pour quelle problème tu essaie de m'aider. C'est pour le cas de mon premier message où j'aurais voulu sélectionner tous les jeux Français ?

Si c'est le cas j'ai tenté ta requête, elle s'exécute bien mais elle affiche tous les enregistrement. La condition ne se fait pas :?

ViPHP
ViPHP | 2287 Messages

21 janv. 2008, 05:23

Oui, je répondais bien à ton premier problème, si tu veux toujours le régler tel quel. J'ai édité la requête que je te postais au dessus. :-)

J'avais commis deux erreurs : d'abord j'avais mis un & de trop (ce qui change complètement le résultat de l'opération "AND"), et mon jeu de données de test était incomplet pour bien tester cette requête, ce qui m'avait amené à penser que la requête précédente marchait bien.

J'ai donc rectifié le tir. Je t'offre une crêpe chocolat-banane si celle-là ne fait pas ce que tu veux, à quelques renommages de champs près ;-)

Tiens-nous au courant.
if(!@work()){ Nespresso(); } else { what(); }
______________________________

Avatar du membre
Modérateur PHPfrance
Modérateur PHPfrance | 10684 Messages

21 janv. 2008, 09:44

Soit chuis pas encore bien réveillé, soit y a un truc qui m'échappe dans ta requête Calimero :?

Je vois vraiment pas comment tu arrives a faire une jointure entre les deux tables... tb_jeux.language !=0 vas t'enlever les jeux qui n'ont pas de langue et language.id_language va te retourner vrai.. tu vas te retrouver avec un produit entre les deux tables sans pour autant savoir si un 6 (chinois + anglais) contient ou pas la valeur 1 (français)

Bref, si ça fonctionne et que tu arrives à m'epliquer comment, c'est moi qui te l'offre la crèpe :)
Ce n'est pas en améliorant la bougie que l'on a inventé l'ampoule...

ViPHP
ViPHP | 2287 Messages

21 janv. 2008, 11:33

Soit chuis pas encore bien réveillé, soit y a un truc qui m'échappe dans ta requête Calimero :?

Je vois vraiment pas comment tu arrives a faire une jointure entre les deux tables... tb_jeux.language !=0 vas t'enlever les jeux qui n'ont pas de langue et language.id_language va te retourner vrai.. tu vas te retrouver avec un produit entre les deux tables sans pour autant savoir si un 6 (chinois + anglais) contient ou pas la valeur 1 (français)

Bref, si ça fonctionne et que tu arrives à m'epliquer comment, c'est moi qui te l'offre la crèpe :)
Le principe est simple et très utilisé, notamment quand tu codes en C : regarde attentivement les valeurs d'id de cette table :

Image

Ce sont des puissances de deux. Voici comment se présentent ces nombres en représentation binaire (pour être simple et lisible j'aligne sur deux octets, soit 16 bits, sachant que mysql travaille sur un nombre de bits bien plus élevé (64 il me semble)), donc pas de risque de déborder dans ce cas précis:

Code : Tout sélectionner

id.............id en binaire ---------------------------------------- 1..............00000000.00000001 2..............00000000.00000010 4..............00000000.00000100 8..............00000000.00001000 16.............00000000.00010000 32.............00000000.00100000 64.............00000000.01000000 128............00000000.10000000 256............00000001.00000000 512............00000010.00000000 // Attention ici on a sauté une puissance 2048...........00001000.00000000 4096...........00010000.00000000 8192...........00100000.00000000
Ca c'est pour bien suivre comment fonctionne l'opérateur & logique. On appelle ceci un masque de bits.

Quand tu fais une jointure classique, tu cherches une égalité entre deux champs dans des tables différentes. Un esprit tordu ( :twisted: ), si les deux champs sont de type numériques, pourrait écrire ce test d'égalité comme une simple soustraction et tester l'égalité du résultat de cette soustraction avec 0, ce qui reviendrait au même... A l'exception que cela demande un traitement préalable sur les valeurs lues et l'évaluation systématique du résultat de ce traitement.

Dans ce cas présent, c'est la même chose : la jointure se fait en faisant un ET logique entre les deux champs et en testant le résultat (si c'est 0, cela veut dire que la valeur contenue dans tb_jeux.languages ne respecte pas le masque demandé, si c'est autre chose que 0 c'est le contraire).

Pas assez clair ? Je peux détailler si besoin. Sinon, tu peux préparer la crêpe... et le cidre :boire9:
Modifié en dernier par Calimero le 21 janv. 2008, 11:42, modifié 1 fois.
if(!@work()){ Nespresso(); } else { what(); }
______________________________

ViPHP
ViPHP | 4039 Messages

21 janv. 2008, 11:41

:boire4:

ééh, j'ai compris les bitmasks !! (m'y suis jamais vraiment intéressé non plus)
Mais qu'importe. (je suis ici - dernier petit projet)
Berze going social.

Avatar du membre
Modérateur PHPfrance
Modérateur PHPfrance | 10684 Messages

21 janv. 2008, 13:02

Et ben voilà... ça m'apprendra à poser des questions quand je suis pas réveillé ;)

Bon ceci dit, je n'avais jamais compris l'intérêt de la comparaison binaire n'ayant jamais eu à m'en servir... donc si je comprend bien, le "&" vérifie "en gros" si l'une des deux valeurs binaires comparée correspond à l'autre ?
101 & 001 sera vrai
110 & 011 sera faux :?:

Va falloir que je testes un peu plus tout ça, et surtout le OU dont je vois mal comment se fait la comparaison du coup :) Mais j'aurais appris quelque chose et t'aura bien mérité ta crèpe (pour le cidre, c'est toujours toi qui a un pot à offrir ;)).

Bon pis j'avoue m'être un peu laissé embué ce matin par la doc de mysql sur la précédence des opérateurs, qui les classes du plus faible au plus fort alors qu'en général on voit le contraire et j'ai cru que contrairement à php, en sql le "!=" était prioritaire sur le "&" #-o

Reste plus à Nico128 qu'à valider pour que tu gagnes ta crèpe :) (ou alors à infirmer pour que lui gagne la sienne et que je n'ai pas à t'en payer une ? Niiiccoooo !! faut qu'on cause !! ;))
Ce n'est pas en améliorant la bougie que l'on a inventé l'ampoule...

ViPHP
ViPHP | 2287 Messages

21 janv. 2008, 13:21

Et ben voilà... ça m'apprendra à poser des questions quand je suis pas réveillé ;)
Rassure toi... regarde l'heure à laquelle j'ai posté ma correction, tu verras que je ne suis guère mieux ;-)
Bon ceci dit, je n'avais jamais compris l'intérêt de la comparaison binaire n'ayant jamais eu à m'en servir... donc si je comprend bien, le "&" vérifie "en gros" si l'une des deux valeurs binaires comparée correspond à l'autre ?
101 & 001 sera vrai
110 & 011 sera faux :?:

Code : Tout sélectionner

// Si on regarde langue par langue 101 & 001 => 001 101 & 100 => 100 101 & 010 => 000 // Voilà ce qui se passe sur une double correspondance de langue ( Anglais ET français // par exemple ), avec quelques équivalences: 101 & 011 === 101 & ( 001 | 010 ) === 101 & ( 001 + 010 ) => 001
J'attire ton attention sur le fait que le résultat du test est numérique, et non "vrai ou faux". Par exemple dans le dernier cas on ne trouve pas zéro (ce qui est normal), c'est pourquoi il y a un != 0 explicite.

Quand tu regardes ces chiffres binaires, imagine une ligne d'une grille de touché-coulé :
Dans le résultat du & logique, le 0 correspond à un coup dans l'eau et le 1 à un coup touché.
Dans le masque par contre, le 1 est un coup joué, et le 0 une absence de coup.

Sur une série de coups, donc, tu peux avoir un résultat complètement nul, complètement positif, ou mixte :-)

Ce serait intéressant de savoir ce que ce genre de jointure vaut en terme de performances comparé à ce que tu proposais, si Hubert passe dans le coin. On peut penser que l'évaluation systématique du & logique fait perdre en perfs, mais d'un autre côté un & logique est une opération très élémentaire à réaliser nativement pour un microprocesseur.[/code]
if(!@work()){ Nespresso(); } else { what(); }
______________________________

Avatar du membre
Modérateur PHPfrance
Modérateur PHPfrance | 10684 Messages

21 janv. 2008, 15:16

Oki, j'a compris cette fois ;)

Je ne sais pas pourquoi je restais bloqué sur le fait qu'il s'agissait d'opérateur logique, et donc forcément avec un résultat booléen. La comparaison se fait donc en fait bit à bit, si a une position donnée de la chaine j'ai un 1 de chaque côté, j'aurais un 1 en résultat, sinon j'ai un 0 :) (et je conçois beaucoup mieux le OU également du coup :))

Merci pour l'explication :)
Ce n'est pas en améliorant la bougie que l'on a inventé l'ampoule...

ViPHP
ViPHP | 2287 Messages

21 janv. 2008, 16:05

/me savoure les remerciements saveur choco-banane

Dis moi Ryle, ne penses-tu pas que ce thread mériterait d'être rangé ailleurs ?
if(!@work()){ Nespresso(); } else { what(); }
______________________________

Avatar du membre
Modérateur PHPfrance
Modérateur PHPfrance | 10684 Messages

21 janv. 2008, 16:21

Sujet déplacé :)

(pis comme ça, si Hubert passe, on aura sans doute une réponse quant aux performances des deux solutions ;))
Ce n'est pas en améliorant la bougie que l'on a inventé l'ampoule...

Administrateur PHPfrance
Administrateur PHPfrance | 3088 Messages

21 janv. 2008, 16:40

101 & 001 sera vrai
Mince, j'ai sauté le post de Calimero qui expliquait la différence et j'avais retapé tout un truc pour rien.

Pour info, si mes souvenirs sont bons, pour des raisons de compatibilité avec les SGBD du siècle dernier, préférez <> à !=
si Hubert passe, on aura sans doute une réponse quant aux performances des deux solutions ;)
Bingo! Ceci dit, je crois que je n'ai pas vu la seconde solution, va falloir que je relise :-k
Les opérateurs bitwise ne peuvent pas utiliser les index, donc les performances sont bof. On peut tricher en utilisant un gros IN() sur toutes les valeurs possibles, par exemple au lieu de

Code : Tout sélectionner

a & 8 > 0
utiliser

Code : Tout sélectionner

WHERE col IN (8, 9, 10 ... 15, 24, 25, etc...)
Mais non seulement c'est illisble et prône à l'erreur, mais en plus le résultat n'est pas garanti est les index pourraient ne pas être utilisés.

Pour finir sur une note optimiste, autant que je sache ENUM est en réalité une valeur numérique dont il serait possible de convertir le type de la colonne assez facilement, et sans toucher au reste de l'application. @Nico128 : poste un schéma de ta table de langues avec ses données et je te le confirmerai.