Делать пропуски диаграммы в ZingChart при отсутствии дат в динамически загружаемых данных?

Я использовал ColdFusion 2016 и ZingCharts (в комплекте) для динамического создания диаграмм с помощью SQL Server с временными рядами по оси X. Когда есть временные промежутки, я бы хотел, чтобы на линейной диаграмме также отображался промежуток, но вместо этого линия является непрерывной и отображает каждую точку данных последовательно.

Изображение диаграммы в том виде, в каком она построена сейчас, видно, что между датами 29 октября и марта нет «разрыва», данные просто объединяются:  NoDataGap

Мои данные обычно делаются с шагом 15 минут, но есть промежутки времени (дни или месяцы), где есть пробелы в временных рядах и данных. Я связался с ZingCharts, чтобы спросить, есть ли какой-то тег стиля, который контролирует, отображаются ли даты последовательно или с пробелами, а его нет. Это то, чем нужно манипулировать на уровне данных. Если бы мои данные были жестко запрограммированы, мне пришлось бы добавить нулевые значения, чтобы диаграммы отображались с пробелами в временных рядах, но мои диаграммы являются динамическими (пользователь может выбрать любое количество из 7 параметров для добавления в диаграмму для выбранного диапазона дат. ). Я нашел информацию о том, как решить эту проблему для жестко закодированных данных, но я ищу идеи для решений для динамически загружаемых данных / серий. Я также нашел информацию об устаревшем теге coldfusion для XML-файла, isInterpolated="false", но это больше не вариант.

Мой вопрос в том, как лучше всего решить эту проблему? Я нашел некоторую информацию о создании календарной таблицы в SQL Server и объединении ее с таблицей (таблицами), предоставляющими данные, чтобы все даты были заполнены. Мне было интересно, есть ли другой подход, о котором я не думаю? Спасибо за любую помощь, я новичок во всем этом.


Обновление: вот текущий запрос данных, который немного сложен. Он извлекает "N-е" строки в зависимости от того, сколько параметров (доступно 7) выбрано и сколько дней находится в диапазоне дат:

SELECT
distinct 
 datepart(year, t.sample_date) as [year]
,datepart(month, t.sample_date) as [month]
,datepart(day, t.sample_date) as [day]
,datepart(hour, t.sample_time) as [hr]
,datepart(minute, t.sample_time) as [min]  
,convert(varchar(10), t.sample_date, 1) + ' ' + 
  RIGHT('0' + CONVERT([varchar](2), DATEPART(HOUR, t.sample_time)), 2) + ':' +
  RIGHT('0' + CONVERT([varchar](2), DATEPART(MINUTE, t.sample_time)), 2) AS [datetime] 
,t.stationdesc
<cfif isDefined("form.parameter") and ListFindNoCase(form.parameter, "salinity")>,ROUND(t.salinity,1) as salinity</cfif>
<!---plus 6 more parameters--->
FROM (
SELECT    
    [sample_date]
    ,sample_time
    ,stationdesc
    <cfif isDefined("form.parameter") and ListFindNoCase(form.parameter, "salinity") >,salinity</cfif>
    <!---plus 6 more parameters--->
    , row_number() OVER (ORDER BY streamcode) AS rownum
    FROM MyUnionizedTables
    WHERE stationdesc = (<cfqueryparam value="#form.station#" cfsqltype="cf_sql_varchar">)
    AND [sample_date] BETWEEN (<cfqueryparam value='#Form.StartDate#' cfsqltype="cf_sql_date">) 
    AND (<cfqueryparam value='#Form.EndDate#' cfsqltype="cf_sql_date">)
    <cfif isDefined("form.parameter") and ListFindNoCase(form.parameter, "salinity")>and salinity > -25 and salinity <40 and salinity is not NULL  </cfif>
    <!---plus 6 more parameters--->                           
    GROUP BY sample_date, sample_time, stationdesc, streamcode 
    <cfif isDefined("form.parameter") and ListFindNoCase(form.parameter, "salinity")>,salinity</cfif>
    <!---plus 6 more parameters--->
    ) AS t
WHERE    <!---returning Nth row when record sets (count of days between dates selected) are long--->
    <cfif IsDefined("form.station") AND IsDefined("form.parameter") AND #ParamCount# LTE 3 AND form.station eq 'Coastal Bays - Public Landing' and #ctdays# gte 10> t.rownum % 64 = 0 
    <cfelseif IsDefined("form.parameter") AND #ParamCount# LTE 3 AND #ctDays# gte '5840'> t.rownum % 64 = 0 
        <!---plus lots more elseifs--->
    <cfelseif  IsDefined("form.parameter") AND #ParamCount# GTE 7  AND  #ctDays# gte '350'> t.rownum % 8 = 0
    <cfelse>t.rownum % 1 = 0</cfif>
ORDER BY 
     datepart(year, t.sample_date) 
    ,datepart(month, t.sample_date) 
    ,datepart(day, t.sample_date) 
    ,datepart(hour, t.sample_time) 
    ,datepart(minute, t.sample_time) 

ВТОРОЕ ОБНОВЛЕНИЕ (после ссылки Ли на запрос на GitHub):

Так что я на самом деле работал над запросом, аналогичным тому, который опубликовал Ли, на основе раздела «Выражение CTE» здесь. Я переключился на попытки работать с ее версией, которая находится ниже. У меня нет правок записи, поэтому я работаю с существующей таблицей. MyDataTable имеет ~ 21 мил строк, с отдельными sample_date (datetime) и sample_time (datetime) [даты и время являются PITA - b / c инструментов и способом удаленной телеметрии этих данных, мы получаем столбец datetime с 'хорошо date », но фиктивное значение времени, которое мы называем« sample_date », а затем отдельный столбец datetime с именем« sample_time »с фиктивной датой и« хорошим временем ».] Есть 125 станций, каждая с данными (например, температура) из разные даты / время начала и окончания, с 2001 г. по настоящее время. Поэтому мне нужно заполнить промежутки даты / времени для 125 различных станций с разными промежутками времени, которые обычно делаются с шагом 15 минут.

--- simulate main table(s)
--CREATE TABLE MyDataTable ( sample_date datetime, sample_time datetime, stationdesc nvarchar, wtemp float)

--- generate all dates within this range
DECLARE @startDate datetime
DECLARE @maxDate datetime
SET @startDate = '2015-01-01'
SET @maxDate = '2016-12-31'

--- get MISSING dates
;WITH missingDates AS
(  
    SELECT DATEADD(day,1,@startDate) AS TheDate
    UNION ALL  
    SELECT  DATEADD(day,1, TheDate) 
    FROM    missingDates  
    WHERE   TheDate < @maxDate  
)
SELECT *
      --[wtemp]
   --  ,[stationdesc]
   --  ,[TIMEVALUE]
FROM   missingDates mi LEFT JOIN MyDataTable t ON t.sample_date = mi.TheDate
WHERE  t.sample_date IS NULL
--and stationdesc = 'Back River - Lynch Point'
--ORDER BY timevalue
OPTION  (MAXRECURSION 0)

Когда я запускаю этот запрос как есть, я получаю только 17 строк данных. В столбце «Дата» указаны даты и время с датами 15–12/16/12 и всегда 00: 00: 00.000. Запрос занимает 49 секунд.
 введите описание изображения здесь


Тем временем мы с коллегой работаем над альтернативными методами.

--Putting data from only 1 station from our big datatable into the new testtable called '_testdatatable'

SELECT        station, sample_date, sample_time, wtemp, streamcode, stationdesc, TIMEVALUE
INTO              _testdatatable
FROM            MyBigDataTable
WHERE        (stationdesc = 'Back River')
order by [sample_date],[sample_time]

--Next, make a new table [_testdatatableGap] with all time values in 15min increments from a datetime table we made
SELECT [wtemp]=null
      ,[streamcode]='ABC1234'
      ,[stationdesc]= 'Back River'
      ,[TIMEVALUE]
      into [tide].[dbo].[_testdatatableGap]
  FROM DateTimeTable
  WHERE  (TIMEVALUE BETWEEN '4/19/2014' AND getdate())

--Then, get the missing dates from the Gap table and put into the testdatatable
INSERT into [_testdatatable]
      (  [wtemp]
        ,[streamcode]
        ,[stationdesc]
        ,[TIMEVALUE] 
)
    (SELECT 
       [wtemp]=null -- needs this for except to work
      ,
      [streamcode]
      ,[stationdesc]
      ,
      [TIMEVALUE] 
  FROM [_testdatatableGap]   
EXCEPT   
SELECT 
       [wtemp]=null -- needs this for except to work
      ,
    [streamcode]
      ,[stationdesc]
      ,
      [TIMEVALUE] 
  FROM [_testdatatable])

Этот метод работал для создания таблицы со всеми 15-минутными приращениями даты / времени, что привело к правильно нарисованной диаграмме (ниже). Однако мы не знаем, как масштабировать это до полной таблицы данных на 125 станций без создания нескольких таблиц.

CorrectDataGaps


person Becalecca    schedule 07.12.2016    source источник
comment
Это действительно требует генерации null значений для всех недостающих точек? : / Сколько данных вы обычно наносите на график? Как выглядит базовый запрос?   -  person Leigh    schedule 07.12.2016
comment
хех, ну это 96 точек данных в день * 365 дней * максимум 16 лет, и они могут загружать от 1 до 7 параметров - поэтому я настроил запрос, который извлекает 'N-е' строки с 'N' в зависимости от того, как количество выбранных параметров и количество дней в выборе даты. Я добавлю запрос к исходному вопросу, потому что он длинный.   -  person Becalecca    schedule 07.12.2016
comment
(Править) Хм ... интересно, можно ли обойтись одним нулем за (день?) Или вам действительно нужно сгенерировать по одному на каждый недостающий элемент. Я уверен, что недостающие элементы могут быть сгенерированы в SQL, это просто вопрос, как и насколько эффективно (например, календарная таблица или cte).   -  person Leigh    schedule 07.12.2016
comment
Мой коллега только что открыл для себя cte и довольно быстро сделал таблицу ... Могу ли я просто объединить это с моими данными? Но да, об этом очень интересно подумать, я мог бы попробовать только с 1 нулем в день.   -  person Becalecca    schedule 07.12.2016
comment
И да, буквальный ответ был: если вы хотите разорвать строку, вам нужно, но там есть ноль. [1,2,3, null, 5,6,7]. Я в шоке, что нет лучшего решения.   -  person Becalecca    schedule 07.12.2016
comment
Обычно я использую такой CTE во внешнем JOIN с другой таблицей. Таким образом, результаты всегда включают необходимые даты. Итак, концептуально результат будет примерно таким SELECT cte.SomeDate, ot.SomeValue FROM cte LEFT JOIN otherTable ot ON ot.SomeDate = cte.SomeDate, т.е. дата всегда заполняется, а значения заканчиваются как null для отсутствующих дат.   -  person Leigh    schedule 07.12.2016
comment
Спасибо, это дает мне возможность поработать, я отчитаюсь.   -  person Becalecca    schedule 07.12.2016
comment
(Edit) Хорошо, если вы хотите попробовать один ноль в день, UNION с этим типом CTE, вероятно, будет работать лучше. См. Пример gist.github.com/anonymous/14346ecb3f502cb3d6653a230b73ee0c. Просто СОЕДИНИТЕ результат с существующими датами.   -  person Leigh    schedule 07.12.2016
comment
Таблицы календаря очень полезны, но не решат полностью вашу проблему. Вам нужно построить значения на уровне даты и времени. Если вы видите другие варианты использования календарной таблицы (например, отчет за финансовый год), создайте и поддерживайте ее. Держите его отдельно от времени суток. Решите это с помощью другой таблицы, табличной функции или подзапроса.   -  person Dan Bracuk    schedule 08.12.2016
comment
Я все еще работаю над этим, но мне не очень повезло, потому что мне также нужно иметь там какой-то «идентификатор станции» / «идентификатор станции». Я успешно соединил 3 таблицы данных с 4-й таблицей даты и времени. Но поскольку таблица datetime (с шагом 15 минут) имеет нулевые значения для всех других параметров, когда я извлекаю данные на основе станции и диапазона дат, она не заполняет отсутствующие даты и время. Я не знаю, что делать, если не считать создания таблицы даты и времени для всех 120+ станций. Я чувствую, что что-то упускаю ...   -  person Becalecca    schedule 09.12.2016
comment
@Becalecca - (Не видел ваш последний комментарий. За некоторыми исключениями, SO уведомляет участников о новых комментариях, только если вы используете '@' + имя пользователя.). Эта проблема имеет смысл концептуально, но я не уверен, что слежу за новым JOIN. Здесь должно работать OUTER JOIN (или, возможно, UNION ALL). В любом случае, вы можете создать небольшой пример вроде этого, который демонстрирует проблему?   -  person Leigh    schedule 13.12.2016
comment
@ Ли, спасибо. Я добавил обновление к своему вопросу - я попробовал ваш запрос и, похоже, не получаю правильного результата. Мой коллега сделал тестовую таблицу, используя другой метод, который, похоже, работает (но не уверен, как масштабировать до нескольких станций без отдельных таблиц), и когда я составляю график данных, он помещает правильные пробелы в данных с правильным интервалом на основе отсутствующих дат времени. Я пробовал отображать данные в диаграмме только с одним значением для отсутствующей даты, и это дает пробел, но не размещает данные правильно.   -  person Becalecca    schedule 14.12.2016
comment
похоже, не дает правильного результата. Да, не уверен, видели ли вы мой комментарий, но это потому, что этот конкретный запрос был разработан для другого использования. Он возвращает только недостающие даты. Поэтому его нужно будет объединить с UNION / ALL (чтобы включить существующие даты), а не с JOIN. Кроме того, он возвращает только одну дату, которая, как вы сказали, в любом случае не генерировала правильный интервал, поэтому мы, вероятно, можем поцарапать эту идею ;-)   -  person Leigh    schedule 14.12.2016
comment
@Becalecca - вот пример использования ВНЕШНЕГО СОЕДИНЕНИЯ. В демонстрации используются CTE для генерации даты и времени, но в более долгосрочной перспективе вы, возможно, захотите сделать эти постоянные таблицы вместо этого. Я также предлагаю взглянуть на планы выполнения, чтобы определить, какие из них обеспечивают лучшую производительность: одна календарная таблица (как для даты, так и для времени) или отдельные таблицы.   -  person Leigh    schedule 14.12.2016
comment
@Becalecca - Как дела с SQL? :) Я думал, учитывая большой объем данных, таблицы пермского календаря будут предлагать лучшую производительность, чем POC с CTE, но не тестировал его.   -  person Leigh    schedule 19.12.2016
comment
@Leigh Я работал над этим, скоро у меня будет большое обновление результатов вашего запроса, а также другого метода (с использованием #temptable), который работает хорошо. Но все стало немного сложнее, когда я понял, что стреляю себе в ногу, добавляя в запрос часть N-й строки. Я наполнял специально удаленные строки нулевыми данными ... поэтому работаю над «уточнением» своего подхода.   -  person Becalecca    schedule 20.12.2016
comment
@Becalecca - Ооо ... лол. Да, не думал об этом, но имеет смысл. Фильтры n-й строки необходимо применить после объединения данных.   -  person Leigh    schedule 21.12.2016
comment
@Leigh - Да! Но когда я делаю это и составляю график данных, он теперь помещает перерывы между каждым пробелом «datetime» в данных, а это НЕ то, что он делал раньше. Пытаюсь понять, что происходит!   -  person Becalecca    schedule 21.12.2016
comment
Одна вещь, которая меня смутила, - это столбец [значение времени]. Похоже, он содержит и дату, и время, но ... фильтры используют только дату, то есть «29.05.2014».   -  person Leigh    schedule 21.12.2016
comment
@Leigh - Да, пользователи выбирают диапазон дат по дате только из столбца [значение времени], я беспокоился об этом, но, похоже, он работает нормально.   -  person Becalecca    schedule 22.12.2016
comment
@Becalecca - Значит, этот столбец содержит дату только? Если да, то ничего :) Однако причина, по которой я спрашиваю, заключается в том, что если [timevalue] содержит и дату, и время, т.е. 29.05.2014 17:15:00, то фильтрация выполняется только по дате, например, 29.05.2014 'будет исключать большинство записей за этот день. Обычно я использую подход в конце этой ветки, вместо between, потому что он работает как со столбцами даты, так и со столбцами даты и времени.   -  person Leigh    schedule 22.12.2016
comment
@Leigh - Это очень полезно, спасибо! Я перейду на этот подход, потому что вы правы в том, что [timevalue] - это столбец datetime, и я выполнял фильтрацию только по дате. Спасибо!   -  person Becalecca    schedule 22.12.2016
comment
@Becalecca - Замечательно, и спасибо за публикацию резолюции. Это оказался один из самых интересных вопросов о графиках, с которыми я сталкивался за последнее время :) Ура.   -  person Leigh    schedule 22.12.2016


Ответы (1)


Проработав несколько предложений и проведя множество исследований, проб и ошибок, я думаю, что решил свою проблему. Мне нужно поработать над дополнительным усложнением, которое иногда требует уменьшения объема возвращаемых и графических данных, но эта часть как бы выходит за рамки моего первоначального вопроса.

Краткая версия моего ответа:

  1. Сделал табличное представление MyBigDataTable с дополнительным столбцом, который представляет собой столбец datetime под названием «TIMEVALUE».

  2. Сделал большую постоянную календарную таблицу datetime с таким же столбцом datetime: «TIMEVALUE».

  3. Затем я разработал набор SQL-запросов,

(a) собрать данные из MyBigDataTable и поместить их в #temptable, и

(б) также собирает дату и время из календарной таблицы и помещает ее в тот же #temptable.

Затем (c) поскольку теперь иногда будет 2 строки datetime, одна с данными и одна с нулями, я запускаю запрос, чтобы сохранить строку с данными только в том случае, если есть 2 строки совпадающих datetime и station. Затем эти данные можно отобразить в виде диаграммы.

  1. Все это теперь динамически записано на моей странице .cfm, станция, диапазон дат и параметры выбираются пользователем, и теперь успешно нарисована диаграмма с правильными «пробелами» в датах для времен отсутствия данных.

Вот SQL (здесь ограничен только одним параметром, а у меня 8):

--Step 1. Check if the temptable exists, if it does then delete it
IF OBJECT_ID('tempdb..#TempTable') IS NOT NULL
BEGIN
DROP TABLE #TempTable
END
;
--Step 2. Create the temptable with data from the parameters, station and dates selected on the .cfm 
SET NOCOUNT ON

SELECT 
     timevalue
    ,stationdesc
    ,wtemp
INTO #TempTable

FROM MyBigDataTable
WHERE 
    stationdesc = 'Station01'
    and [timevalue] BETWEEN '5/29/2014' AND '10/01/2016'
GROUP BY 
    TIMEVALUE
    ,stationdesc
    ,wtemp
;
--Step 3. Now select datetimes from a big calendar table, and set stationdesc to the selected station, 
--and rest of parameters to null. And do this for the same selected date range
INSERT INTO #TempTable
SELECT 
[TIMEVALUE] 
,[stationdesc]= 'Station01' 
,wtemp=null
FROM MyDatetimeCalendarTable
WHERE  [timevalue] BETWEEN '5/29/2014' AND '10/01/2016'
;
--Step 4. Run query on the temptable to gather data for chart, but b/c sometimes there will be 2 rows with the same datetime and station but one with data and one with nulls, this query only gathers the row with data if there are 2 rows with matching datetime and station
SELECT distinct *
FROM #TempTable a
WHERE 
wtemp is not null or
    wtemp is null and 
    not exists(
        SELECT * FROM #TempTable b
        WHERE a.timevalue=b.timevalue 
and a.stationdesc=b.stationdesc and b.wtemp is not null)
ORDER BY timevalue
;

Мне нужно полностью протестировать его и внести некоторые поправки, но я думаю, что это удовлетворяет требованиям ответа, потому что пока он делает то, что мне нужно. Спасибо @Leigh и @Dan Bracuk за их мудрость (и терпение!)

person Becalecca    schedule 21.12.2016