web-dev-qa-db-fra.com

Problèmes à connaître lors de l'appel de `concatenate ()`, `regex ()`, `Rand ()` et `cast As Char ()` dans les requêtes SQL

Je prends sur moi de prendre conscience de certaines des méthodes de requête moins connues/utilisées parce que j'aime jouer avec MySQL et je veux implémenter les appels Joomla lorsque cela est possible/raisonnable - parce qu'ils ont été conçus pour être utilisés.

Pour cette question, je fais référence à:
https://github.com/joomla/joomla-cms/blob/staging/libraries/joomla/database/query/mysqli.php

La liste actuelle des méthodes est la suivante:

  • __toString()
  • processLimit()
  • concatenate()
  • setLimit()
  • regexp()
  • Rand()
  • selectRowNumber()
  • castAsChar()

Maintenant, j'ai vu __toString() et setLimit() utilisés avec une certaine fréquence, et je n'ai encore découvert aucun hoquet avec processLimit() ou selectRowNumber() ce ne sont pas des scénarios de faute de frappe. Pour ces raisons, je vais éviter toute discussion sur ces quatre méthodes.

Pour formuler une question:

Quels types de pièges dois-je savoir lors de l'appel: concatenate(), regexp(), Rand() et castAsChar() dans les requêtes SQL?

2
mickmackusa

Toutes mes démonstrations supposeront que cette ligne est déjà déclarée:

$db = JFactory::getDBO();

Je fournirai également un tableau "cobaye" comme référence de démonstration et terrain de jeu pour les chercheurs lorsque des données d'échantillonnage sont nécessaires pour le contexte. ( démo db-fiddle.com )

CREATE TABLE IF NOT EXISTS `#__calendar` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `month` varchar(100) NOT NULL,
  `color` varchar(100) NOT NULL,
  `total` int(11) NOT NULL,
  `status` varchar(10) NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `month` (`month`)
) ENGINE=InnoDB AUTO_INCREMENT=13 DEFAULT CHARSET=utf8;

INSERT INTO `#__calendar` (`id`, `month`, `color`, `total`, `status`) VALUES
(1, 'January', 'red', 234, 'active'),
(2, 'February', 'red-orange', 998, 'inactive'),
(3, 'March', 'orange', 10, 'active'),
(4, 'April', 'orange-yellow', 718, null),
(5, 'May', 'yellow', 11, 'active'),
(6, 'June', 'yellow-green', 209, 'active'),
(7, 'July', 'green', 315, 'active'),
(8, 'August', 'green-blue', 8, 'inactive'),
(9, 'September', 'blue', 555, 'active'),
(10, 'October', 'blue-Violet', 188, 'active'),
(11, 'November', 'Violet', 39, 'active'),
(12, 'December', 'Violet-red', 711, 'inactive');

| id  | month     | color         | total | status   |
| --- | --------- | ------------- | ----- | -------- |
| 1   | January   | red           | 234   | active   |
| 2   | February  | red-orange    | 998   | inactive |
| 3   | March     | orange        | 10    | active   |
| 4   | April     | orange-yellow | 718   |          |
| 5   | May       | yellow        | 11    | active   |
| 6   | June      | yellow-green  | 209   | active   |
| 7   | July      | green         | 315   | active   |
| 8   | August    | green-blue    | 8     | inactive |
| 9   | September | blue          | 555   | active   |
| 10  | October   | blue-Violet   | 188   | active   |
| 11  | November  | Violet        | 39    | active   |
| 12  | December  | Violet-red    | 711   | inactive |

Voir sur DB Fiddle Voici une liste que j'ai compilée qui révèle les problèmes possibles/probables et les malentendus qui peuvent être rencontrés lors de l'appel des méthodes à partir de l'objet $query:


Enchaînement d'un objet non déclaré

Si vous avez l'habitude de chaîner à partir de la déclaration de variable $query, Vous rencontrerez probablement un accroc si vous voulez appeler une méthode à partir d'une méthode chaînée.

Problème:

$query = $db->getQuery(true)
    ->select($query->Rand());

Erreur: 0 Appel à une fonction membre Rand () sur null

Au lieu de cela, vous devez d'abord déclarer l'objet $query En tant que variable avant de pouvoir appeler une méthode (non chaînée) à partir de celui-ci.

Solution: Déclarez $query Puis enchaînez au contenu de votre cœur.

$query = $db->getQuery(true);
$query->select($query->Rand());  // output: 0.28479570456295805

Paramètres nuls avec concaténat ()

concatenate() appelle conditionnellement l'une des deux fonctions MySQL. Si vous fournissez une valeur non-falsey (pas une valeur vide, fausse, nulle ou nulle) comme deuxième paramètre, CONCAT_WS() sera appelée, sinon CONCAT(). Apprenez la différence. Alors que Joomla essaie de fournir une protection contre une valeur null dérangeante en tant que paramètre séparateur/colle via if ($separator), il ruine la possibilité de coller des colonnes/du texte qui peuvent être ou ne pas être NULL avec un espace vide (pas NULL). Ce qui pourrait causer des problèmes est si CONCAT() est rendu par la méthode et qu'une des valeurs à coller ensemble est NULL.

Problème: Syntaxe:

$query = $db->getQuery(true);
$query->select($query->concatenate(["month", "status"]))
    ->from("#__calendar")
    ->where("id = 4");`

ou

$query = $db->getQuery(true);
$query->select($query->concatenate(["month", "status"], ""))
    ->from("#__calendar")
    ->where("id = 4");`

Sortie: [~ # ~] null [~ # ~] au lieu de: "April" (avec une chaîne vide collée à la fin)

Solutions : Découvrez ici les techniques de conversion NULL et d'autres solutions .
Il y a trop de cas marginaux à préciser ici, mais vous pourriez souhaiter utiliser COALESCE(`column_with_possibly_null_values`,'') ou IFNULL(`column_with_possibly_null_values`,'') ou éviter complètement la méthode Joomla et coder manuellement votre fonction MySQL préférée .

Recommandation : J'aimerais que les gitters envisagent de modifier le code source de if ($separator) à if ($separator !== null) ou similaire pour permettre l'utilisation d'une chaîne vide comme colle.


Erreur de syntaxe avec regexp ()

Problème: Syntaxe:

$query = $db->getQuery(true);
$query->select("month")
    ->from("#__calendar")
    ->where($db->qn("month") . $query->regexp("[^elrty]$"));

Erreur: Erreur de syntaxe de requête ... près de [^elrty]$

Solution : Le modèle d'expression régulière doit être mis entre guillemets, donc utilisez $db-q() ou $db->quote().

$query = $db->getQuery(true);
$query->select("month")
    ->from("#__calendar")
    ->where($db->qn("month") . $query->regexp($db->q("[^elrty]$")));  // output: March

De plus : Si vous allez transmettre des données fournies par l'utilisateur/non fiables dans le modèle d'expression régulière, sachez que vous devrez échapper des caractères ayant une signification spéciale pour le moteur regex. En php, l'outil est presque toujours preg_quote(), mais comme nous transmettons la valeur à MySQL, un ajustement supplémentaire est nécessaire (une double barre oblique inverse est utilisée pour l'échappement). Si vous n'êtes pas trop familier avec l'implémentation ou les expressions régulières de MySQL, sachez que les délimiteurs de modèle ne sont pas utilisés et que si vous allez utiliser des barres obliques dans votre modèle, vous devrez écrire \\\\ En php pour chaque \ Que vous avez l'intention de livrer.

Nouvel exemple : si une ligne avait une valeur month de wh^t? the hey! Et que vous vouliez la rechercher en fournissant $search = "wh^t? the" Dans regexp(), vous auriez besoin d'une clause where codée comme ceci:

->where("month " . $query->regexp($db->q(preg_replace('~[-/\\^$*+?.()|[\]{}]~u', '\\\\\\\\$0', $search), false)));
// yes, you really do need all of these backslashes ------------------------------^^^^^^^^

et il serait rendu comme WHERE month REGEXP 'wh\\^t\\? the'

De plus, si vous vouliez inclure les limites de Word des deux côtés de la variable de recherche dans votre logique de condition, l'expression ressemblerait à ceci:

->where("month " . $query->regexp($db->quote('([[:<:]]|^)' . preg_replace('~[-/\\^$*+?.()|[\]{}]~u', '\\\\\\\\$0', $search) . '([[:>:]]|$)', false)));

et il rendrait comme WHERE month REGEXP '([[:<:]]|^)wh\\^t\\? the([[:>:]]|$)'


Passer un Seed à Rand ()

Il s'agit certainement d'une fonctionnalité moins connue et peu susceptible d'être implémentée pour la grande majorité des requêtes à la recherche de randomisation, mais la fonction Rand() de MySQL accepte un paramètre facultatif à utiliser comme "graine". En savoir plus. Ceci est utile si vous avez besoin d'une séquence prévisible/répétable de "nombres aléatoires" pour votre requête. Malheureusement, le noyau Joomla ne reconnaît aucun paramètre passé à cette fonction et omettra silencieusement la valeur de départ.

Problème: Syntaxe:

$query = $db->getQuery(true);
$query->select("month")
    ->from("#__calendar")
    ->where("FLOOR(" . $query->Rand(2) . "*10) = 9");

Sortie: [jeu de résultats complètement aléatoire/imprévisible]

Solution : N'appelez pas la méthode Rand() lorsque vous fournissez une valeur de départ.

$query = $db->getQuery(true);
$query->select("month")
    ->from("#__calendar")
    ->where("FLOOR(Rand(2)*10) = 9");  // output: June (*everytime)

Recommandation : Je voudrais voir le code source mis à jour pour permettre le passage d'une valeur de départ. Dans l'ensemble, je trouve que cette méthode est un gaspillage de programmation, car offre moins de fonctionnalités et gonfle le processus de création de requêtes avec plus de caractères. Avons-nous besoin d'une méthode pour chaque fonction? par exemple. FLOOR(), MAX(), etc? Si rien d'autre n'est fait, la majuscule R typo doit être modifiée dans le code source.

* Divulgation complète, je n'ai jamais utilisé Rand() avec une graine dans aucun de mes projets - c'est une nouvelle découverte pour moi.


Utilisation de castAsChar () sans paramètre de longueur

Problème: Syntaxe:

$query = $db->getQuery(true);
$query->select("id, month")
      ->from($db->qn("#__calendar"))
      ->where("id IN (1,2,10,11,12)")
      ->order($query->castAsChar("id"));

Sortie: [tri naturel (numérique) au lieu du tri informatique (alphabétique)]

En savoir plus sur le tri naturel par rapport au tri par ordinateur. Le code source est conçu pour annuler l'action par défaut de la méthode si aucun paramètre de longueur n'est fourni dans l'appel. La clause d'ordre rendue devient: ORDER BY id <- aucune trace de CHAR() malgré l'intention du développeur.

Solution : N'appelez pas cette méthode si vous ne fournissez pas de paramètre de longueur.

$query->select("id, month")
    ->from($db->qn("#__calendar"))
    ->where("id IN (1,2,10,11,12)")
    ->order("CAST(id AS CHAR)");  // output order: 1, 10, 11, 12, 2

De plus : cette fonction peut être utilisée pour tronquer une valeur à un certain nombre de caractères (multibyte-safe). Ainsi, SELECT CAST('âêîôû' AS CHAR(2)) renvoie âê.

Recommandation : Fournir plus de flexibilité (ou des méthodes supplémentaires) aux types de casting qui peuvent être effectués semble être une bonne idée. En savoir plus sur les autres types.

3
mickmackusa