Do not input private or sensitive data. View Qlik Privacy & Cookie Policy.
Skip to main content

Announcements
Document boards are being consolidated, this board no longer allows NEW documents READ MORE

The Fastest Dynamic Calendar Script (Ever)

cancel
Showing results for 
Search instead for 
Did you mean: 
tseebach
Partner - Creator III
Partner - Creator III

The Fastest Dynamic Calendar Script (Ever)

Last Update:

Jun 25, 2019 6:28:51 AM

Updated By:

tseebach

Created date:

May 22, 2014 7:42:46 AM

Attachments

Me and a colleague have put together a new Calendar, combining a super fast method for generating the needed calendar, while also removing a lot configuration options in the previous methods.

 

So if you're using a  Min/Max based approach like the one described here: Creating A Master Calendar

 

Then you're properly used to waiting for the Min/Max to find the largest values in your dataset. This can take minutes. What we have here takes less than a second. I've actually not been able to time this, because this calendar script takes less than a 1 seconds even on billions of rows on decent hardware. The only exception to this is if you use a date range starting before christ and spans a thousand years. If you have a date range, with a lot of holes in it (dates with no data), then you should use the last parameter:

Call Calendar('[Date (NK)]','MyCal','Pre.','','true');

The Attached qvw shows both methods (remember the qvs file).

I've not commented it, because most people will not grasp what is going on anyway, and don't need to

To try it out, include the following in your script, and add this to your code:

Call Calendar('DateField');

/* ************************************************************************************

itelligence Standard Qlik Calender Torben Seebach/Martin Didriksen Special Thanks to Rob Wunderlich and John Witherspoon

Configure Calender parameters

  Syntax:

       Calendar(DateField[,CalendarName][,Prefix[,Suffix[,FullCalendar]]])

  Example:

       Call Calendar('Date (NK)','MyCal','Pre.','','true'); // creates a the MyCal table and fields are prefixed with Pre. In the superfast variat

       Call Calendar('Date (NK)'); //creates a table called calendar based of "Date (NK)" field

  Mandatory:

       @_DateField Contains the name of the field to connect the calendar to

  Optional:

       @_CalendarName Contains the name of the calendar we create      
       @_CalendarPrefix A Prefix for all fields
       @_CalendarSuffix A Suffix for all fields      
       @_FullCalendar If this contains a value then it creates a calendar with all dates from Min(Date) to Max(Date), default is the slowest option

************************************************************************************ */

Sub Calendar (_DateField,_CalendarName,_CalendarPrefix,_CalendarSuffix,_FullCalendar)

  Let _StartTime = Now(); 
  Let _CalendarName = If(Len('$(_CalendarName)')=0,'Calendar','$(_CalendarName)');
  Let _CalendarPrefix = If(Len('$(_CalendarPrefix)')=0,'','$(_CalendarPrefix)');
  Let _CalendarSuffix = If(Len('$(_CalendarSuffix)')=0,'','$(_CalendarSuffix)');
  Let _FullCalendar = If(Len('$(_FullCalendar)')=0,1,0);
  Let _DateField = PurgeChar(_DateField,'"[]');

  "$(_CalendarName)":
   LOAD
        Distinct [$(_DateField)] as [$(_DateField)],
        Text(Date([$(_DateField)])) as [$(_CalendarPrefix)DateText$(_CalendarSuffix)],
        Year([$(_DateField)]) as [$(_CalendarPrefix)Year$(_CalendarSuffix)],
        Week([$(_DateField)])&'-'&Year([$(_DateField)]) as [$(_CalendarPrefix)WeekYear$(_CalendarSuffix)],
        Week([$(_DateField)]) as [$(_CalendarPrefix)Week$(_CalendarSuffix)],
        Month([$(_DateField)]) as [$(_CalendarPrefix)Month$(_CalendarSuffix)],
        Year([$(_DateField)])&'-'&Text(Date([$(_DateField)],'MM')) as [$(_CalendarPrefix)YearMonth$(_CalendarSuffix)],         'Q'&Ceil(Month([$(_DateField)])/3) as [$(_CalendarPrefix)Quarter$(_CalendarSuffix)],
        AutoNumber(MonthStart([$(_DateField)]),'_MonthSerial') as [$(_CalendarPrefix)MonthSerial$(_CalendarSuffix)],
        AutoNumber(QuarterStart([$(_DateField)]),'_QuarterSerial') as [$(_CalendarPrefix)QuarterSerial$(_CalendarSuffix)],

        AutoNumber(weekyear([$(_DateField)]) &'|'&week([$(_DateField)]),'_WeekSerial') as [$(_CalendarPrefix)WeekSerial$(_CalendarSuffix)] ;

  If _FullCalendar=1 Then
        LOAD Date(_DateStart+(Iterno()-1),'$(DateFormat)' ) as [$(_DateField)]
               While (_DateStart+(Iterno()-1)<=_DateStop);
        LOAD
               Floor(Min(Fieldvalue('$(_DateField)',RecNo()))) as _DateStart,
               Floor(Max(Fieldvalue('$(_DateField)',RecNo()))) as _DateStop
        AUTOGENERATE FieldValueCount('$(_DateField)');
Else
        LOAD Num(Fieldvalue('$(_DateField)',RecNo())) as [$(_DateField)]
        AUTOGENERATE FieldValueCount('$(_DateField)');
End If

  /* clean variables  */

Let _TotalTime = Round((Now()-_StartTime)*60*60*24,0.00000000001);
Let _StartTime = NULL;
Let _vDateStart = NULL;
Let _vDateStop = NULL;
Trace $(_CalendarName) created in: $(_TotalTime) seconds;
Let _TotalTime = NULL;

ENDSUB

Updated 2014-05-23:

  • Implement preceding load method suggested by Rob Wunderlich
  • Fixed Timer to show seconds
  • Corrected error in documentation
  • Added PurgeChar so [Date A],"Date B" also works.
  • Thanks to Stephen Redmond for pointing to a boolean error in line 22

Other than my brilliant Collegue Martin Didriksen, I also want to give John Witherspoon some credit for inspiration on the autogerate method used. I think he was the first who posted it online.

Tags (3)
Comments
tseebach
Partner - Creator III
Partner - Creator III

Well, If you want to benchmark, I think at real test needs to be done. This calendar is fast, because it finds the highest and lowest value and generates any date in between, within the application.

Generating the calendar outside of Qlik, is a very typical datawarehousing method, which is mostly discarded by the Qlik community because of lack of flexibility and speed.

Also the engine and core is written in a different language, parts of this is done in assembly. So its actually much faster than Visual C++, which is only used for the GUI and API.

0 Likes
Anonymous
Not applicable

Only some parts of calculation is Assembler made.


Loading and Generation of QVD's don't. My process is fastest than QVS or Desktop.


Assembler  is fastest as any language but - Have You already made any code in Assembler?


I have use it in IBM Main Frame and I prefer C++ pointers...


Windows it self is almost C++  with Inline Assembler.

Generating QVD's outside QVS is the best solution always because of all reasons:

Security - No DB access needed

Performance - QVS run's in optimized mode

Lightest -. Don't need ODBC, OLEDB others

Simplicity - Don't need confused Scripts to generate a simple table.

Dynamic - Try generate your Calendar begin in  before 46 BC when "Ianuarius" as 29 days.

Conclusion: Your calendar inside QVW is maybe the fastest EVER but not the fastest pure QVD generation Calendar.

0 Likes
Not applicable

Is it also working with Sense?

I have already tried a lot but it doesn't work...

Actually Sense is telling me that there is an error but not exactly where it is and whats wrong.

0 Likes
datanibbler
Champion
Champion

Hi,

really good post.

I think, though, that quite a lot of people would like to grasp what's going on in your script - I would want to know, anyway, before I run a script, and generally speaking, when I don't understand a script, I can't expect my colleagues to do so and I don't use it - I only use code people can understand, even if it's not the fastest possible, and my scripts usually contain quite a nr. of lines of comments.

In my experience, to most people that matters more than waiting a few seconds more or less for a script to complete.

We have had a mastercalendar script, running (on AUTOGENERATE, independent of the data in the specific app, fast enough) from a few years back until the end of next year) and we have included that everywhere ever since, only adding a routine for holidays and for that year-end-week 52/ 53.

In our industry, having a longer-running calendar would not make sense  because all the base_files we have for any data (and even the database) change every now and then ...

0 Likes
plexpro52
Creator
Creator

Hi Stephen,

>> Luckily, QlikView has a WeekYear function that will give you the correct year to go with the ISO week number.

In Qlik Sense 2.0.1 (and so I suppose this may apply to QlikView as well), I have discovered a strange result in date analysis when I extract year and week for certain dates. I created a Seed Calendar table containing one column, a date field called TempDate. I populated it from 1/1/2010 to the present. Among the composite date fields with which I am experimenting are some week-year fields.

I set up these values to rule my date functions:

SET FirstWeekDay=6;
SET BrokenWeeks=1;

The following is the minimum of the master calendar code to show the anomaly:

SeedCalendar:
LOAD
      Date($(vMinDate) + (RowNo() - 1)) as TempDate
AutoGenerate $(vCalendarSpan);

//Create the Master Calendar
MasterCalendar:
LOAD
      *
      ,Week & '-' & If(Week=1 AND Month=12,Year+1, If(Match(Week,52,53) AND Month=1, Year-1, Year)) as WeekYear1;

LOAD
TempDate as Date
Week(WeekStart(TempDate)) & '-' & WeekYear(TempDate) as WeekYear2;

The formulae for WeekYear1 and WeekYear2 both come from various Qlik sources. Here are the results for the first four days of 2010:

Date          Day of Week     WeekYear1     WeekYear2
1/1/2010     Friday                1-2010               53-2009
1/2/2010     Saturday            1-2010               53-2009
1/3/2010     Sunday              2-2010                2-2009
1/4/2010     Monday              2-2010                2-2010

The WeekStart() function by default refers to the FirstWeekDay value that has been set previously, and so the week changes on 1/3/2010. However, it appears that the WeekYear() function does not take this into account, and it says, "Hey, Sunday is still in the prior week, and the year on the prior Monday was still 2009!" This produces a bizarre result of January 3, 2010 belonging to the second week of 2009!

0 Likes
Anonymous
Not applicable

It was very useful to me.

Thank you.

CB.

0 Likes
ali-soy
Contributor
Contributor

Hello everyone 
This is really great @tseebach . But I have one issue if I use Day-Serial as additional information as soon as I use the FullCalendar... As soon as I'm using it the numbering of my 'Day-Serial' is in the opposite order.

using Full Calendar:

DateDaySerial
30.5.20214
29.5.20213
28.5.20212
27.5.20211

 

not using the Full Calendar

DateDaySerial
30.5.20211
28.5.20212
27.5.20213

 

I like to have the most recent date (30.5.2021) as '1' even if I use the full calendar

DaySerial :

AutoNumber(Daystart([$(_DateField)]),'_DaySerial')    as [$(_CalendarPrefix)DaySerial$(_CalendarSuffix)],

THANK YOU FOR YOUR HELP!!!

0 Likes
nate_muir_anderson
Contributor II
Contributor II
        LOAD Date(_DateStart+(Iterno()-1),'$(DateFormat)' ) as [$(_DateField)]
               While (_DateStart+(Iterno()-1)<=_DateStop);
        LOAD
               Floor(Min(Fieldvalue('$(_DateField)',RecNo()))) as _DateStart,
               Floor(Max(Fieldvalue('$(_DateField)',RecNo()))) as _DateStop
        AUTOGENERATE FieldValueCount('$(_DateField)');

 

@rwunderlich  gave the advice on preceding load with MIN() and MAX()... His example used RESIDENT

However @tseebach  uses  AUTOGENERATE FieldValueCount('$(_DateField)');

I was confused by that; there's no reason to AUTOGENERATE so many rows, you only want 1 single row, with a MIN() and MAX() value

However, @tseebach  macro does not get the table name; so can't use RESIDENT $(_DateTable) because you don't know the name of the table where the $(_DateField) comes from!

Also, the MIN() and MAX() will ensure, even if there is an AUTOGENERATE(1000) statement, that only 1 row is generated! I guess that's because MIN() and MAX() are aggregations, and have an implied GROUP; which is all rows; reducing arbitrary number of rows (1, 10, 1000), to a single row.

0 Likes
Contributors
Version history
Last update:
‎2019-06-25 06:28 AM
Updated by: