Home > Blockchain >  How to sum a field of a child table by a parent table field when a second child table has records?
How to sum a field of a child table by a parent table field when a second child table has records?

Time:06-21

Given the following three example tables:

tournament_oc

id_
1
2

match_oc:

id_ tournament_oc_id
1 1
2 1

stat_oc

id_ tournament_oc_id value_
1 1 1
2 1 1
3 2 1

I want to sum stat_oc.value_ by tournament_oc.id_ if there are one or more match_oc records for a given tournament_oc.id_. The desired output in this case should be:

tournament_oc_id sum_value
1 2

I've tried:

SELECT
    t.id_ AS tournament_oc_id, sum(s.value_) AS sum_value
FROM
    tournament_oc AS t
        JOIN
    match_oc AS m ON m.tournament_oc_id = t.id_
        JOIN
    stat_oc as s on s.tournament_oc_id = t.id_
GROUP BY t.id_

However, this returns:

tournament_oc_id sum_value
1 4

Presumably because there are two match_oc records where tournament_oc.id_ == 1.

How would I achieve the desired result?

SQL to replicate tables:

CREATE DATABASE  IF NOT EXISTS `test` /*!40100 DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci */ /*!80016 DEFAULT ENCRYPTION='N' */;
USE `test`;

/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!50503 SET NAMES utf8 */;
/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
/*!40103 SET TIME_ZONE=' 00:00' */;
/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;

DROP TABLE IF EXISTS `match_oc`;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `match_oc` (
  `id_` int NOT NULL AUTO_INCREMENT,
  `tournament_oc_id` int NOT NULL,
  PRIMARY KEY (`id_`),
  KEY `fk__match_oc__tournament_oc_id_idx` (`tournament_oc_id`),
  CONSTRAINT `fk__match_oc__tournament_oc_id` FOREIGN KEY (`tournament_oc_id`) REFERENCES `tournament_oc` (`id_`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
/*!40101 SET character_set_client = @saved_cs_client */;

LOCK TABLES `match_oc` WRITE;
/*!40000 ALTER TABLE `match_oc` DISABLE KEYS */;
INSERT INTO `match_oc` VALUES (1,1),(2,1);
/*!40000 ALTER TABLE `match_oc` ENABLE KEYS */;
UNLOCK TABLES;

DROP TABLE IF EXISTS `stat_oc`;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `stat_oc` (
  `id_` int NOT NULL AUTO_INCREMENT,
  `tournament_oc_id` int NOT NULL,
  `value_` tinyint DEFAULT NULL,
  PRIMARY KEY (`id_`),
  KEY `fk__stat_oc__tournament_oc_id_idx` (`tournament_oc_id`),
  CONSTRAINT `fk__stat_oc__tournament_oc_id` FOREIGN KEY (`tournament_oc_id`) REFERENCES `tournament_oc` (`id_`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
/*!40101 SET character_set_client = @saved_cs_client */;

LOCK TABLES `stat_oc` WRITE;
/*!40000 ALTER TABLE `stat_oc` DISABLE KEYS */;
INSERT INTO `stat_oc` VALUES (1,1,1),(2,1,1),(3,2,1);
/*!40000 ALTER TABLE `stat_oc` ENABLE KEYS */;
UNLOCK TABLES;

DROP TABLE IF EXISTS `tournament_oc`;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `tournament_oc` (
  `id_` int NOT NULL AUTO_INCREMENT,
  PRIMARY KEY (`id_`)
) ENGINE=InnoDB AUTO_INCREMENT=26193 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
/*!40101 SET character_set_client = @saved_cs_client */;

LOCK TABLES `tournament_oc` WRITE;
/*!40000 ALTER TABLE `tournament_oc` DISABLE KEYS */;
INSERT INTO `tournament_oc` VALUES (1),(2);
/*!40000 ALTER TABLE `tournament_oc` ENABLE KEYS */;
UNLOCK TABLES;
/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;

/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;

CodePudding user response:

You can't aggregate on the same level of the JOIN operation as long as this one is carried out before the aggregation, hence if a tournament has been contested in more than one match, you'll get repeated sum for each contested match (in your case, 2 matches by sum of 2 = 4).

In order to circumvent this problem, you can first filter out tournaments without matches (inside a WHERE clause), then sum up your tournaments.

SELECT tournament_oc_id,
       SUM(value_)         AS sum_value
FROM stat_oc
WHERE tournament_oc_id IN (SELECT tournament_oc_id FROM match_oc) 
GROUP BY tournament_oc_id

There's no need to use the "tournament_oc" table as long as it is supposed that if a tournament doesn't exist, it should neither appear into the "match_oc" table.

Check the demo here.

  • Related