Избавьтесь от нулевых значений в MySQL запросе - PullRequest
0 голосов
/ 16 января 2019

У меня есть база данных, где я храню рыболовные лицензии. Лицензии имеют даты и даты, и я использую таблицу календаря для подсчета количества проданных лицензий за каждый день. Каждая лицензия имеет определенный тип лицензии. Тип лицензии может быть действителен для одной или нескольких рыболовных зон.

Я пытаюсь создать запрос, который показывает некоторые столбцы из таблицы зон и количество проданных лицензий, сгруппированных по дням и рыбацким зонам и отфильтрованных по типу лицензии. Я сделал это для тех дней, когда лицензии были проданы. Однако дни, когда лицензии еще не были проданы, показывают только нулевые значения.

Я крутил голову вокруг этого уже 8 часов. Я уверен, что есть простое решение, я просто не вижу его. SQL Fiddle здесь .

Я думаю, что схема довольно понятна, поэтому я не буду включать ее здесь. Пожалуйста, смотрите скрипку, если это необходимо.

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 голосов
/ 16 января 2019

Спасибо за диджейскую скрипку. Я в значительной степени изменил ваш запрос в соответствии со следующими рекомендациями:

  • Подзапрос не кажется необходимым: вычисление может быть выполнено за один GROUP BY запрос
  • Вам нужен декартово произведение, чтобы сгенерировать список всех дат для всех зон, следовательно, FROM calendar CROSS JOIN zones; общие фильтры, исходя из параметров, которые применяются к calendar и zones и могут быть помещены в предложение WHERE
  • Каждое условие фильтрации в таблицах LEFT JOIN ed должно быть перемещено в условие ON соответствующего объединения
  • более эффективно использовать функцию DATE, чем DATE_FORMAT, чтобы обрезать дату и время до даты

Новый запрос (см. скрипта БД ):

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

Примечание: этот запрос неправильно учитывает случай, когда в licensetypes.ValidForZone хранятся несколько значений, разделенных запятыми: при совпадении нескольких типов лицензий учитывается только первый (точно так же, как ваш исходный запрос). Было бы лучше нормализовать вашу базу данных и использовать таблицу мостов для хранения отношения 1-N между типами лицензий и зонами, вместо того, чтобы вставлять значения N в одно поле ...

0 голосов
/ 16 января 2019

Я думаю, что этот запрос должен дать вам желаемые результаты (которые, как представляется, 1 для 3/1, 2 для 4/1 и 5/1). Вы должны начать с 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

Демонстрация по SQLFiddle

0 голосов
/ 16 января 2019

Если вам нужна строка для каждой зоны, даже для тех, которые не были проданы в тот день, вы можете взять таблицу zones из подзапроса. Вы хотите начать с CROSS JOIN между таблицами zones и calendar, чтобы получить все комбинации зон и дат. Затем LEFT JOIN, что с информацией о лицензии для получения лицензии считается.

Также, чтобы присоединиться к списку через запятую, вам нужно использовать FIND_IN_SET, а не IN(). См. Поиск с использованием значения через запятую 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
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...