SELECT les enregistrements qui contiennent une valeur puissance de 2

Répondre


Cette question est un moyen d’empêcher des soumissions automatisées de formulaires par des robots.
Smileys
:D :) :( :o :shock: :? 8-) :lol: :x :P :oops: :cry: :evil: :twisted: :roll: :wink: :!: :?: :idea: :arrow: :| :mrgreen: =D> #-o =P~ :^o :non: :priere: 8-|
Voir plus de smileys
  Revue du sujet
 

  Étendre la vue Revue du sujet : SELECT les enregistrements qui contiennent une valeur puissance de 2

par iclo » 22 janv. 2008, 12:53

En effet, très intéressant ce sujet.

Mais dans le cas présent, je pense que modifier la structure de la base donnée pour utiliser une table de liaison reste la solution la plus propre.
Surtout si on risque à un moment ou un autre de devoir ajouter d'autre fonctionnalitée: par exemple savoir combien de jeu sont en Français, etc.
En plus, j'émets des doutes sur les performances d'opérations bits à bits utilisées en grands nombres dans des requêtes

par Berzemus » 22 janv. 2008, 11:33

juste au passage, mais il me semble avoir lu quelque part que dans SQL server, les bitmask pouvaient se subdiviser automatiquement en colonnes (rendant possible leur interrogation directe, et donc plus besoin d'index dans la colonne puisqu'il n'y a que deux valeurs possibles).

par zeus » 22 janv. 2008, 09:06

Très intéressant ce sujet ...

Il va falloir trouver une place pour ne pas le perdre ;)

par Hubert Roksor » 22 janv. 2008, 05:01

Désolé, j'étais un peu pressé lorsque j'ai tapé mon précédent message et je n'ai pas pris le temps de copier/coller les bons liens.

J'ai parlé du type de données "ENUM" dans mon précédent post, c'était une erreur, il fallait vraiment lire 'le type de données "SET"'. Calimero disait que les bitmasks étaient simples et très utilisés, et en effet ils sont tellement utilisés que MySQL possède un type de données (SET) spécialement adapté à leur utilisation. Dans ce message, je vais t'expliquer comment l'utiliser mais avant toute chose j'aimerais précisé que ce n'est pas la solution que je recommanderais. Même si c'est un peu plus compliqué et même un tout petit poil plus lent, je recommanderais la version avec trois tables utilisant une table de relations. Voilà donc pour les avertissements :)

J'ai créé sur mon système une table "roms_config_language", ainsi qu'une table "tb_jeux" pour pouvoir exécuter des requêtes identiques aux tiennes. Voici cette table :

Code : Tout sélectionner

CREATE TABLE tb_jeux ( id_jeu INT UNSIGNED NOT NULL, nom VARCHAR(255) NOT NULL, language SET('Français','Anglais','Chinois','Danois','Néerlandais','Finlandais','Allemand','Italien','Japonais','Norvégien','vide1','Portugais','Espagnol','Suédois','vide2','vide3','Coréen') ) DEFAULT CHARSET=latin1; INSERT INTO tb_jeux VALUES (1, 'jeu 1 FR-EN', 'Français,Anglais'), (2, 'jeu 2', ''), (3, 'jeu 3 FR-ZH', 'Chinois,Français'), (4, 'jeu 4 DA-NL-FI', 'Danois,Néerlandais,Finlandais'), (5, 'jeu 5 FR', 'Français')
Le type de données SET est un type hybride, que l'on peut utiliser à la fois comme une chaine en listant une liste de valeurs séparées par des virgules, ou comme un nombre correspondant au bitmask de ses données. En effet, dans la liste de langues utilisée lors de la définition de la colonne, chaque membre (valeur de la colonne) s'est vue attribuée une valeur correspondant à 1^r (1 puissance r) où r est le rang de la valeur à partir de 1. En clair, 'Français' vaut 1, 'Anglais' vaut 2, 'Chinois' vaut 4, etc... Le type SET ne permet pas d'avoir des "trous" dans la progression arithmétique de ses membres, j'ai donc ajoutés des langues "vide" pour les combler et faire correspondre les langues à ton système actuel.

À partir de là, puisque le type SET est considéré comme un nombre et les valeurs sont les même que ton système actuel, je peux réutiliser toutes les requêtes citées dans ce sujet à l'identique

Code : Tout sélectionner

SELECT j.* FROM tb_jeux j JOIN roms_config_language l ON l.id_language & j.language > 0 WHERE l.language = 'Français'

Code : Tout sélectionner

+--------+-------------+------------------+ | id_jeu | nom | language | +--------+-------------+------------------+ | 1 | jeu 1 FR-EN | Français,Anglais | | 3 | jeu 3 FR-ZH | Français,Chinois | | 5 | jeu 5 FR | Français | +--------+-------------+------------------+
Mais le type SET est une chaine aussi, donc je peux l'utiliser directement ! Si je veux récupérer les jeux qui ne sont disponibles qu'en français

Code : Tout sélectionner

SELECT * FROM tb_jeux WHERE language = 'Français'

Code : Tout sélectionner

+--------+----------+----------+ | id_jeu | nom | language | +--------+----------+----------+ | 5 | jeu 5 FR | Français | +--------+----------+----------+
En utilisant la fonction FIND_IN_SET(), les jeux possédant 'Français' dans leur liste

Code : Tout sélectionner

SELECT * FROM tb_jeux WHERE FIND_IN_SET('Français', language)

Code : Tout sélectionner

+--------+-------------+------------------+ | id_jeu | nom | language | +--------+-------------+------------------+ | 1 | jeu 1 FR-EN | Français,Anglais | | 3 | jeu 3 FR-ZH | Français,Chinois | | 5 | jeu 5 FR | Français | +--------+-------------+------------------+
Et pour vérifier que le type s'utilise bien comme un nombre

Code : Tout sélectionner

SELECT * FROM tb_jeux WHERE language = 5

Code : Tout sélectionner

+--------+-------------+------------------+ | id_jeu | nom | language | +--------+-------------+------------------+ | 3 | jeu 3 FR-ZH | Français,Chinois | +--------+-------------+------------------+
Normalement tu devrais maintenant être incollable sur le type SET. :)
Puisque le type est compatible avec ton système actuel, tu pourrais directement changer le type de ta table sans rien changer à tes requêtes actuelles. Sous phpMyAdmin, tu peux éditer ta colonne, remplacer le type INT par SET et dans le champs valeurs entrer

Code : Tout sélectionner

'Français','Anglais','Chinois','Danois','Néerlandais','Finlandais','Allemand','Italien','Japonais','Norvégien','vide1','Portugais','Espagnol','Suédois','vide2','vide3','Coréen'
La conversion sera automatique (en fait il n'y aura même pas de conversion puisque SET est un type numérique) et si tu ne souhaites plus utiliser SET tu pourras reconvertir ta colonne en INT de la même façon. Mais que cela ne t'empêche pas de faire une copie de tes tables par sécurité.

Attention toutefois, ce système n'est pas aussi flexible qu'un système à 3 tables, ou même ton système de bitmask. Pour ajouter une nouvelle langue il te faudra modifier la colonne à chaque fois. Comme dans ton système de bitmask, il est impossible d'effacer une langue sans la remplacer par une valeur quelconque. De plus, ces valeurs doivent être uniques (on ne peut pas avoir 2 fois 'vide' dans la liste).

Dernière chose, pour ceux qui se poseraient des questions sur les performances, FIND_IN_SET() n'utilise pas les index, comme & et les opérateurs bitwise. D'un autre côté, je remarque qu'il n'y a déjà pas d'index sur roms_config_language.language...

par Nico128 » 21 janv. 2008, 20:57

Okey, voila donc :

Code : Tout sélectionner

-- -- Structure de la table `roms_config_language` -- CREATE TABLE roms_config_language ( id_language int(11) NOT NULL default '-1', language varchar(255) NOT NULL default '', PRIMARY KEY (id_language) ) ENGINE=MyISAM DEFAULT CHARSET=latin1; -- -- Contenu de la table `roms_config_language` -- INSERT INTO roms_config_language VALUES (1, 'Français'); INSERT INTO roms_config_language VALUES (2, 'Anglais'); INSERT INTO roms_config_language VALUES (4, 'Chinois'); INSERT INTO roms_config_language VALUES (8, 'Danois'); INSERT INTO roms_config_language VALUES (16, 'Néerlandais'); INSERT INTO roms_config_language VALUES (32, 'Finlandais'); INSERT INTO roms_config_language VALUES (64, 'Allemand'); INSERT INTO roms_config_language VALUES (128, 'Italien'); INSERT INTO roms_config_language VALUES (256, 'Japonais'); INSERT INTO roms_config_language VALUES (512, 'Norvégien'); INSERT INTO roms_config_language VALUES (1024, '?'); INSERT INTO roms_config_language VALUES (2048, 'Portugais'); INSERT INTO roms_config_language VALUES (4096, 'Espagnol'); INSERT INTO roms_config_language VALUES (8192, 'Suédois'); INSERT INTO roms_config_language VALUES (16384, '?'); INSERT INTO roms_config_language VALUES (32768, '?'); INSERT INTO roms_config_language VALUES (65536, 'Coréen');

par Ryle » 21 janv. 2008, 20:52

Pour Hubert Roksor, je comprends pas bien ce que tu essaye d'expliquer. Et je sais pas exactement si c'est ça que tu me demandes :
Il a besoin de ça : http://www.phpfrance.com/forums/voir_sujet-34456.php :)

Comme ca il peut éventuellement recréer la table chez lui et avoir un jeu de données pour essayer plein de choses diverses et variées dont il a le secret, et te donner plein de conseils vachement bien pour t'aider à optimiser ta base de données :pouce:

par Nico128 » 21 janv. 2008, 20:40

Re tout le monde ! Je vois que mon sujet a fait réagir beaucoup de personnes ^^

Désolé si j'ai pas pu répondre plus tôt, j'étais à l'école cette après-midi. Donc j'ai tester la solution de Calimero et il mérite bien sa crêpe chocolat-banane et tout ce qui va avec, puisque ça requête marche du tonnerre ^^ !!

Un grand merci à toi Calimero ! J'avais vu dernièrement en VBScript que c'était possible de tester des valeur comme ça mais j'avais aucune idée comment faire avec mysql, et je pense que j'aurais jamais trouvé. Merci encore!


Pour Hubert Roksor, je comprends pas bien ce que tu essaye d'expliquer. Et je sais pas exactement si c'est ça que tu me demandes :
Image

Je vais essayé avec une table associative voir ce que ça vaut.

Sinon Ryle, ma table contient 1500-2000 enregistrement.

par Ryle » 21 janv. 2008, 17:27

En fait "l'autre solution" proposée était de modifier la structure de sa base pour avoir un id classique dans sa table de langues et utiliser une table de liaison pour associer les langues aux jeux :)

Et on se demandait donc s'il était plus rapide, optimisé, tout ça, de passer par une jointure classique avec 3 tables (2nd message de Nico128), ou par la comparaison binaire sur les deux tables (1er message) :)

Mais le fait qu'on ne puisse utiliser d'index va sans doute faire pencher la balance en faveur de la première solution (bon tout dépend du nombre de jeux of course)

par Hubert Roksor » 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.

par Ryle » 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 ;))

par Calimero » 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 ?

par Ryle » 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 :)

par Calimero » 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]

par Ryle » 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 !! ;))

par Berzemus » 21 janv. 2008, 11:41

:boire4:

ééh, j'ai compris les bitmasks !! (m'y suis jamais vraiment intéressé non plus)