перемещение логики sql в серверную часть — bash

#bash #shell #perl #awk

#bash #оболочка #perl #awk

Вопрос:

Одна из логик sql перемещается в серверную часть, и мне нужно сгенерировать отчет с использованием сценариев оболочки. Для понимания я упрощаю это следующим образом.

Мой входной файл — sales.txt (идентификатор, цена, месяц)

 101,50,2019-10
101,80,2020-08
101,80,2020-10
201,100,2020-09
201,350,2020-10
 

Вывод должен быть на 6 месяцев для каждого идентификатора, например, t1 = 2020-07 и t2 = 2020-12

 101,50,2020-07
101,80,2020-08
101,80,2020-09
101,80,2020-10
101,80,2020-11
101,80,2020-12
201,100,2020-09
201,350,2020-10
201,350,2020-11
201,350,2020-12
 

Для id 101 , хотя для 2020-07 записи нет, она должна быть взята из значения предыдущего месяца, доступного в файле продаж.
Таким образом, цена = 50 с 2019-10 используется для 2020-07.

Для 201 , сама первая запись относится к 2020-09, поэтому 2020-08 и 2020-07 не применимы. Везде, где есть пробелы, следует распространять значение за предыдущий месяц.

Я пытаюсь использовать awk для решения этой проблемы, я создаю повторно используемый скрипт util.awk, как показано ниже, чтобы сгенерировать недостающие значения, передать его в команду сортировки, а затем снова использовать util.awk для окончательного вывода.

util.awk

 function get_month(a,b,t1) { return strftime("%Y%m",mktime(a " " b t1)) } 
BEGIN { ss=" 0 0 0 "; ts1=" 1 " ss; ts2=" 35 " ss ; OFS="," ; x=1 } 
{ 
  tsc=get_month($3,$4,ts1);
  if ( NR>1 amp;amp; $1==idp )
  {
  if( tsc == tsp) { print $1,$2,get_month($3,$4,ts1); x=0  }
  else { for(i=tsp; i < tsc; i=get_month(j1,j2,i) )  
         { 
       j1=substr(i,1,4); j2=substr(i,5,2); 
           print $1,tpr,i;
         }
       }
   }

  tsp=get_month($3,$4,ts2);  
  idp=$1;
  tpr=$2;
  if(x!=0) print $1,$2,tsc
  x=1;
  
}
 

Но он работает бесконечно awk -F"[,-]" -f utils.awk sales.txt

Хотя я пытался в awk, я приветствую и другие ответы, которые будут работать в среде bash.

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

1. что вы имеете в виду sql logic is moving to backend ? когда я слышу / вижу backend , я думаю о сервере базы данных, но в вопросе нет ничего, связанного с базой данных (sql) …?? сколько строк в вашем входном файле ( sales.txt )?

2. когда я запускаю код, который он генерирует: 101,50,201910 , 101,50,201911 , 101,50,196912 , с этим последним ( 101,50,196912 ), повторяющимся снова и снова (бесконечный цикл?); 196912 выглядит как время эпохи минус 1 сек, что, вероятно, происходит из-за отсутствия / нулевого / пустого / недопустимого аргумента в mktime() (???);при первом прохождении for цикла, j1 , j2 не определены, поэтому не уверен, что это делает для for приращения комонента; for цикл имеет приращение i=get_month(j1,j2,i) , i равное 6-значному числу YYYYMM , которое, похоже, не является допустимым аргументом для функции (???)…

3. … Я бы предложил добавить некоторые print команды в стратегических местах, чтобы показать ваши переменные … из этого вы должны иметь возможность получить некоторые идеи относительно того, где логика выходит за рамки; чтобы основной вызов не прокручивался с экрана, рассмотрите: awk -F"[,-]" -f utils.awk sales.txt | head -100 (настройка head аргумента для отображения достаточного количества информации для нескольких циклов)

Ответ №1:

Общий план:

  • предположение: sales.txt уже отсортирован (численно) по первому столбцу
  • пользователь предоставляет отображаемый диапазон дат min-> max ( awk переменные mindt и maxdt )
  • для отдельного id значения мы загрузим все цены и даты в array ( prices[] )
  • даты будут использоваться в качестве индексов ассоциативного массива для хранения prices ( prices[YYYY-MM] )
  • как только мы прочитаем все записи для данного id
  • сортировка prices[] массива по индексам (т.Е. Сортировка по YYYY-MM )
  • найдите цену за максимальную дату меньше mindt (сохранить как prevprice )
  • для каждой даты между mindt и maxdt (включительно), если у нас есть цена, затем отобразите ее (и сохраните как prevprice ) else …
  • если у нас нет цены, но у нас есть prevprice , тогда используйте это prevprice как текущую дату price (т. Е. Заполните пробел предыдущей ценой)

Одна (GNU) awk идея:

 mindate='2020-07'
maxdate='2020-12'

awk -v mindt="${mindate}" -v maxdt="${maxdate}" -v OFS=',' -F',' ' 

# function to add "months" (number) to "indate" (YYYY-MM)

function add_month(indate,months) {

    dhms="1 0 0 0"                                     # default day/hr/min/secs
    split(indate,arr,"-")
    yr=arr[1]
    mn=arr[2]

    return strftime("%Y-%m", mktime(arr[1]" "(arr[2] months)" "dhms))
}

# function to print the list of prices for a given "id"

function print_id(id) {

    if ( length(prices) == 0 )                         # if prices array is empty then do nothing (ie, return)
       return

    PROCINFO["sorted_in"]="@ind_str_asc"               # sort prices[] array by index in ascending order

    for ( i in prices )                                # loop through indices (YYYY-MM)
        { if ( i < mindt )                             # as long as less than mindt
             prevprice=prices[i]                       # save the price
          else
             break                                     # no more pre-mindt indices to process
        }

    for ( i=mindt ; i<=maxdt ; i=add_month(i,1) )     # for our mindt - maxdt range
        { if ( !(i in prices) amp;amp; prevprice )          # if no entry in prices[], but we have a prevprice, then ...
             prices[i]=prevprice                      # set prices[] to prevprice (ie, fill the gap)

          if ( i in prices )                          # if we have an entry in prices[] then ...
             { prevprice=prices[i]                    # update prevprice (for filling future gap) and ...
               print id,prices[i],i                   # print our data to stdout
             }
        }
}

BEGIN { split("",prices) }                             # pre-declare prices as an array

previd != $1 { print_id(previd)                        # when id changes print the prices[] array, then ...
               previd=$1                               # reset some variables for processing of the next id and ...
               prevprice=""
               delete prices                           # delete the prices[] array
             }

             { prices[$3]=$2 }                         # for the current record create an entry in prices[]

END   { print_id(previd) }                             # flush the last set of prices[] to stdout
' sales.txt
 

ПРИМЕЧАНИЕ: предполагается sales.txt , что сортируется (численно) по первому полю; если это не так, то последняя строка должна быть изменена на ' <(sort -n sales.txt)

Это генерирует:

 101,50,2020-07
101,80,2020-08
101,80,2020-09
101,80,2020-10
101,80,2020-11
101,80,2020-12
201,100,2020-09
201,350,2020-10
201,350,2020-11
201,350,2020-12
 

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

1. @makp-fuso .. файл будет отсортирован по идентификатору и дате. ваше решение работает сейчас .. будет тщательно протестировано

Ответ №2:

Надеюсь, я немного понял ваш вопрос. Следующий awk должен сделать трюк

 $ awk -v t1="2020-07" -v d="6" '
     function next_month(d,a) { 
         split(d,a,"-"); a[2]==12?a[1]   amp;amp; a[2]=1 : a[2]  
         return sprintf("%0.4d-%0.2d",a[1],a[2])
     } 
     BEGIN{FS=OFS=",";t2=t1; for(i=1;i<=d;  i) t2=next_month(t2)}
     {k[$1]}
     ($3<t1){a[$1,t1]=$2}
     (t1 <= $3 amp;amp; $3 < t2) { a[$1,$3]=$2 }
     END{ for (key in k) {
            p=""; t=t1; 
            for(i=1;i<=d;  i) { 
               if(p!="" || (key,t) in a) print key, ((key,t) in a ? p=a[key,t] : p), t
               t=next_month(t)
            }
          }
     }' input.txt
 

Мы реализовали простую функцию next_month , которая вычисляет следующий месяц на основе формата YYYY-MM . Исходя из продолжительности d месяцев, мы вычисляем период времени, который должен быть показан в BEGIN блоке. Интересующий период времени составляет t1 <= t < t2 .

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

для всех случаев, предшествующих интересующему периоду времени, мы сохраняем значение в массиве a с индексом (key,t1) , в то время как для всех остальных случаев мы сохраняем значение в массиве a с ключом (key,$3) .

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

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

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

1. да, это работает.. и большое вам спасибо за ваш ответ.. КОНЕЧНЫЙ раздел немного сложнее.. но потратит некоторое время на понимание..