2016-06-30 1 views
3

Je souhaite croiser certaines données d'enquête pondérées dans un contexte où un individu peut contribuer à plus d'une cellule. Le défi consiste à s'assurer que les sous-totaux et les totaux généraux sont effectués sans double comptage.Pondérations SQL non distinctes pour des lignes distinctes avec des totaux par catégorie

Je peux obtenir les valeurs de cellules individuelles mais pas les totaux en utilisant des méthodes similaires aux solutions à How do I SUM DISTINCT Rows? ou Sum Distinct By Other Column. J'essaie d'utiliser l'instruction Oracle CUBE pour obtenir les totaux de manière agréable.

Voici un exemple de bébé. Supposons que nous comptons les gens selon leurs animaux de compagnie et selon leurs passe-temps. Le problème est qu'une personne peut avoir plus d'un animal de compagnie, ou plus d'un passe-temps. Nous devons transformer cet ensemble d'enregistrements unitaires:

person_id, weight 
1, 10 
2, 10 
3, 12 

person_id, pet 
1, "cat" 
1, "dog" 
2, "cat" 
3, "cat" 

person_id, hobby 
1, "chess" 
2, "chess" 
2, "skydiving" 
3, "skydiving" 

dans cette paire de tables:

Unweighted count 

     | chess | skydiving | total 
------+-------+-----------+-------- 
cat | 2 | 2  | 3 
------+-------+-----------+-------- 
dog | 1 | 0  | 1 
------+-------+-----------+-------- 
total | 2 | 2  | 3  


Weighted count 

     | chess | skydiving | total 
------+-------+-----------+-------- 
cat | 20 | 22  | 32 
------+-------+-----------+-------- 
dog | 10 | 0  | 10 
------+-------+-----------+-------- 
total | 20 | 22  | 32  

Notez que le total non pondéré de la ligne "chat" est 3, et non 2 + 2 = 4 , puisque la personne numéro 2 est comptée à deux endroits différents. Seulement trois personnes distinctes contribuent à cette rangée. De même pour les autres totaux.

Notez que le total pondéré pour "chat, échecs" est 20 = 10 + 10, car deux personnes différentes contribuent chacune poids 10 à cette cellule.

Notez que le total général pour le tableau pondéré est de 32. Cela vient des personnes 1 et 2 qui contribuent 10 chacune, et la personne 3 qui en donne 12. Le total général n'est pas seulement la somme de toutes les cellules individuelles!

Pour les comptes non pondérés, je peux obtenir tous les chefs de cellules et totaux par:

CREATE TABLE weights(person_id INTEGER, weight INTEGER); 
INSERT INTO weights(person_id,weight) VALUES (1,10); 
INSERT INTO weights(person_id,weight) VALUES (2,10); 
INSERT INTO weights(person_id,weight) VALUES (3,12); 

CREATE TABLE pets(person_id INTEGER, pet VARCHAR(3)); 
INSERT INTO pets(person_id,pet) VALUES (1,'cat'); 
INSERT INTO pets(person_id,pet) VALUES (1,'dog'); 
INSERT INTO pets(person_id,pet) VALUES (2,'cat'); 
INSERT INTO pets(person_id,pet) VALUES (3,'cat'); 

CREATE TABLE hobbies(person_id INTEGER, hobby VARCHAR(9)); 
INSERT INTO hobbies(person_id,hobby) VALUES (1,'chess'); 
INSERT INTO hobbies(person_id,hobby) VALUES (2,'chess'); 
INSERT INTO hobbies(person_id,hobby) VALUES (2,'skydiving'); 
INSERT INTO hobbies(person_id,hobby) VALUES (3,'skydiving'); 

SELECT pet, hobby, COUNT(DISTINCT weights.person_id) 
FROM weights JOIN pets on weights.person_id=pets.person_ID 
JOIN hobbies on weights.person_id=hobbies.person_id 
GROUP BY CUBE(pet, hobby); 

La combinaison de COUNT(DISTINCT ...) et CUBE donne les totaux corrects.

Pour comptes pondérés, si je tente la même idée:

SELECT pet, hobby, SUM(DISTINCT weight) 
FROM weights JOIN pets on weights.person_id=pets.person_ID 
JOIN hobbies on weights.person_id=hobbies.person_id 
GROUP BY CUBE(pet, hobby); 

la cellule « chat, jeu d'échecs » vient à 10 pas 20, parce que les gens 1 et 2 ont tous deux le même poids. Supprimer le mot clé «distinct» signifie que les comptes de cellules individuels sont corrects mais que les totaux sont faux (il produit un grand total de 52 où il devrait être de 32, car les personnes 1 et 2 sont comptées deux fois dans le total).

Des suggestions?

+1

si vous supprimez la somme distincte à l'intérieur? –

+0

Vous voulez dire SUM (poids)? Alors la cellule "chat, échecs" est correcte, mais le total général est de 52 où il devrait être de 32, car les personnes 1 et 2 sont comptées deux fois chacune dans le total général. –

+0

mais somme (poids distinct) donne le total 22 pas 32. –

Répondre

1

Vous pouvez le faire en utilisant une requête imbriquée, où la requête interne spécifie un mappage des lignes aux cellules de table (c.-à-d.quels enregistrements sont dans la portée de chaque cellule de table), et la requête externe spécifie la fonction de synthèse (s) à appliquer:

SELECT pet, hobby, COUNT(1), SUM(weight) FROM 
(SELECT pet, hobby, weights.person_ID, weight 
FROM weights JOIN pets on weights.person_id=pets.person_ID 
JOIN hobbies on weights.person_id=hobbies.person_id 
GROUP BY CUBE(pet, hobby), weights.person_ID, weight) 
GROUP BY pet, hobby; 

Results

En plus: Vous pouvez également écrire la requête interne sans utiliser la opérateur CUBE, mais il est beaucoup messier:

WITH 
    pet_cube_map as (SELECT DISTINCT pet, NULL as pet_cubed FROM pets UNION ALL SELECT DISTINCT pet, pet as pet_cubed FROM pets), 
    hobby_cube_map as (SELECT DISTINCT hobby, NULL as hobby_cubed FROM hobbies UNION ALL SELECT DISTINCT hobby, hobby as hobby_cubed FROM hobbies) 
SELECT DISTINCT pet_cubed as pet, hobby_cubed as hobby, weights.person_ID, weight 
FROM weights 
    JOIN pets on weights.person_ID=pets.person_ID 
    JOIN pet_cube_map on pets.pet=pet_cube_map.pet 
    JOIN hobbies on weights.person_ID=hobbies.person_ID 
    JOIN hobby_cube_map on hobbies.hobby=hobby_cube_map.hobby 
; 
1

essayer cela, ci-dessous donne le résultat correct mais il est le plus simplifié un

SELECT pet, hobby, SUM(weight) 
FROM weights JOIN pets on weights.person_id=pets.person_ID 
JOIN hobbies on weights.person_id=hobbies.person_id 
GROUP BY pet, hobby 
UNION 
SELECT pet, NULL, SUM(weight) 
FROM weights JOIN pets on weights.person_id=pets.person_ID 
GROUP BY pet 
UNION 
SELECT NULL, hobby, SUM(weight) 
FROM weights JOIN hobbies on weights.person_id=hobbies.person_id 
GROUP BY hobby 
UNION 
SELECT SUM(weight) 
FROM weights 

travaille toujours sur simple sélection

+0

Merci, cela donne en effet les totaux corrects. Le problème est que ce n'est pas facilement évolutif. Si je classifie selon trois dimensions (animal de compagnie, passe-temps et un autre), alors je dois générer manuellement les huit ensembles de comptes de cellules, les sous-totaux et les totaux. Pour quatre dimensions, j'aurai 16 SELECTs. Etc. L '"exemple de bébé" que j'ai donné est juste pour preuve de concept: je veux l'appliquer dans des scénarios plus compliqués. Si cela peut être fait avec un seul SELECT plus un CUBE, alors la vie est beaucoup plus facile! –

0

Je pense que vous devez faire un peu de mathématiques comme ceci:

;WITH t AS (
    SELECT 
     p.pet, 
     SUM(DISTINCT CASE WHEN h.hobby = 'chess' THEN POWER(2,h.person_id) ELSE 0 END) chess, 
     SUM(DISTINCT CASE WHEN h.hobby = 'skydiving' THEN POWER(2,h.person_id) ELSE 0 END) skydiving, 
     SUM(DISTINCT POWER(2,h.person_id)) total 
    FROM 
     hobbies h 
     LEFT JOIN 
     pets p ON h.person_id = p.person_id 
    GROUP BY 
     p.pet 
    UNION ALL 
    SELECT 
     'total', 
     SUM(DISTINCT CASE WHEN h.hobby = 'chess' THEN POWER(2,h.person_id) ELSE 0 END), 
     SUM(DISTINCT CASE WHEN h.hobby = 'skydiving' THEN POWER(2,h.person_id) ELSE 0 END), 
     SUM(DISTINCT POWER(2,h.person_id)) 
    FROM 
     hobbies h 
), w(person_id, weight) as (
    SELECT POWER(2,person_id), weight 
    FROM weights 
), cte(person_id, weight) AS (
    SELECT * 
    FROM w 
    UNION ALL 
    SELECT w1.person_id + w2.person_id, w1.weight + w2.weight 
    FROM cte w1 JOIN w w2 ON w2.person_id > w1.person_id 
) 
SELECT 
    pet, 
    COALESCE((SELECT cte.weight FROM cte WHERE cte.person_id = t.chess), 0) AS chess, 
    COALESCE((SELECT cte.weight FROM cte WHERE cte.person_id = t.skydiving), 0) AS skydiving, 
    COALESCE((SELECT cte.weight FROM cte WHERE cte.person_id = t.total), 0) AS total 
FROM t; 

Non cubique, statique et un peu sale. Mais je viens de le tester dans SQL Server;).


Cela peut être une version cubed (non testé):

;With t as (
SELECT h.hobby, p.pet, POWER(2,h.person_id) weight 
FROM hobbies h 
JOIN pets p 
ON  h.person_id = p.person_id 
JOIN weights w 
ON  h.person_id = w.person_id 
), w(person_id, weight) as (
    SELECT POWER(2,person_id), weight 
    FROM weights 
), cte(person_id, weight) AS (
    SELECT * 
    FROM w 
    UNION ALL 
    SELECT w1.person_id + w2.person_id, w1.weight + w2.weight 
    FROM cte w1 JOIN w w2 ON w2.person_id > w1.person_id 
), c as (
SELECT 
    hobby, pet, SUM(DISTINCT weight) person_id 
FROM t 
GROUP BY CUBE(hobby, pet) 
) 
SELECT c.hobby, c.pet, cte.weight 
FROM c JOIN 
    cte ON c.person_id = cte.person_id;