web-dev-qa-db-fra.com

Comment collecter des données multi-lignes à partir de #__fields_values ​​et affecter des alias spécifiques pour chaque valeur dans le jeu de résultats?

J'ai la requête (de travail) suivante pour obtenir name, username et le value d'un champ personnalisé:

// Get a db connection.
$db = JFactory::getDbo();

// Create a new query object.
$query = $db->getQuery(true);

// Select name and username from USERS table and value from FIELDS_VALUES table.
// Define USERS table as ju
// Define FIELD_VALUES as jfv and match id and item_id to perform an inner join
// Set condition
// Set Order as ascending

$query
    ->select(array('ju.username', 'ju.name', 'jfv.value'))
    ->from($db->quoteName('#__users', 'ju'))
    ->join('INNER', $db->quoteName('#__fields_values', 'jfv') . ' ON (' . $db->quoteName('ju.id') . ' = ' . $db->quoteName('jfv.item_id') . ')')
    ->where($db->quoteName('jfv.value') . ' LIKE ' . $db->quote('C%'))
    ->order($db->quoteName('ju.username') . ' ASC');

// Reset the query using our newly populated query object.
$db->setQuery($query);

// Load results as a list of objects in an array
$results = $db->loadObjectList();
print_r($results);

Il est utilisé pour afficher les name et username d'un utilisateur avec door, floor et staircase dans un immeuble à appartements. Dans ce cas, toutes les personnes de staircase "C".

La table #__fields_values ressemble à ceci pour l'utilisateur 48:

field_id | item_id | value |
============================
2        | 48      | 5     |
============================
3        | 48      | C     |
============================
4        | 48      | 2     |
============================

Dans le tableau ci-dessus:

  • la valeur door est 5
  • la valeur de staircase est C
  • la valeur floor est 2

Mon problème est que ma sortie ressemble à ceci:

Array (
    [0] => stdClass Object (
        [username] => C1 [name] => NameOfTheGuy [value] => C
    )...

mais ce dont j'ai besoin, c'est d'avoir la valeur floor, pas la valeur staircase.

Est-il possible d'affecter un alias en fonction de la valeur à l'intérieur d'une colonne (disons jvf.staircase si jfv.field_id = 3)? Ou existe-t-il une meilleure façon de réaliser ce que je veux faire?

1
FollaKY

Requête de pivot brut

SELECT ju.username,
       ju.name,
       MAX(IF(jfv.field_id = 2, jfv.value, NULL)) AS `door`,
       MAX(IF(jfv.field_id = 3, jfv.value, NULL)) AS `staircase`,
       MAX(IF(jfv.field_id = 4, jfv.value, NULL)) AS `floor`
FROM `#__users` AS ju
INNER JOIN `#__fields_values` AS jfv ON ju.id = jfv.item_id
GROUP BY ju.username ASC, ju.name
HAVING MAX(IF(jfv.field_id = 3, jfv.value, NULL)) LIKE 'C%'

Jeu de résultats possible:

| username | name  | door | staircase | floor |
| -------- | ----- | ---- | --------- | ----- |
| FollaKY  | Folla | 5    | C         | 2     |

Voir la démo sur DB Fiddle

Code PHP/Joomla (non testé testé):

$db = JFactory::getDbo();
$juUsername = $db->qn("ju.username");   // Cache this (D.R.Y.)
$juName     = $db->qn("ju.name");       // Cache this (D.R.Y.)
$jfvFieldId = $db->qn("jfv.field_id");  // Cache this (D.R.Y.)
$jfvValue   = $db->qn("jfv.value");     // Cache this (D.R.Y.)

$query = $db->getQuery(true)
    ->select([
        $juUsername,
        $juName,
        "MAX(IF($jfvFieldId = 2, $jfvValue, NULL)) AS " . $db->qn('door'),
        "MAX(IF($jfvFieldId = 3, $jfvValue, NULL)) AS " . $db->qn('staircase'),
        "MAX(IF($jfvFieldId = 4, $jfvValue, NULL)) AS " . $db->qn('floor')
    ])
    ->from($db->qn('#__users', 'ju'))
    ->innerJoin($db->qn('#__fields_values', 'jfv') . ' ON ' . $db->qn('ju.id') . ' = ' . $db->qn('jfv.item_id'))
    ->group([
        "$juUsername ASC",  // declare the sorting order here
        $juName
    ])
    ->having("MAX(IF($jfvFieldId = 3, $jfvValue, NULL) LIKE " . $db->q("C%"));

// echo $query->dump();  // uncomment if you want to confirm the rendered query
try {
    $db->setQuery($query);
    echo "<pre>";
    var_export($db->loadObjectList());
} catch (Exception $e) {
    JFactory::getApplication()->enqueueMessage("Query Syntax Error: " . $e->getMessage(), 'error');  // never show getMessage() to public
}

Explication par étapes:

  1. Joignez la table users à la table fields_values. Il s'agit d'une relation un-à-plusieurs pour chaque ligne users. L'utilisation de INNER JOIN Est différente de LEFT JOIN Car INNER JOIN Omettra users lignes qui n'ont pas au moins une ligne fields_values À associer.

  2. Pour empêcher plusieurs lignes pour le même utilisateur, GROUP BY Est implémenté. Cela crée des "données agrégées" (en d'autres termes, un nuage | masse | cluster | pile de fields_values Données spécifiques à cet utilisateur) pour chaque utilisateur.

  3. Pour déterminer quels utilisateurs sont éligibles pour l'ensemble de résultats en fonction d'une valeur spécifique à la ligne dans fields_values, Vous devrez effectuer une comparaison sur les données agrégées. La clause HAVING vérifiera toutes les données agrégées. Si une ligne agrégée n'a pas de field_id De 3, Une valeur de NULL lui est attribuée (dans le cadre de ce sous-processus). Si field_id Est 3, L'original value est conservé. Cela crée (sauf si vous avez une ligne avec field_id De 3 Et un value de NULL - auquel cas chaque value est NULL) une seule valeur non NULL à être "récupérée" par MAX() - appliquez votre logique de comparaison sur cette seule valeur.

  4. Maintenant que toutes les jointures, regroupements et filtres sont terminés, il est temps de corriger le jeu de résultats réel. À ce stade, il reste des "données agrégées" envoyées à la clause SELECT, mais l'ensemble de résultats ne peut pas être livré avec des "données agrégées" (en d'autres termes, les lignes doivent être aplaties). En utilisant la même technique filter & max que dans la clause HAVING, écrivez manuellement chaque colonne spécifique que vous souhaitez générer pour chaque ligne du jeu de résultats - attribuez l'alias de colonne que vous souhaitez après AS. Terminé.


Maintenant, il existe plusieurs façons de coder cela ...

  • "SEC." (trouvé comme commentaires dans mon script) signifie "Don't Repeat Yourself". Il s'agit d'une technique de bonnes pratiques qui aide à garder votre script propre et propre et qui évite à php d'avoir à effectuer plusieurs fois la même tâche.
  • Parfois, vous verrez des instructions CASE-WHEN Au lieu des instructions IF à l'intérieur de MAX() elles sont logiquement interchangeables.
  • Le tri de l'ensemble de résultats peut être effectué à l'intérieur de la clause GROUP BY Dans ce scénario, mais peut également être effectué avec une clause ORDER BY.
  • [~ # ~] aucun [~ # ~] des appels de $db->qn() sont en fait nécessaire pour cette requête car aucun des noms de table ou de colonne n'a de caractères monkeywrenching ou n'est sur la liste des mots clés réservés MySQL . Comme vous pouvez le voir, toutes ces concaténations et appels de citations gonflent vraiment la syntaxe et rendent la lecture et la recherche de fautes de frappe beaucoup plus difficiles. Si tel était mon projet, je serais enclin à supprimer tous les appels générateurs de backtick et je supprimerais également les appels de devis sur des chaînes statiques comme C% ... mais tout cela est une question de préférence personnelle.
0
mickmackusa