Циклы по месяцам в смысле Qlik

#loops #date #qliksense

Вопрос:

Может ли кто-нибудь помочь мне найти решение для приведенного ниже сценария? (Формат даты «ММ/ДД/ГГГГ»)

введите описание изображения здесь

Дата Cr — это дата создания учетной записи, Дата окончания-дата окончания учетной записи, количество месяцев означает месяцы между датой создания и датой окончания, Дата оплаты означает номер дня каждого месяца, в который должен быть произведен платеж.

Мне нужно сгенерировать это поле Даты выполнения в виде даты, как показано ниже, которая является непрерывными сроками выполнения между датой создания и датой окончания.

1/8/2020

2/8/2020

3/8/2020

4/8/2020

5/8/2020

6/8/2020

Не могли бы вы, пожалуйста, помочь мне найти решение этой задачи?

Ответ №1:

Одним из возможных решений является перебор всех значений и автогенерация дат.

Пример аннотированного сценария загрузки:

 // Sample data
RawData:
Load * Inline [
  Id, Cr Date   , End Date
  1 , 12/08/2019, 06/08/2020
  2 , 05/07/2019, 16/11/2020
];


// temp table to keep distinct values 
// of the concatenation of id, cr date and end date
// example record:
// 1^12/08/2019^06/08/2020
TempTable:
Load
  distinct
  Id amp; '^' amp; [Cr Date] amp; '^' amp; [End Date] as Id_Dates
Resident
  RawData
;

// for each record in Id_Dates field
for i = 1 to FieldValueCount('Id_Dates')
  // get the current iteration value
  let value = FieldValue('Id_Dates', $(i));
  // extract the Id
  let currentId = SubField('$(value)', '^', 1);
  // extract cr date  
  let currentCrDate = Num(SubField('$(value)', '^', 2));
  // extract end date
  let currentEndDate = Num(SubField('$(value)', '^', 3));

  // autogenerate all dates between the currentCrDate and currentEndDate
  // add the current Id value (this will link to the RawData table
  DueDates:
  LOAD
    '$(currentId)' as Id,
    date($(currentCrDate)   IterNo() - 1, 'DD/MM/YYYY') AS DueDate
  AUTOGENERATE (1)
  WHILE 
    $(currentCrDate)   IterNo() -1 <= $(currentEndDate)
  ;

next

// we dont need this table anymore
Drop Table TempTable;
 

Как только сценарий будет завершен, он будет содержать две таблицы:

Модель данных

И DueDates таблица будет содержать такие значения, как это:

Обусловливает ценности

P.S. Это решение может быть не очень эффективным, если исходные данные имеют много различных Id значений. Дайте мне знать, если это так, и я придумаю какое-нибудь другое решение

Обновление (06/10/2021)

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

Этот подход будет быстрее, но, скорее всего, он будет потреблять больше оперативной памяти во время перезагрузки. Как только перезагрузка будет завершена, результирующее приложение должно иметь тот же объем памяти, что и подход «цикл по каждой строке».

 RawData:
Load * Inline [
  Id, Cr Date   , End Date
  1 , 12/08/2019, 06/08/2020
  2 , 05/07/2019, 16/11/2020
];

// Get min and max dates from [Cr Date] and [End Date] fields
TempTable1:
Load 
    min([Cr Date]) as MinDate,
    max([Cr Date]) as MaxDate
Resident
    RawData
;

concatenate

Load 
    min([End Date]) as MinDate,
    max([End Date]) as MaxDate
Resident
    RawData
;

// Get the overall min and max dates
NoConcatenate

TempTable2:
Load
  min(MinDate) as MinDate,
  max(MaxDate) as MaxDate
Resident
    TempTable1
;

Drop Table TempTable1;

// gnerate all possible dates between the min and max dates
// once the dates are generated join the result table to RawData
// since there is no common fields between both tables 
// the result table will be many to many join (cartesian join)
// as a result at this point RawData will be quite large table
// No of rows in RawData (initially) * No of rows in the Calendar table
// for example if RawData has 10 rows and calendar have 1000 the result table
// will have 10 000 rows
// We will reduce the rows a bit in the next step
let vMinDate = peek('MinDate');
let vMaxDate = peek('MaxDate');

join (RawData)

Calendar:
Load
  Date($(vMinDate)   IterNo() - 1, 'DD/MM/YYYY') as DueDate
Autogenerate 1
While 
    $(vMinDate)   IterNo() - 1 <= $(vMaxDate)
;

Drop Table TempTable2;

// Load resident modified RawData table and while loading we'll create new field
// This field will be used a flag and we'll filter on it at the end
// The logic in the field is:
//   if [Cr Date] >= DueDate <= [End Date] then set it to 1 else 0
// The final step is to keep only records with TempFlag == 1
NoConcatenate

RawData_Final:
Load 
  Id,
  [Cr Date],
  [End Date],
  DueDate
Where 
  TempFlag = 1  
;
Load 
  Id,
  [Cr Date],
  [End Date],
  DueDate,
  if(DueDate >= [Cr Date] and DueDate <= [End Date], 1, 0) as TempFlag
Resident
    RawData
;

Drop Table RawData;
 

Комментарии:

1. Большое вам спасибо, Стефан Стойчей. Я нашел решение. Большое спасибо.

Ответ №2:

@Стефан Стойчев

Спасибо за ваш отличный код…тем не менее, моя таблица значений идентификаторов содержит около 200000 entries…it делает загрузку скрипта довольно медленной. Вы думали о другом методе ?

Спасибо, вот адаптация вашего кода, который я использую

 [ZSQ042_TEMP]:
LOAD
    key //commande amp; poste
    ,[COMMANDE]
    ,[POSTE]
    , YEAR_WEEK_REF_TAUX_SERVICE
    , WEEK_SM_REELLE_ZSQ
    
    ,[ORIGINE]
    ,[Business unit]
    ,[Sector]
    ,TYPE_cde
    
 resident [SALES]
 where num(WEEK_SM_REELLE_ZSQ) > num(YEAR_WEEK_REF_TAUX_SERVICE)
 ;
 
 
//on duplique toutes les lignes qui ne sont pas livrées à la semaine annoncée
 
 TempTable:
Load
  distinct
  key amp; '^' amp; COMMANDE amp; '^' amp; POSTE amp; '^' amp; YEAR_WEEK_REF_TAUX_SERVICE amp; '^' amp; WEEK_SM_REELLE_ZSQ amp; '^' amp;
  [ORIGINE] amp; '^' amp; [Business unit] amp; '^' amp; [Sector] amp; '^' amp; TYPE_cde
  as Id_Weeks
Resident
  [ZSQ042_TEMP]
;


// for each record in Id_Dates field
for i = 2 to FieldValueCount('Id_Weeks')
 
 // get the current iteration value
  let value = FieldValue('Id_Weeks', $(i));
  
  // extract the Id
  let currentId = SubField('$(value)', '^', 1);
  
  //Extract Order Number
  let currentOrder = SubField('$(value)', '^', 2);
  
  //Extract Order Line Number
  let currentLine = SubField('$(value)', '^', 3);
    
  // extract week to be delivered  
  let currentWeekStart = Num(SubField('$(value)', '^', 4));
  
  // extract week of delivery
  let currentWeekEnd = Num(SubField('$(value)', '^', 5));
  
  // extract origin
  let currentOrigin = SubField('$(value)', '^', 6);
  
  // extract BU
  let currentBU = SubField('$(value)', '^', 7);

// extract Sector
  let currentSector = SubField('$(value)', '^', 8);
  
  // extract Type de commande
  let currentType = SubField('$(value)', '^', 9);
   
// autogenerate all weeks between the week to deliver and week of delivery
   // add the current Id value (this will link to the RawData table)


Concatenate([SALES])
LOAD 
    '$(currentId)' as key,
    '$(currentOrder)' as COMMANDE,
    '$(currentLine)' as POSTE,
    '$(currentWeekStart)' as INITIAL_YEAR_WEEK_REF_TAUX_SERVICE,
    $(currentWeekStart)   IterNo() as YEAR_WEEK_REF_TAUX_SERVICE, //IterNo()-1
    '$(currentWeekEnd)' as WEEK_SM_REELLE_ZSQ,
    IterNo() as Nb_Weeks_Late,
   
    
    
    '$(currentOrigin)' as ORIGINE,
    '$(currentBU)' as Business_unit,
    '$(currentSector)' as Sector,
    '$(currentType)' as TYPE_cde
    
    
    
  AUTOGENERATE (1)
  WHILE 
    $(currentWeekStart)   IterNo() <= $(currentWeekEnd) //IterNo()-1
  ;

next

// we dont need this table anymore
Drop Table TempTable;
Drop Table ZSQ042_TEMP;
 

Комментарии:

1. я обновил свой ответ и предоставил другое решение. Второе решение должно работать быстрее, но должно потреблять больше памяти во время перезагрузки. Как только перезагрузка будет завершена, данные о результатах будут такими же, как и в первом решении