我有一个包含交易的表(“转储”),我想列出每个月按类别分组的总金额,如:月|类别|类别ID |和。涉及的表格如下:
TABLE dump: id INT date DATE event VARCHAR(100) amount DECIMAL(10, 2)
TABLE dump_cat: id INT did INT (id in dump) cid INT (id in categories)
TABLE categories: id INT name VARCHAR(100)
现在我尝试使用的查询是:
SELECT SUBSTR(d.date,1,7) AS month, c.name, c.id AS catid, SUM(d.amount) AS sum FROM dump as d, dump_cat as dc, categories AS c WHERE dc.did = d.id AND c.id = dc.cid AND SUBSTR(d.date, 1, 7) >= '2008-08' GROUP BY month, c.name ORDER BY month;
但大多数类别的总和是应有的两倍。我的猜测是,这是因为连接返回多行,但在字段部分中添加“DISTINCT d.id”没有任何区别。查询返回的示例如下:
+---------+--------------------------+-------+-----------+ | month | name | catid | sum | +---------+--------------------------+-------+-----------+ | 2008-08 | Cash | 21 | -6200.00 | | 2008-08 | Gas | 8 | -2936.19 | | 2008-08 | Rent | 1 | -15682.00 |
其中
SELECT DISTINCT d.id, d.amount FROM dump AS d, dump_cat AS dc WHERE d.id = dc.did AND SUBSTR(d.date, 1, 7) ='2008-08' AND dc.cid = 21;
返回
+------+----------+ | id | amount | +------+----------+ | 3961 | -600.00 | | 2976 | -200.00 | | 2967 | -400.00 | | 2964 | -200.00 | | 2957 | -300.00 | | 2962 | -1400.00 | +------+----------+
这总计3100,是上面列出的一半。如果我从上一个查询中删除“DISTINCT d.id”,则每行列出两次。我认为这是问题,但我需要帮助来弄清楚如何解决它。提前谢谢。
补充:如果我使用
将dump和dump_cat表收集到一个表中CREATE table dumpwithcat SELECT DISTINCT d.id, d.date, d.event, d.amount, dc.cid FROM dump AS d, dump_cat AS c WHERE c.did = d.id;
并在该表上执行查询,一切正常,总和正常。有没有办法在原始查询中执行此操作,使用子查询或类似的东西?
答案 0 :(得分:2)
这总计3100,是上面列出的一半。如果我从上一个查询中删除“DISTINCT d.id”,则每行列出两次。
虽然每个转储可能只有一个类别,但每个转储中dump_cat
必须有多个行。您应该考虑定义UNIQUE
约束,以确保每对did
,cid
只存在一行:
ALTER TABLE dump_cat ADD CONSTRAINT UNIQUE (did, cid);
我预测,如果您的表中有当前数据,此语句将失败。当这些列已包含重复项时,它无法创建唯一约束!
您可以通过这种方式删除重复项,例如:
DELETE dc1 FROM dump_cat dc1 JOIN dump_cat dc2 USING (did, cid)
WHERE dc1.id > dc2.id; -- only delete the second duplicate entry
编辑顺便说一句,在您确认我是正确的之前,请不要接受我的问题! : - )
您可以使用如下所示的查询验证实际上是否存在重复:
SELECT did, COUNT(*)
FROM dump_cat
GROUP BY did
HAVING COUNT(*) > 1;
另一种可能性:您有多个具有相同名称的类别? (对不起,我第一次尝试这个查询是错误的,这是一个编辑版本)
SELECT c.name, GROUP_CONCAT(c.id) AS cat_id_list, COUNT(*) AS c
FROM category c
GROUP BY c.name
HAVING COUNT(*) > 1;
FWIW,我测试了我展示的DELETE
命令:
INSERT INTO dump_cat (did, cid) VALUES (1, 2), (3,4), (3,4); -- duplicates!
DELETE dc1 FROM dump_cat dc1 JOIN dump_cat dc2 USING (did, cid) WHERE dc1.id > dc2.id
Query OK, 1 row affected (0.00 sec)
PS:这与您的问题相关,但DISTINCT
查询修饰符始终适用于整行,而不仅仅是第一列。这是许多SQL程序员的常见误解。
答案 1 :(得分:1)
在第一次检查时,我认为你可能会向后转发Dump和Dump_Cat之间的参照完整性约束。
交易(转储)可以分为多个类别吗?如果没有,那么交易表,(转储)不应该指定每个交易所在的类别,而不是otjher方式?即,转储表中是否有CatId而Cat表中是否有DumpId?
如果交易可以处于多个类别,那么您的数据结构是正确的,但是在任何聚合查询中您都不可避免地会对事务金额进行双倍(或多次)计数,因为交易金额实际上是在多个类别。
答案 2 :(得分:1)
如果转储记录可以分为多个类别,则会影响该月类别行的所有。
对此的一个解决方案是为每个转储记录提取COUNT()类别,并将其用作单个金额的除数。因此,金额在转储记录所属的所有类别中以均匀的方式自动分配,从而保持整体总体的完整性。
这样的事情(对不起,MySQL不是我的日常RDBMS,不确定确切的语法):
SELECT SUBSTR(d.date,1,7) AS month, c.name, c.id AS catid,
SUM(d.amount / (SELECT COUNT(*) FROM dump_cat dc2 WHERE dc2.did=d.id)) AS sum
FROM dump as d, dump_cat as dc, categories AS c
WHERE dc.did = d.id AND c.id = dc.cid AND SUBSTR(d.date, 1, 7) >= '2008-08'
GROUP BY month, c.name ORDER BY month;
答案 3 :(得分:1)
您可以接受任何查询,例如您用于创建不同表的查询,然后只选择该查询。只需给查询一个“表名”。
SELECT SUBSTR(d_dc.date,1,7) AS month, c.name, c.id AS catid, SUM(d_dc.amount) AS sum
FROM (SELECT DISTINCT d.id, d.date, d.event, d.amount, dc.cid
FROM dump AS d, dump_cat AS dc WHERE dc.did = d.id
WHERE SUBSTR(d.date, 1, 7) >= '2008-08') AS d_dc
JOIN categories AS c ON d_dc.cid=c.id
GROUP BY month, c.name ORDER BY month
这可能不是最有效的查询方式,而且我可能已经把一些表别名弄错了,但这应该让你知道如何去做。