我有一个存储钓鱼许可证的数据库。许可证有最新的日期,我使用日历表来计算每天售出的许可证的数量。每个许可证都有定义的许可证类型。许可证类型可以对一个或多个钓鱼区有效。
我正在尝试建立一个查询,该查询显示zones表中的某些列,以及按天和捕鱼区分组并按许可证类型过滤了多少许可证。我已经在出售许可证的日子里使它工作了。但是,尚未出售许可证的日子仅显示空值。
我已经将这个问题扭曲了8个小时。我敢肯定有一个简单的解决方案,只是看不到。 SQL Fiddle here。
我认为该模式是不言自明的,因此在此不再赘述。如果需要,请查看小提琴。
SET @licensetype = 1,
@fromdate = '2019-01-01',
@todate = '2019-01-10';
SELECT zoneID,
dy,
seasonmax,
daymax,
COUNT(lID) AS sold
FROM
( SELECT z.zoneID,
z.seasonmax,
z.daymax,
l.ID AS lID,
l.From,
l.To,
lt.ValidForZone
FROM zones z
LEFT JOIN licensetypes lt ON z.zoneID IN(lt.ValidForZone)
LEFT JOIN licenses l ON lt.ID = l.TypeID
WHERE FIND_IN_SET( z.zoneID,
( SELECT lt2.ValidForZone
FROM licensetypes lt2
WHERE lt2.ID = @licensetype ) ) ) derived
RIGHT JOIN calendar ON calendar.dy >= DATE_FORMAT(derived.From, '%Y-%m-%d')
AND calendar.dy < DATE_FORMAT(derived.To, '%Y-%m-%d')
WHERE calendar.dy >= @fromdate
AND calendar.dy <= @todate
GROUP BY dy,
zoneID
ORDER BY dy
答案 0 :(得分:2)
如果您想为每个区域都排一行,即使是当天未售出的区域,也可以将zones
表从子查询中取出。您想从CROSS JOIN
和zones
表之间的calendar
开始,以获取区域和日期的所有组合。然后LEFT JOIN
与许可证信息一起获得许可证计数。
此外,要使用逗号分隔的列表,您需要使用FIND_IN_SET
,而不是IN()
。参见Search with comma-separated value mysql。
SELECT z.zoneID, c.dy, z.seasonmax, z.daymax, IFNULL(COUNT(l.ID), 0) AS sold
FROM zones AS z
CROSS JOIN calendar AS c
JOIN licensetypes AS lt ON FIND_IN_SET(z.zoneID, lt.ValidForZone)
LEFT JOIN licenses AS l ON lt.ID = l.typeID AND c.dy >= DATE_FORMAT(l.From, '%Y-%m-%d') AND c.dy < DATE_FORMAT(l.To, '%Y-%m-%d')
WHERE c.dy BETWEEN @fromdate AND @todate
AND lt.ID = @licensetype
GROUP BY z.zoneID, c.dy
ORDER BY dy
答案 1 :(得分:1)
感谢数据库小提琴。我已按照以下准则对您的查询进行了很大的修改:
GROUP BY
查询来实现计算FROM calendar CROSS JOIN zones
;来自参数的常规过滤器,适用于calendar
和zones
,可以放在WHERE
子句中LEFT JOIN
表上的每个过滤条件都应移到相关联接的ON
子句中DATE
函数比DATE_FORMAT
截取日期时间更有效新查询(请参见the db fiddle):
SELECT z.zoneID, calendar.dy, z.seasonmax, z.daymax, COUNT(DISTINCT l.ID) Asold
FROM
calendar
CROSS JOIN zones z
LEFT JOIN licensetypes lt ON z.zoneID IN(lt.ValidForZone)
LEFT JOIN licenses l
ON lt.ID = l.TypeID
AND calendar.dy BETWEEN DATE(l.From) AND DATE(l.To)
WHERE
FIND_IN_SET( z.zoneID, ( SELECT lt2.ValidForZone FROM licensetypes lt2 WHERE lt2.ID = @licensetype ) )
AND calendar.dy >= @fromdate
AND calendar.dy <= @todate
GROUP BY z.zoneID, calendar.dy, z.seasonmax, z.daymax
ORDER BY dy
NB:此查询未正确考虑将多个逗号分隔值存储在licensetypes.ValidForZone
中的情况:当多个许可证类型匹配时,仅对第一个许可证类型进行计数(就像原始查询一样)。最好规范化数据库并使用桥表存储许可证类型和区域之间的1-N关系,而不是在单个字段中填充N个值...
答案 2 :(得分:1)
我认为此查询应该为您提供所需的结果(对于3/1来说似乎是1,对于4/1和5/1来说似乎是2)。您必须从CROSS JOIN
天和区域开始,然后从LEFT JOIN
到licensetypes
和licenses
的相关许可证类型和天数:
SELECT z.zoneID, DATE(c.dy) AS dy, z.seasonmax, z.daymax, COUNT(l.ID) AS sold
FROM calendar c
CROSS JOIN zones z
LEFT JOIN licensetypes t ON t.ID = @licensetype AND FIND_IN_SET(z.zoneID, t.ValidForZone)
LEFT JOIN licenses l ON l.TypeID = t.ID AND DATE(c.dy) >= DATE(l.From) AND DATE(c.dy) < DATE(l.To)
WHERE DATE(c.dy) BETWEEN @fromdate AND @todate
GROUP BY dy, z.zoneID