Assistant de base de données GreenPig

  1. Entrée
  2. Connexion Ă  la bibliothĂšque
  3. OĂč classe
  4. Rejoignez la classe
  5. RequĂȘte de classe

╔═══╗╔═══╗╔═══╗╔═══╗╔╗─╔╗────╔═══╗╔══╗╔═══╗ ║╔══╝║╔═╗║║╔══╝║╔══╝║╚═╝║────║╔═╗║╚╗╔╝║╔══╝ ║║╔═╗║╚═╝║║╚══╗║╚══╗║╔╗─║────║╚═╝║─║║─║║╔═╗ ║║╚╗║║╔╗╔╝║╔══╝║╔══╝║║╚╗║────║╔══╝─║║─║║╚╗║ ║╚═╝║║║║║─║╚══╗║╚══╗║║─║║────║║───╔╝╚╗║╚═╝║ ╚═══╝╚╝╚╝─╚═══╝╚═══╝╚╝─╚╝────╚╝───╚══╝╚═══╝ 5HHHG HH HHHHHHH 9HHHA HHHHHHHH5 HHHHHHHHHHHHHHHHHH 9HHHHH5 5HHHHHHHHHHHHHHHHHHHHHHHHHHH HHHHHHHHHHHHHHHHHHHHHHHHHHHH ;HHHHHHHHHHHHHHHHHHHHHHHHHHA H2 HHHHHHHHHHHHHHHHHHHHHH HHHHHHHHHHHHHHHHHHHHHHH9 HHHHHHHHHHHHHHHHHHHHHHH AHHHHHHHHHHHHHHHHHHHHHH HHHHHHHHHHHHHHHHHHHHH9 iHS HHHHHHHHHHHHHHHHHHHHHHhh HHHHHHHHHHHHHHHHHH AA HHHHHHHHHHHHHH3 &H Hi HS Hr & H& H& Hi 

Entrée


Je veux vous parler du dĂ©veloppement de ma petite bibliothĂšque en php. Quelles tĂąches rĂ©sout-elle? Pourquoi ai-je dĂ©cidĂ© de l'Ă©crire et pourquoi pourrait-il vous ĂȘtre utile? Essayez bien de rĂ©pondre Ă  ces questions.


GreenPig (ci-aprÚs GP ) est un petit assistant de base de données qui peut compléter les fonctionnalités de n'importe quel framework php que vous utilisez.


Comme tout outil GP , il est affinĂ© pour rĂ©soudre certains problĂšmes. Il vous sera utile si vous prĂ©fĂ©rez Ă©crire des requĂȘtes de base de donnĂ©es en SQL pur et que vous n'utilisez pas Active record et d'autres technologies similaires. Par exemple, nous avons une base de donnĂ©es Oracle au travail et souvent les requĂȘtes occupent plusieurs Ă©crans avec des dizaines de jointures, les fonctions plsql, union all, etc. sont toujours utilisĂ©es. etc., donc il ne reste plus qu'Ă  Ă©crire des requĂȘtes en SQL pur.


Mais avec cette approche, la question se pose: comment gĂ©nĂ©rer oĂč la partie de la requĂȘte SQL lorsque les utilisateurs recherchent des informations? GP vise, tout d'abord, une compilation pratique au moyen de php oĂč d'une demande de toute complexitĂ©.


Mais qu'est-ce qui m'a poussé à écrire cette bibliothÚque (sauf, bien sûr, pour acquérir une expérience intéressante)? Ce sont trois choses:


PremiÚrement, la nécessité d'obtenir non pas une réponse plate standard de la base de données, mais un tableau imbriqué semblable à un arbre.


Voici un exemple d'un exemple de base de données standard:
 [ [0] => [ ['id'] => 1 ['type'] => 'car' ['val_id'] => 1 ['name'] => ' ()' ['value'] => 790 ], [1] => [ ['id'] => 1 ['type'] => 'car' ['val_id'] => 2 ['name'] => '   ' ['value'] => 24 ], [2] => [ ['id'] => 1 ['type'] => 'car' ['val_id'] => 3 ['name'] => ' ' ['value'] => 75 ], [3] => [ ['id'] => 4 ['type'] => 'phone' ['val_id'] => 10 ['name'] => ' ' ['value'] => 5 ], [4] => [ ['id'] => 4 ['type'] => 'phone' ['val_id'] => 8 ['name'] => ' ()' ['value'] => 0.12 ], [5] => [ ['id'] => 4 ['type'] => 'phone' ['val_id'] => 9 ['name'] => '   ' ['value'] => 1 ], [6] => [ ['id'] => 4 ['type'] => 'phone' ['val_id'] => 10 ['name'] => ' ' ['value'] => 5 ], [7] => [ ['id'] => 4 ['type'] => 'phone' ['val_id'] => 8 ['name'] => ' ()' ['value'] => 0.12 ], [8] => [ ['id'] => 4 ['type'] => 'phone' ['val_id'] => 9 ['name'] => '   ' ['value'] => 1 ], [9] => [ ['id'] => 4 ['type'] => 'phone' ['val_id'] => 10 ['name'] => ' ' ['value'] => 5 ], [10] => [ ['id'] => 4 ['type'] => 'phone' ['val_id'] => 8 ['name'] => ' ()' ['value'] => 0.12 ], [11] => [ ['id'] => 4 ['type'] => 'phone' ['val_id'] => 9 ['name'] => '   ' ['value'] => 1 ], [12] => [ ['id'] => 1 ['type'] => 'car' ['val_id'] => 1 ['name'] => ' ()' ['value'] => 790 ], [13] => [ ['id'] => 1 ['type'] => 'car' ['val_id'] => 2 ['name'] => '   ' ['value'] => 24 ], [14] => [ ['id'] => 1 ['type'] => 'car' ['val_id'] => 3 ['name'] => ' ' ['value'] => 75 ], [15] => [ ['id'] => 1 ['type'] => 'car' ['val_id'] => 1 ['name'] => ' ()' ['value'] => 790 ], [16] => [ ['id'] => 1 ['type'] => 'car' ['val_id'] => 2 ['name'] => '   ' ['value'] => 24 ], [17] => [ ['id'] => 1 ['type'] => 'car' ['val_id'] => 3 ['name'] => ' ' ['value'] => 75 ] ] 

Pour obtenir un tableau arborescent, nous devons soit apporter le rĂ©sultat Ă  la forme souhaitĂ©e nous-mĂȘmes, soit effectuer N requĂȘtes de base de donnĂ©es pour chaque produit. Et si nous avons besoin de pagination, et mĂȘme de tri? GP est capable de rĂ©soudre ces problĂšmes. Voici un exemple d'Ă©chantillon avec GP :


 [ [1] => [ ['prod_type'] => 'car' ['properties'] => [ [1] => [ ['name'] => ' ()' ['value'] => 790 ] [2] => [ ['name'] => '   ' ['value'] => 24 ] [3] => [ ['name'] => ' ' ['value'] => 75 ] ] ] [4] => [ ['prod_type'] => 'phone' ['properties'] => [ [10] => [ ['name'] => ' ' ['value'] => 5 ] [8] => [ ['name'] => ' ()' ['value'] => 0.12 ] [9] => [ ['name'] => '   ' ['value'] => 1 ] ] ] ] 

Et bien sĂ»r, en mĂȘme temps, une pagination et un tri pratiques: ->pagination(1, 10)->sort('id') .


La deuxiĂšme raison n'est pas si frĂ©quente, mais nĂ©anmoins elle se produit (et dans mon cas, c'est la raison principale). Si certaines entitĂ©s sont stockĂ©es dans la base de donnĂ©es et que les propriĂ©tĂ©s de ces entitĂ©s sont dynamiques et dĂ©finies par les utilisateurs, alors lorsque vous devez rechercher des entitĂ©s par leurs propriĂ©tĂ©s, vous devrez ajouter (joindre) la mĂȘme table avec les valeurs de propriĂ©tĂ© (autant de fois qu'elles sont utilisĂ©es). propriĂ©tĂ©s lors de la recherche). Le GP vous aidera donc Ă  connecter toutes les tables et Ă  gĂ©nĂ©rer la requĂȘte where avec presque une fonction. Vers la fin de l'article, j'analyserai ce cas en dĂ©tail.


Et enfin, tout cela devrait fonctionner à la fois pour la base de données Oracle et pour mySql. Il existe également un certain nombre de fonctionnalités décrites dans la documentation.


Il est possible que j'aie inventé un autre vélo, mais j'ai consciencieusement cherché et je n'ai pas trouvé de solution qui me convienne. Si vous connaissez une bibliothÚque qui résout ces problÚmes - veuillez écrire dans les commentaires.


Avant de passer directement Ă  un examen de la bibliothĂšque elle-mĂȘme et Ă  des exemples, je dirai qu'il n'y aura que l'essentiel, sans explications dĂ©taillĂ©es. Si vous vous intĂ©ressez au fonctionnement du GP , vous pouvez consulter la documentation , j'ai essayĂ© de tout expliquer en dĂ©tail.


Connexion Ă  la bibliothĂšque


La bibliothĂšque peut ĂȘtre installĂ©e via le compositeur: le composer require falbin/green-pig-dao


Ensuite, vous devez Ă©crire une usine Ă  travers laquelle vous utiliserez cette bibliothĂšque.


OĂč classe


En utilisant cette classe, vous pouvez composer la partie where de la requĂȘte sql de n'importe quelle complexitĂ©.


Partie atomique de la demande


ConsidĂ©rez la plus petite partie atomique d'une requĂȘte. Il est dĂ©crit par un tableau: [, , ]
Exemple: ['name', 'like', '%%']


  • Le premier Ă©lĂ©ment du tableau est juste une chaĂźne, insĂ©rĂ©e dans la requĂȘte sql sans modifications, et, par consĂ©quent, vous pouvez y Ă©crire des fonctions sql. Exemple: ['LOWER(name)', 'like', '%%']
  • Le deuxiĂšme Ă©lĂ©ment est Ă©galement une chaĂźne insĂ©rĂ©e dans sql sans changement entre deux opĂ©randes. Il peut prendre les valeurs suivantes: =,>, <,> =, <=, <>, comme, pas comme, entre, pas entre, dans, pas dans .
  • Le troisiĂšme Ă©lĂ©ment du tableau peut ĂȘtre de type numĂ©rique ou chaĂźne. OĂč la classe remplacera automatiquement l'alias gĂ©nĂ©rĂ© dans la requĂȘte SQL.
  • ÉlĂ©ment de tableau avec clĂ© sql. Parfois, il est nĂ©cessaire que la valeur soit insĂ©rĂ©e dans le code SQL sans modifications. Par exemple, pour appliquer des fonctions. Ceci peut ĂȘtre rĂ©alisĂ© en spĂ©cifiant 'sql' comme clĂ© (pour le 3Ăšme Ă©lĂ©ment). Exemple: ['LOWER(name)', 'like', 'sql' => "LOWER('$name')"]
  • Un Ă©lĂ©ment de tableau avec la clĂ© de liaison est un tableau pour stocker des liaisons. L'exemple ci-dessus est faux du point de vue de la sĂ©curitĂ©. Vous ne pouvez pas insĂ©rer de variables dans SQL - c'est trop d'injection. Par consĂ©quent, dans ce cas, vous devrez spĂ©cifier vous-mĂȘme des alias, par exemple comme ceci: ['LOWER(name)', 'like', 'sql' => "LOWER(:name)", 'bind'=> ['name' => $name] ]
  • L'opĂ©rateur in peut s'Ă©crire comme ceci: ['curse', 'not in', [1, 3, 5]] . La classe Where convertit une telle entrĂ©e dans le code SQL suivant: curse not in (:al_where_jCgWfr95kh, :al_where_mCqefr95kh, :al_where_jCfgfr9Gkh)
  • L'instruction entre peut ĂȘtre Ă©crite comme ceci: ['curse', ' between', 1, 5] . La classe Where convertit une telle entrĂ©e dans le code SQL suivant: curse between :al_where_Pi4CRr4xNn and :al_where_WiPPS4NKiG
    Mais attention, si les troisiÚme et quatriÚme éléments du tableau sont des chaßnes, une logique spéciale est appliquée. Dans ce cas, on pense que la sélection est à partir d'une plage de dates et, par conséquent, la fonction sql de transtyper une chaßne en une date est utilisée. La fonction de conversion en une date (mySql et Oracle en ont différentes) et ses paramÚtres sont tirés d'un tableau de paramÚtres (plus dans la documentation). Le tableau ['build_date', 'between', '01.01.2016', '01.01.2019'] sera converti en sql: build_date between TO_DATE(:al_where_fkD7nZg5lU, 'dd.mm.yyyy hh24:mi::ss') and TO_DATE(:al_where_LdyVRznPF8, 'dd.mm.yyyy hh24:mi::ss')

RequĂȘtes compliquĂ©es


Créons une instance de la classe via la fabrique: $wh = GP::where();


Pour indiquer la connexion logique entre les "parties atomiques" de la demande, vous devez utiliser les fonctions linkAnd() ou linkOr() . Un exemple:


 // sql: (old > 18 and old < 50) $wh->linkAnd([ ['old', '<', 18], ['old', '>', 50] ]); // sql: (old < 18 or old > 50) $wh->linkOr([ ['old', '<', 18], ['old', '>', 50] ]); 

Lorsque vous utilisez les fonctions linkAnd / linkOr, toutes les données sont stockées dans une instance de la classe Where - $ wh. De plus, toutes les «parties atomiques» indiquées dans la fonction sont entre crochets .


Un SQL de toute complexitĂ© peut ĂȘtre dĂ©crit par trois fonctions: linkAnd(), linkOr(), getRaw() . Prenons un exemple:


 // sql: curse = 1 and (old < 18 or old > 50) $wh->linkAnd([ ['curse', '=', 1], $wh->linkOr([ ['old', '<', 18], ['old', '>', 50] ])->getRaw() ]); 

La classe Where a une variable privée qui stocke l'expression brute. Les linkAnd() et linkOr() cette variable, par conséquent, lors de la construction d'une expression logique, les méthodes sont imbriquées ensemble et la variable avec l'expression brute contient des données obtenues à partir de la derniÚre méthode exécutée.


Rejoindre la classe


Join est une classe qui gĂ©nĂšre un fragment de jointure de code SQL. CrĂ©ons une instance de la classe via l'usine: $jn = GP::leftJoin('coursework', 'student_id', 's.id') , oĂč:


  • les cours sont la table que nous allons rejoindre.
  • student_id - une colonne avec une clĂ© Ă©trangĂšre de la table des cours .
  • s.id - la colonne de la table avec laquelle join doit ĂȘtre Ă©crite avec l'alias de la table (dans ce cas, l'alias de la table est s).

SQL généré: left JOIN coursework coursework_joM9YuTTfW ON coursework_joM9YuTTfW.student_id = s.id


Lors de la crĂ©ation d'une instance de la classe, nous avons dĂ©jĂ  dĂ©crit la condition pour joindre des tables, mais il peut ĂȘtre nĂ©cessaire de clarifier et d'Ă©tendre la condition. Les fonctions linkAnd / linkOr vous aideront Ă  faire ceci: $jn->linkAnd(['semester_number', '>', 2])


SQL généré: inner JOIN coursework coursework_Nd1n5T7c0r ON coursework_Nd1n5T7c0r.student_id = s.id and (semester_number > :al_where_M1kEcHzZyy)


S'il y a plusieurs tables Ă  joindre, vous pouvez les combiner dans une classe: CollectionJoin .


RequĂȘte de classe


C'est la classe principale pour travailler avec la base de données, à travers elle il y a une sélection, un enregistrement, une mise à jour et une suppression des données. Vous pouvez également effectuer certains traitements de données obtenues à partir de la base de données.


Prenons un exemple typique.


Créons une instance de la classe via la fabrique: $qr = GP::query();


Maintenant, nous allons définir le modÚle sql, remplacer les valeurs nécessaires pour le scénario donné dans le modÚle sql et dire que nous voulons obtenir un enregistrement, et en particulier les données de la colonne average_mark .


 $rez = $qr->sql("select /*select*/ from student s inner join mark m on s.id = m.student_id inner join lesson l on l.id = m.lesson_id /*where*/ /*group*/") ->sqlPart('/*select*/', 's.name, avg(m.mark) average_mark', []) ->whereAnd('/*where*/', ['s.id', '=', 1]) ->sqlPart('/*group*/', 'group by s.name', []) ->one('average_mark'); 

RĂ©sultat: 3,16666666666666666666666666666666666667


Sélection à partir d'une base de données avec des paramÚtres imbriqués


Surtout, je n'ai pas eu l'occasion d'obtenir une sélection de la base de données dans une arborescence, avec des propriétés attachées. Par conséquent, la bibliothÚque GP a une telle opportunité et la profondeur d'imbrication n'est pas limitée.


La maniÚre la plus simple de considérer le principe de fonctionnement est basée sur un exemple. Pour examen, nous prenons le schéma de base de données suivant :



Contenu du tableau:



Souvent, lorsque vous interrogez une base de donnĂ©es, vous souhaitez obtenir une rĂ©ponse arborescente et non plate. Par exemple, en exĂ©cutant cette requĂȘte:


 SELECT s.id, s.name, c.id title_id, c.title FROM student s INNER JOIN coursework c ON c. student_id = s.id WHERE s.id = 3 

Nous obtenons un résultat plat:


 [ 0 => [ 'id' => 3, 'name' => '', 'title_id' => 6, 'title' => '«»    ', ], 1=> [ 'id' => 3, 'name' => '', 'title_id' => 7, 'title' => '  ' ] ] 

En utilisant GP, vous pouvez obtenir ce résultat:


 [ 3 => [ 'name' => '', 'courseworks' => [ 6 => ['title' => '«»    '], 7 => ['title' => '  '] ] ] ] 

Pour obtenir ce rĂ©sultat, vous devez passer un tableau avec des options Ă  la fonction all (la fonction renvoie toutes les lignes de requĂȘte):


 all([ 'id'=> 'pk', 'name' => 'name', 'courseworks' => [ 'title_id' => 'pk', 'title' => 'title' ] ]) 

Le tableau $option dans l' agrégateur de fonctions ( $option , $ rawData) et all ( $options ) est construit selon les rÚgles suivantes:


  • ClĂ©s de tableau - noms de colonnes. ÉlĂ©ments de tableau - nouveaux noms pour les colonnes, vous pouvez entrer l'ancien nom.
  • Il y a un mot rĂ©servĂ© pour les valeurs du tableau - pk . Il indique que les donnĂ©es seront regroupĂ©es par cette colonne (la clĂ© du tableau est le nom de la colonne).
  • À chaque niveau, il ne devrait y avoir qu'un seul pk .
  • Dans le tableau agrĂ©gĂ© (rĂ©sultant), les valeurs de la colonne dĂ©clarĂ©e par pk seront utilisĂ©es comme clĂ©s.
  • S'il est nĂ©cessaire de placer une partie des colonnes Ă  un niveau infĂ©rieur, un nouveau nom inventĂ© est utilisĂ© comme clĂ© de tableau, et un tableau construit selon les rĂšgles dĂ©crites ci-dessus sera utilisĂ© comme valeur.

Prenons un exemple plus complexe. Supposons que nous devions obtenir tous les Ă©tudiants avec le nom de leur cours et avec toutes les notes dans toutes les matiĂšres. Nous aimerions le recevoir non pas sous une forme plate, mais sous forme d'arbre, sans doublons. Vous trouverez ci-dessous la requĂȘte souhaitĂ©e dans la base de donnĂ©es et le rĂ©sultat.


 SELECT s.id student_id, s.name student_name, s.semester_number, c.id coursework_id, c.semester_number coursework_semester, c.title coursework_title, l.id lesson_id, l.name lesson, m.id mark_id, m.mark FROM student s LEFT JOIN coursework c ON c.student_id = s.id LEFT JOIN mark m ON m.student_id = s.id LEFT JOIN lesson l ON l.id = m.lesson_id ORDER BY s.id, c.id, l.id, m.id 

Le résultat ne nous convient pas:



Pour réaliser la tùche, vous devez écrire le tableau $option suivant:


 $option = [ 'student_id' => 'pk', 'student_name' => 'name', 'courseworks' => [ 'coursework_semester' => 'pk', 'coursework_title' => 'title' ], 'lessons' => [ 'lesson_id' => 'pk', 'lesson' => 'lesson', 'marks' => [ 'mark_id' => 'pk', 'mark' => 'mark' ] ] ]; 

RequĂȘte de base de donnĂ©es:


 //    Query   .       // (    yii2) $qr = Yii::$app->gp->query( "SELECT s.id student_id, s.name student_name, s.semester_number, c.id coursework_id, c.semester_number coursework_semester, c.title coursework_title, l.id lesson_id, l.name lesson, m.id mark_id, m.mark FROM student s LEFT JOIN coursework c ON c.student_id = s.id LEFT JOIN mark m ON m.student_id = s.id LEFT JOIN lesson l ON l.id = m.lesson_id ORDER BY s.id, c.id, l.id, m.id"); //   1,  2   3  //  1 $result = $qr->all($option); //  2 $result = $qr->aggregator($option, $qr->all()); //  3 $qr->all(); $result = $qr->aggregator($option, $qr->rawData()); 

La fonction d' aggregator peut traiter n'importe quel tableau avec une structure similaire au rĂ©sultat d'une requĂȘte de base de donnĂ©es, selon les rĂšgles dĂ©crites dans l' $option .


La variable $result contient les données suivantes:


 [ 1 => [ 'name' => '', 'courseworks' => [ 1 => ['title' => '    '], ], 'lessons' => [ 1 => [ 'lesson' => '', 'marks' => [ 1 => ['mark' => 3], 2 => ['mark' => 4] ] ], 2 => [ 'lesson' => '', 'marks' => [ 3 => ['mark' => 2], 4 => ['mark' => 2], 5 => ['mark' => 3] ] ], 4 => [ 'lesson' => '', 'marks' => [ 6 => ['mark' => 5] ] ] ] ], 3 => [ 'name' => '', 'courseworks' => [ 1 => ['title' => '«»    '], 2 => ['title' => '  '] ], 'lessons' => [ 1 => [ 'lesson' => '', 'marks' => [ 17 => ['mark' => 5] ] ], 2 => [ 'lesson' => '', 'marks' => [ 18 => ['mark' => 2] ] ], 3 => [ 'lesson' => '-', 'marks' => [ 20 => ['mark' => 4] ] ], 4 => [ 'lesson' => '', 'marks' => [ 16 => ['mark' => 2], 19 => ['mark' => 3] ] ], ] ] ] 

Soit dit en passant, avec la pagination avec une requĂȘte agrĂ©gĂ©e, seules les donnĂ©es les plus Ă©lĂ©mentaires sont prises en compte. Dans l'exemple ci-dessus, il n'y aura que 2 lignes pour la pagination.


Union multiple avec vous-mĂȘme au nom de la recherche


Comme je l'ai Ă©crit plus tĂŽt, la tĂąche principale de ma bibliothĂšque est de simplifier la gĂ©nĂ©ration des parties where pour certaines requĂȘtes. Donc, dans ce cas, nous devrons peut-ĂȘtre joindre Ă  plusieurs reprises la mĂȘme table pour quelle requĂȘte? L'une des options est lorsque nous avons un certain produit dont les propriĂ©tĂ©s ne sont pas connues Ă  l'avance et qu'elles seront ajoutĂ©es par les utilisateurs, et nous devons avoir la possibilitĂ© de rechercher des produits par ces propriĂ©tĂ©s dynamiques. La façon la plus simple d'expliquer avec un exemple simplifiĂ©.


Supposons que nous ayons une boutique en ligne vendant des composants informatiques, que nous n'ayons pas d'assortiment strict et que nous achetions pĂ©riodiquement un composant ou un autre. Mais nous aimerions dĂ©crire tous nos produits comme une entitĂ© unique et rechercher tous les produits. Alors, quelles entitĂ©s peuvent ĂȘtre distinguĂ©es du point de vue de la logique mĂ©tier:


  1. Produit. L'entité la plus importante autour de laquelle tout est construit.
  2. Type de produit. Cela peut ĂȘtre reprĂ©sentĂ© comme la propriĂ©tĂ© racine de toutes les autres propriĂ©tĂ©s du produit. Par exemple, dans notre petit magasin, c'est seulement: RAM, SSD et HDD.
  3. PropriĂ©tĂ©s du produit. Dans notre mise en Ɠuvre, toute propriĂ©tĂ© peut s'appliquer Ă  tout type de produit, le choix reste sur la conscience du manager. Dans notre magasin, les gestionnaires n'ont crĂ©Ă© que 3 propriĂ©tĂ©s: la taille de la mĂ©moire, le facteur de forme et le DDR.
  4. La valeur des marchandises. La valeur que l'acheteur générera lors de la recherche.

Toute la logique métier décrite ci-dessus est reflétée en détail dans l'image ci-dessous.



Par exemple, nous avons un produit: 16 Go de RAM DDR 3 . Dans le diagramme, cela peut ĂȘtre affichĂ© comme suit:



La structure et les données de la base de données sont clairement visibles dans la figure suivante:



Comme nous le voyons sur le diagramme, toutes les valeurs de toutes les propriétés sont stockées dans une table de valeurs (au fait, dans notre version simplifiée, toutes les propriétés ont des valeurs numériques). Par conséquent, si nous voulons rechercher simultanément plusieurs propriétés avec un tas de ET, nous obtenons une sélection vide.


Par exemple, un acheteur recherche des produits adaptĂ©s Ă  une telle demande: la quantitĂ© de mĂ©moire doit ĂȘtre supĂ©rieure Ă  10 Go et le facteur de forme doit ĂȘtre de 2,5 pouces . Si nous Ă©crivons sql comme indiquĂ© ci-dessous, nous obtenons une sĂ©lection vide:


 select * from product p inner join val v on v.product_id = p.id where (v.property_id = 1 and v.value > 10) AND (v.property_id = 3 and v.value = 2.5) 

Étant donnĂ© que les valeurs de toutes les propriĂ©tĂ©s sont stockĂ©es dans une table, pour rechercher plusieurs propriĂ©tĂ©s, vous devez joindre la valeur de table pour chaque propriĂ©tĂ© qui sera recherchĂ©e. Mais il y a une nuance, joindre les tables de jointures «horizontalement» (au mot union, toutes les jointures «verticales»), voici un exemple:



Ce résultat ne nous convient pas, nous aimerions voir toutes les valeurs dans une colonne. Pour ce faire, vous devez joindre la table val 1 fois plus que les propriétés par lesquelles la recherche est effectuée.



Nous sommes sur le point de gĂ©nĂ©rer automatiquement une requĂȘte SQL. Regardons la fonction
whereWithJoin ($aliasJoin, $options, $aliasWhere, $where) , qui fera tout le travail:


  • $ aliasJoin - un alias dans le modĂšle de base, au lieu duquel la partie SQL avec jointures est remplacĂ©e.
  • $ options - un tableau avec des descriptions des rĂšgles de gĂ©nĂ©ration de la piĂšce jointe.
  • $ aliasWhere - un alias dans le modĂšle de base, qui remplace la partie where sql Ă  la place.
  • $ where est une instance de la classe Where.

Regardons un exemple: whereWithJoin('/*join*/', $options, '/*where*/', $wh) .


Créez d'abord la variable $ options : $options = ['v' => ['val', 'product_id', 'p.id']];


v est l'alias de la table. Si l'alias donnĂ© se trouve dans $ wh , une nouvelle table val sera connectĂ©e par jointure (oĂč product_id est la clĂ© Ă©trangĂšre de la table val , et p.id est la clĂ© primaire de la table avec l'alias p ), un nouvel alias et cet alias sont gĂ©nĂ©rĂ©s pour cela remplacera v oĂč.


$ wh est une instance de la classe Where. Nous formons la mĂȘme demande: la mĂ©moire doit ĂȘtre supĂ©rieure Ă  10 Go et le facteur de forme doit ĂȘtre de 2,5 pouces.


 $wh->linkAnd([ $wh->linkAnd([ ['v.property_id', '=', 1], ['v.value', '>', 10] ])->getRaw(), //    $wh->linkAnd([ ['v.property_id', '=', 3], ['v.value', '=', 2.5] ])->getRaw(),//    ]); 

Lors de la crĂ©ation d'une requĂȘte where, il est nĂ©cessaire d'envelopper la partie avec la propriĂ©tĂ© id et sa valeur entre crochets, cela indique Ă  la fonction whereWithJoin() que l'alias de table sera le mĂȘme dans cette partie.


 $qr->sql("select p.id, t.name type_name, pr.id prop_id, pr.name prop_name, v.id val_id, v.value from product p inner join type t on t.id = p.type_id inner join val v on v.product_id = p.id inner join properties pr on pr.id = v.property_id /*join*/ /*where*/") ->whereWithJoin('/*join*/', $options, '/*where*/', $wh) //     . ->all([ 'id' => 'pk', 'type_name' => 'type', 'properties' => [ 'prop_id' => 'pk', 'prop_name' => 'name', 'values' => [ 'val_id' => 'pk', 'value' => 'val' ] ] ]); 

Nous examinons le sql gĂ©nĂ©rĂ©, les $qr->debugInfo() et le temps d'exĂ©cution de la requĂȘte: $qr->debugInfo() :


 [ [ 'type' => 'info', 'sql' => 'select p.id, t.name type_name, pr.id prop_id, pr.name prop_name, v.id val_id, v.value from product p inner join type t on t.id = p.type_id inner join val v on v.product_id = p.id inner join properties pr on pr.id = v.property_id inner JOIN val val_mIQWpnHhdQ ON val_mIQWpnHhdQ.product_id = p.id inner JOIN val val_J0uveMpwEM ON val_J0uveMpwEM.product_id = p.id WHERE ( val_mIQWpnHhdQ.property_id = :al_where_leV5QlmOZN and val_mIQWpnHhdQ.value > :al_where_ycleYAswIw ) and ( val_J0uveMpwEM.property_id = :al_where_dinxDraTOE and val_J0uveMpwEM.value = :al_where_wZJhUqs74i )', 'binds' => [ 'al_where_leV5QlmOZN' => 1, 'al_where_ycleYAswIw' => 10, 'al_where_dinxDraTOE' => 3, 'al_where_wZJhUqs74i' => 2.5 ], 'timeQuery' => 0.0384588241577 ] ] 

$qr->rawData() :


 [ [ 'id' => 3, 'type_name' => 'SSD', 'prop_id' => 1, 'prop_name' => ' ', 'val_id' => 5, 'value' => 512 ], [ 'id' => 3, 'type_name' => 'SSD', 'prop_id' => 3, 'prop_name' => '-', 'val_id' => 6, 'value' => 2.5 ], [ 'id' => 4, 'type_name' => 'SSD', 'prop_id' => 1, 'prop_name' => ' ', 'val_id' => 7, 'value' => 256 ], [ 'id' => 4, 'type_name' => 'SSD', 'prop_id' => 3, 'prop_name' => '-', 'val_id' => 8, 'value' => 2.5 ], [ 'id' => 6, 'type_name' => 'HDD', 'prop_id' => 1, 'prop_name' => ' ', 'val_id' => 11, 'value' => 1024 ], [ 'id' => 6, 'type_name' => 'HDD', 'prop_id' => 3, 'prop_name' => '-', 'val_id' => 12, 'value' => 2.5 ] ] 

$qr->aggregateData() :


 [ 3 => [ 'type' => 'SSD', 'properties' => [ 1 => [ 'name' => ' ', 'values' => [ 5 => ['val' => 512] ] ], 3 => [ 'name' => '-', 'values' => [ 6 => ['val' => 2.5] ] ] ] ], 4 => [ 'type' => 'SSD', 'properties' => [ 1 => [ 'name' => ' ', 'values' => [ 7 => ['val' => 256] ] ], 3 => [ 'name' => '-', 'values' => [ 8 => ['val' => 2.5] ] ] ] ], 6 => [ 'type' => 'HDD', 'properties' => [ 1 => [ 'name' => ' ', 'values' => [ 11 => ['val' => 1024] ] ], 3 => [ 'name' => '-', 'values' => [ 12 => ['val' => 2.5] ] ] ] ] ] 

, , whereWithJoin() , .


whereWithJoin() , , n , m . n m 1 id . , AND .




GitHub .

Source: https://habr.com/ru/post/fr483458/


All Articles