#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. … Я бы предложил добавить некоторые
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. да, это работает.. и большое вам спасибо за ваш ответ.. КОНЕЧНЫЙ раздел немного сложнее.. но потратит некоторое время на понимание..