Question

Notre base de données d'analyse Web MySQL contient un tableau récapitulatif qui est mis à jour tout au long de la journée comme nouvelle activité est importée. Nous utilisons DUPLICATE KEY UPDATE pour que le calculs antérieurs summarization écrase, mais ont de la difficulté parce que l'une des colonnes de la clé de tableau récapitulatif UNIQUE est une option FK, et contient des valeurs NULL.

Ces NULLs sont destinés à signifier « pas présent, et tous ces cas sont équivalentes ». Bien sûr, MySQL traite habituellement NULLs comme signifiant « inconnu, et tous ces cas ne sont pas équivalents ».

La structure de base est la suivante:

Une table « Activité » contenant une entrée pour chaque session, appartenant chacun à une campagne, avec filtre en option et ID transaction pour certaines entrées.

CREATE TABLE `Activity` (
    `session_id` INTEGER AUTO_INCREMENT
    , `campaign_id` INTEGER NOT NULL
    , `filter_id` INTEGER DEFAULT NULL
    , `transaction_id` INTEGER DEFAULT NULL
    , PRIMARY KEY (`session_id`)
);

Une table « Résumé » contenant des cumuls quotidiens du nombre total de sessions dans le tableau d'activité, un d le nombre total de ces sessions qui contiennent un identifiant de transaction. Ces résumés sont divisés, avec un pour chaque combinaison de la campagne et le filtre (en option). Ceci est une table non transactionnelle en utilisant MyISAM.

CREATE TABLE `Summary` (
    `day` DATE NOT NULL
    , `campaign_id` INTEGER NOT NULL
    , `filter_id` INTEGER DEFAULT NULL
    , `sessions` INTEGER UNSIGNED DEFAULT NULL
    , `transactions` INTEGER UNSIGNED DEFAULT NULL
    , UNIQUE KEY (`day`, `campaign_id`, `filter_id`)
) ENGINE=MyISAM;

La requête réelle est quelque chose summarization comme suit, en comptant le nombre de sessions et des transactions, le regroupement puis par la campagne et le filtre (en option).

INSERT INTO `Summary` 
    (`day`, `campaign_id`, `filter_id`, `sessions`, `transactions`)
    SELECT `day`, `campaign_id`, `filter_id
        , COUNT(`session_id`) AS `sessions`
        , COUNT(`transaction_id` IS NOT NULL) AS `transactions`
    FROM Activity
    GROUP BY `day`, `campaign_id`, `filter_id`
ON DUPLICATE KEY UPDATE
    `sessions` = VALUES(`sessions`)
    , `transactions` = VALUES(`transactions`)
;

Tout fonctionne très bien, sauf pour le résumé des cas où le filter_id est NULL. Dans ces cas, la clause KEY UPDATE Duplicate ne correspond pas à la ligne existante, et une nouvelle ligne est écrite chaque fois. Cela est dû au fait que « NULL! = NULL ». Ce qu'il nous faut, cependant, est « NULL = NULL » lorsque l'on compare les clés uniques.

Je cherche des idées pour des solutions de contournement ou des commentaires sur ceux que nous sommes venus avec jusqu'à présent. Contournements nous avons pensé à ce jour suivre.

  1. Supprimer toutes les entrées de synthèse contenant une valeur clé NULL avant d'exécuter le summarization. (C'est ce que nous faisons maintenant) Cela a pour effet secondaire négatif du retour des résultats avec des données manquantes si une requête est exécutée au cours du processus de compression.

  2. Modifier la colonne NULL DEFAULT 0 à DEFAULT, qui permet la clé unique à apparier cohérente. Cela a pour effet secondaire négatif de compliquer excessivement le développement de requêtes sur le tableau récapitulatif. Il nous oblige à utiliser beaucoup de « CASE filter_id = 0 ELSE filter_id NULL ALORS FIN », et permet de rejoindre maladroit puisque tous les autres tables ont NULLs réelles pour l'filter_id.

  3. Créer une vue qui retourne « CASE filter_id = 0 ALORS NULL FIN SINON filter_id », et en utilisant ce point de vue au lieu de la table directement. Le tableau récapitulatif contient quelques centaines de milliers de lignes, et je l'ai été dit vue de la performance est assez pauvre.

  4. Autoriser les entrées en double à créer et supprimer les anciennes entrées après summarization complète. A des problèmes similaires à les supprimer à l'avance.

  5. Ajouter une colonne de remplacement qui contient 0 pour NULL, et utiliser cette substitution dans la clé unique (en fait, nous pourrions utiliser PRIMARY KEY si toutes les colonnes sont NOT NULL).
    Cette solution semble raisonnable, sauf que l'exemple ci-dessus est seulement un exemple; la base de données réelle contient une demi-douzaine de tableaux de synthèse, dont une contient quatre colonnes nullable dans la clé unique. Il craint par certains que les frais généraux est trop.

Avez-vous une meilleure solution, structure de la table, le processus de mise à jour ou MySQL meilleures pratiques qui peuvent aider?

EDIT: Pour clarifier le "sens de nulle"

Les données contenues dans les lignes de synthèse contenant des colonnes NULL sont considérées comme appartenant ensemble seulement dans le sens que d'être une seule rangée « fourre-tout » dans les rapports de synthèse, résumant les éléments pour lesquels ce point de données n'existe pas ou est inconnu . Ainsi, dans le contexte du tableau récapitulatif lui-même, le sens est « la somme de ces entrées pour lesquels il est sans valeur ». Dans les tables relationnelles, d'autre part, eese sont vraiment des résultats NULL.

La seule raison pour les mettre dans une clé unique sur le tableau récapitulatif est de permettre la mise à jour automatique (par Duplicate KEY UPDATE) lors de recalculant les rapports de synthèse.

Peut-être une meilleure façon de le décrire est l'exemple spécifique que l'un des résultats des groupes de tableaux de synthèse géographiquement par le préfixe de code postal de l'adresse commerciale donnée par l'intimé. Tous les répondants ne fournissent une adresse commerciale, de sorte que la relation entre la table des transactions et adresses est tout à fait correctement NULL. Dans le tableau récapitulatif pour ces données, une ligne est générée pour chaque préfixe de code postal, contenant le résumé des données dans cette zone. Une ligne supplémentaire est générée pour afficher le résumé des données pour lesquelles aucun préfixe de code postal est connu.

Modifier le reste des tables de données pour avoir un explicite « THERE_IS_NO_ZIP_CODE » 0 valeur, et en plaçant un dossier spécial dans le tableau ZipCodePrefix représentant cette valeur est incorrecte -. Cette relation est vraiment NULL

Était-ce utile?

La solution

Je pense que quelque chose le long des lignes de (2) est vraiment le meilleur pari - ou, au moins, il serait si vous aviez commencé à partir de zéro. Dans SQL, NULL signifie inconnue. Si vous voulez un autre sens, vous devriez vraiment utiliser une valeur spéciale pour cela, et 0 est certainement un choix OK.

Vous devez le faire à travers le ensemble base de données, non seulement celui-ci table. Ensuite, vous ne devriez pas liquider avec d'étranges cas particuliers. En fait, vous devriez être en mesure de se débarrasser d'un grand nombre de vos proches actuels (exemple: actuellement, si vous voulez que la ligne de résumé où il n'y a pas de filtre, vous avez le cas particulier « filtre est nul », par opposition au cas normal "filter =?".)

Vous devriez aussi aller de l'avant et de créer un « pas présent » entrée dans le dénommé à table ainsi, pour maintenir la contrainte FK valide (et éviter des cas particuliers).

PS:. Tableaux w / o une clé primaire ne sont pas des tables relationnelles et doivent vraiment être évités

modifier 1

Hmmm, dans ce cas, vous devez en fait la mise à jour sur clé en double? Si vous faites une INSERT ... SELECT, puis vous probablement. Mais si votre application fournit les données, il suffit de faire à la main -. Faire la mise à jour (zip = null de cartographie à zip is null), vérifier le nombre de lignes ont été modifiées (MySQL retourne cela), si 0 faire un insert

Autres conseils

  

Modifier la colonne NULL DEFAULT 0 à DEFAULT, qui permet la clé unique à apparier cohérente. Cela a pour effet secondaire négatif de compliquer excessivement le développement de requêtes sur le tableau récapitulatif. Il nous oblige à utiliser beaucoup de « CASE filter_id = 0 ALORS NULL FIN SINON filter_id », et fait pour se joindre à maladroite puisque tous les autres tables ont NULLs réelles pour l'filter_id.

     

Créer une vue qui retourne « CASE filter_id = 0 ALORS NULL FIN SINON filter_id », et en utilisant ce point de vue au lieu de la table directement. Le tableau récapitulatif contient quelques centaines de milliers de lignes, et je l'ai été dit vue de la performance est assez pauvre.

Voir les performances 5.x MySQL sera très bien, que la vue ne fait que remplacer un zéro avec une valeur nulle. Sauf si vous utilisez des agrégats / sortes dans une vue, la plupart une requête sur la vue sera ré-écrit par l'optimiseur de requête pour frapper juste la table sous-jacente.

Et bien sûr, car il est un FK, vous devrez créer une entrée dans la table à mentionné avec un id de zéro.

Avec les versions modernes de MariaDB (anciennement MySQL), upserts peut être fait simplement avec insert sur les états de mise à jour de clé en double si vous allez avec la route colonne de remplacement # 5. Ajout des colonnes stockées générées de MySQL ou MariaDB colonnes virtuelles persistantes pour appliquer la contrainte d'unicité sur les champs nullables conserve indirectement des données non-sens de la base de données en échange d'un ballonnement.

par exemple.

CREATE TABLE IF NOT EXISTS bar (
    id INT PRIMARY KEY AUTO_INCREMENT,
    datebin DATE NOT NULL,
    baz1_id INT DEFAULT NULL,
    vbaz1_id INT AS (COALESCE(baz1_id, -1)) STORED,
    baz2_id INT DEFAULT NULL,
    vbaz2_id INT AS (COALESCE(baz2_id, -1)) STORED,
    blam DOUBLE NOT NULL,
    UNIQUE(datebin, vbaz1_id, vbaz2_id)
);

INSERT INTO bar (datebin, baz1_id, baz2_id, blam)
    VALUES ('2016-06-01', null, null, 777)
ON DUPLICATE KEY UPDATE
    blam = VALUES(blam);

Pour MariaDB remplaçons PERSISTENT stockés avec, les indices ont besoin de persévérance.

MySQL Colonnes Generated MariaDB Colonnes virtuels

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top