摆脱mysql查询中的空值

时间:2019-01-15 21:28:53

标签: mysql database null

我有一个存储钓鱼许可证的数据库。许可证有最新的日期,我使用日历表来计算每天售出的许可证的数量。每个许可证都有定义的许可证类型。许可证类型可以对一个或多个钓鱼区有效。

我正在尝试建立一个查询,该查询显示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

3 个答案:

答案 0 :(得分:2)

如果您想为每个区域都排一行,即使是当天未售出的区域,也可以将zones表从子查询中取出。您想从CROSS JOINzones表之间的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;来自参数的常规过滤器,适用于calendarzones,可以放在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 JOINlicensetypeslicenses的相关许可证类型和天数:

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

Demo on SQLFiddle