functions
  Functions allow to do advanced sql, the general syntax is this
  select column_name, function_parameters
  from view_name function function_name(function_arguments) 
  group by column_name
  function_parameters: p(colum_name1), p(colum_name2), ...  
  function_arguments: depends on the function
   
  
function fn_having
  Fn_having is a filter that applys on an operator results (for instance items that have been sold in more that 3 stores)
  fn_having(<count|countdistinct|sum|sumpareto>, operator, value)
  operator: = < > <= >= 
   
  select item_id, count(customer_id), countdistinct(customer_id), sum(sales_qty)
  from v_sales
  group by item_id
  callback sort(4,'desc');
| item_id | count(customer_id) | countdistinct(customer_id) | sum(sales_qty) | 
|---|
| artB | 3 | 2 | 17 | 
| artA | 3 | 2 | 15 | 
| # | 1 | 1 | 8 | 
  select sum(sales_qty) from v_sales;
  select item_id, p(customer_id) from v_sales function fn_having(count,=,3) group by item_id;
  select item_id, p(customer_id) from v_sales function fn_having(countdistinct,>=,2) group by item_id;
  select item_id, p(sales_qty) from v_sales function fn_having(sum,<,17) group by item_id;
  
  select item_id, p(sales_qty) from v_sales function fn_having(sumpareto,<=,40) group by item_id;
| item_id | <=40.00 | 
|---|
| artA | y | 
| artB | y | 
| # | y | 
  
  select item_id, p(sales_qty) from v_sales function fn_having(sumpareto,>=,40) group by item_id;
   
function fn_store/fn_merge basics
  Sql is often used to format/display results, fn_store/fn_merge do that
  with 
  with_name1 as (
    select group_by_col_name, stored_values
    from view_name function fn_store ... 
    group by group_by_col_name
  ),
  ...
  select col_type.val, with_name1.alias1 ... from no_view, * function fn_merge
  stored_values: operator(col_name) as alias1, operator(col_name) as alias2 etc..
  operator: <sum|count,countdistinct|min|max|minstr|maxstr|uniques>
  Note1: all the with must have 0 or 1 group by column
  Note2: all the group by columns must have the same col_type, you refer to this col_type with col_type.val in the fn_merge select
   
  with 
  a as(select item_id, sum(avg_week_sales) as v, unique(art_label) as label from v_items function fn_store group by item_id),
  b as(select item_id, sum(sales_qty) as v from v_sales function fn_store group by item_id)
  select 
    col_type.val, 
    a.v, a.label,
    b.v, 
    'avg is '..decode(nvl(a.v,0)>b.v,true,'greater','lower')..' than real sales' as label,
    'previous value is: '..line.label as label2,
  from no_view, * function fn_merge;
| col_type.val | a.v | a.label | b.v | label | label2 | 
|---|
|  | # | # | 8 | avg is lower than real sales | previous value is: avg is lower than real sales | 
| artA | 10 | the article A | 15 | avg is lower than real sales | previous value is: avg is lower than real sales | 
| artB | 10 | the article B | 17 | avg is lower than real sales | previous value is: avg is lower than real sales | 
| box1 | 10 | a box | # | # | previous value is: [string "return 'avg is '..decode(nvl(a.v,0)>b.v,tru..."]:1: attempt to compare nil with number | 
 
function fn_build_filter
  Fn_build_filter builds a boolean matrix, matrix[x][y] = y/n (x: group by col value, y: text column value)
  select group_by_col_name, p(text_col_name) from view_name function fn_build_filter(order,columns_kept)
  group_by_col_name: optional, a one line matrix is created if not provided
  order: <asc|desc> optional defaults to asc
  columns_kept: 1 means keep first, 2 keep first two, etc... optional defaults to keep all
   
  
  select item_id, p(sales_date) from v_sales function fn_build_filter() and item_id nin (null) group by item_id;
| item_id | 20191231 | 20200102 | 
|---|
| artA | X | _ | 
| artB | X | X | 
  
  select item_id, p(sales_date) from v_sales function fn_build_filter(desc) and item_id nin (null) group by item_id;
| item_id | 20200102 | 20191231 | 
|---|
| artA | _ | X | 
| artB | X | X | 
  
  select item_id, p(sales_date) from v_sales function fn_build_filter(asc,1) and item_id nin (null) group by item_id;
| item_id | 20191231 | 20200102 | 
|---|
| artA | X | _ | 
| artB | X | _ | 
 
function fn_apply_filter
  Fn_apply_filter applys the boolean matrix produced by fn_build_filter
  select group_by_col_name, stored_values, p(col_name1), p(col_name2) from view_name
  function fn_apply_filter(with_alias) ... 
  group by group_by_col_name
  with_alias: with holding the fn_build_filter, or null meaning "take previous with"
  group_by_col_name: optional
  stored_values: operator(col_name) as alias1, operator(col_name) as alias2 etc..
  col_name1: must have the same col_type as fn_build_filter_with group by column
  col_name2: must have the same col_type as fn_build_filter_with first parameter column
   
  
  select unique(item_id), sales_date, sum(sales_qty), count(sales_qty) from v_sales and item_id='artA' group by sales_date callback sort(2,'desc') limit(1);
| unique(item_id) | sales_date | sum(sales_qty) | count(sales_qty) | 
|---|
| artA | 20191231 | 15 | 3 | 
  
  select unique(item_id), sales_date, sum(sales_qty), count(sales_qty) from v_sales and item_id='artB' group by sales_date callback sort(2,'desc') limit(1);
| unique(item_id) | sales_date | sum(sales_qty) | count(sales_qty) | 
|---|
| artB | 20200102 | 13 | 2 | 
  
  
  with 
  a as(select item_id, p(sales_date) from v_sales function fn_build_filter(desc,1) and item_id nin (null) group by item_id),
  b as(
    select item_id,
    	maxstr(sales_date) as sales_date, sum(sales_qty) as sales_qty, count(sales_qty) as count, 
    	p(item_id), p(sales_date),
    from v_sales function fn_apply_filter(a)
    group by item_id
  )
  select col_type.val, b.sales_date, b.sales_qty, b.count, stringify(b) from no_view, * function fn_merge
  ;
| col_type.val | b.sales_date | b.sales_qty | b.count | stringify(b) | 
|---|
| artA | 20191231 | 15 | 3 | {#lines:3,count:3,sales_qty:15,sales_date:'20191231',} | 
| artB | 20200102 | 13 | 2 | {#lines:2,count:2,sales_qty:13,sales_date:'20200102',} | 
 
function fn_pivot
  Fn_pivot builds a number matrix, matrix[x][y] = num_value (x: group by col value, y: text column value)
  select group_by_col_name, p(text_col_name), p(number_col_name)
  from view_name function fn_pivot(operator, with_alias, custom_function, signature, before_fn, after_fn)
  group by group_by_col_name
  group_by_col_name: optional, a one line matrix is created if not provided
  operator: <sum|sumprevious|sumafter|last>
  with_alias: with holding the fn_build_filter, or null meaning "no filter, no sort"
  custom_function: optional, can be dynamic code or function in stormbase.so
  signature: <fn_num|fn_num_num|fn_key_num|fn_key3_num2>, the custom function signature (if and only if custom function is provided)
  before_fn: optional, code or null, can be dynamic code or fn name in stormbase.so
  after_fn: optional, code or null, can be dynamic code or fn name in stormbase.so
  Note: custom function usage is explain in the fn_custom section
   
  
  select p(item_id), p(sales_qty) from v_sales function fn_pivot(sum,null);
|  |  |  |  | null |  |  |  |  |  | NO_DATA | artA | artB | box1 | artC | 
|---|
| 0 | 0 | 0 | 0 | 8 | 0 | 0 | 0 | 0 | 0 | 0 | 15 | 17 | 0 | 0 | 
  
  select customer_id, p(item_id), p(sales_qty) from v_sales function fn_pivot(sum,null) group by customer_id;
| customer_id |  |  |  |  | null |  |  |  |  |  | NO_DATA | artA | artB | box1 | artC | 
|---|
| C1 | 0 | 0 | 0 | 0 | 8 | 0 | 0 | 0 | 0 | 0 | 0 | 10 | 4 | 0 | 0 | 
| C2 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 5 | 13 | 0 | 0 | 
  
  select item_id, p(sales_date), p(sales_qty) from v_sales function fn_pivot(sum,null) group by item_id;
| item_id |  |  |  |  | null |  |  |  |  |  | 20191201 | 20191231 | 20201201 | 20201231 | 20190101 | 20200101 | 20200102 | 
|---|
| artA | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 15 | 0 | 0 | 0 | 0 | 0 | 
| artB | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 4 | 0 | 0 | 0 | 0 | 13 | 
| # | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 8 | 
  select item_id, p(sales_date), p(sales_qty) from v_sales function fn_pivot(sumprevious,null) group by item_id;
| item_id |  |  |  |  | null |  |  |  |  |  | 20191201 | 20191231 | 20201201 | 20201231 | 20190101 | 20200101 | 20200102 | 
|---|
| artA | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 15 | 15 | 15 | 15 | 15 | 15 | 
| artB | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 4 | 4 | 4 | 4 | 4 | 17 | 
| # | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 8 | 
  select item_id, p(sales_date), p(sales_qty) from v_sales function fn_pivot(sumafter,null) group by item_id;
| item_id |  |  |  |  | null |  |  |  |  |  | 20191201 | 20191231 | 20201201 | 20201231 | 20190101 | 20200101 | 20200102 | 
|---|
| artA | 15 | 15 | 15 | 15 | 15 | 15 | 15 | 15 | 15 | 15 | 15 | 0 | 0 | 0 | 0 | 0 | 0 | 
| artB | 17 | 17 | 17 | 17 | 17 | 17 | 17 | 17 | 17 | 17 | 17 | 13 | 13 | 13 | 13 | 13 | 0 | 
| # | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 0 | 
  select item_id, p(sales_date), p(sales_qty) from v_sales function fn_pivot(last,null) group by item_id;
| item_id |  |  |  |  | null |  |  |  |  |  | 20191201 | 20191231 | 20201201 | 20201231 | 20190101 | 20200101 | 20200102 | 
|---|
| artA | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 5 | 0 | 0 | 0 | 0 | 0 | 
| artB | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 4 | 0 | 0 | 0 | 0 | 7 | 
| # | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 8 | 
  
  
  select minstr(sales_date) from v_sales and customer_id='C2' and item_id='artA';
| minstr(sales_date) | 
|---|
| 20191231 | 
  with 
  a as(select p(sales_date) from v_sales function fn_build_filter(asc,1) and customer_id='C2' and item_id='artA')
  b as(select item_id, p(sales_date), p(sales_qty) from v_sales function fn_pivot(sum,a) group by item_id)
  select col_type.val, stringify(b) from no_view, * function fn_merge;
| col_type.val | stringify(b) | 
|---|
|  | {20191231:0,} | 
| artA | {20191231:15,} | 
| artB | {20191231:4,} | 
 
function fn_filter_pivot
  Fn_filter_pivot removes columns from matrix created by fn_pivot
  select * from no_view function fn_filter_pivot(with_alias1, with_alias2)
  with_alias1: with holding the fn_pivot
  with_alias2: with holding the fn_build_filter
  Note: used in general with sumafter/sumprevious
   
  
  
  with 
  a as(select p(sales_date) from v_sales function fn_build_filter(asc,1) and customer_id='C2' and item_id='artA'),
  b as(select item_id, p(sales_date), p(sales_qty) from v_sales function fn_pivot(sum,null) group by item_id),
  c as(select * from no_view function fn_filter_pivot(b,a)),
  select col_type.val, stringify(a), stringify(b), stringify(c) from no_view, * function fn_merge;
| col_type.val | stringify(a) | stringify(b) | stringify(c) | 
|---|
|  | {20191231:'X',} | {:0,20191201:0,20201201:0,20191231:0,20200102:8,20190101:0,20201231:0,null:0,20200101:0,} | {20191231:0,} | 
| artA | {20191231:'X',} | {:0,20191201:0,20201201:0,20191231:15,20200102:0,20190101:0,20201231:0,null:0,20200101:0,} | {20191231:15,} | 
| artB | {20191231:'X',} | {:0,20191201:0,20201201:0,20191231:4,20200102:13,20190101:0,20201231:0,null:0,20200101:0,} | {20191231:4,} | 
  
  
  with 
  a as(select item_id, p(sales_date) from v_sales function fn_build_filter(asc,1) and customer_id='C2' group by item_id),
  b as(select item_id, p(sales_date), p(sales_qty) from v_sales function fn_pivot(sum,null) group by item_id),
  c as(select * from no_view function fn_filter_pivot(b,a)),
  select col_type.val, stringify(a), stringify(b), stringify(c) from no_view, * function fn_merge;
| col_type.val | stringify(a) | stringify(b) | stringify(c) | 
|---|
|  | {} | {:0,20191201:0,20201201:0,20191231:0,20200102:8,20190101:0,20201231:0,null:0,20200101:0,} | {} | 
| artA | {20191231:'X',20200102:'_',} | {:0,20191201:0,20201201:0,20191231:15,20200102:0,20190101:0,20201231:0,null:0,20200101:0,} | {20191231:15,20200102:0,} | 
| artB | {20191231:'_',20200102:'X',} | {:0,20191201:0,20201201:0,20191231:4,20200102:13,20190101:0,20201231:0,null:0,20200101:0,} | {20191231:0,20200102:13,} | 
 
function fn_unpivot
  Fn_unpivot factorize the matrix created by fn_pivot
  select * from no_view funtion fn_unpivot(<sum|avg>, with_holding_the_fn_pivot, custom_function)
  custom_function: optional, can be dynamic code or code in stormbase.so
   
  select sum(sales_qty), count(sales_qty), avg(sales_qty) from v_sales;
| sum(sales_qty) | count(sales_qty) | avg(sales_qty) | 
|---|
| 40 | 7 | 5.714 | 
  select p(item_id), p(sales_qty) from v_sales function fn_pivot(sum,null);
|  |  |  |  | null |  |  |  |  |  | NO_DATA | artA | artB | box1 | artC | 
|---|
| 0 | 0 | 0 | 0 | 8 | 0 | 0 | 0 | 0 | 0 | 0 | 15 | 17 | 0 | 0 | 
  with 
  a as (select p(item_id), p(sales_qty) from v_sales function fn_pivot(sum,null))
  b as (select * from no_view function fn_unpivot(sum,a)),
  c as (select * from no_view function fn_unpivot(avg,a))
  select b.val, c.val, b.val/c.val as nb_col_in_fn_pivot from no_view,* function fn_merge;
| b.val | c.val | nb_col_in_fn_pivot | 
|---|
| 40 | 2.667 | 15 | 
  select customer_id,item_id, sum(sales_qty) from v_sales group by customer_id,item_id;
| customer_id | item_id | sum(sales_qty) | 
|---|
| C1 | artA | 10 | 
| C2 | artB | 13 | 
| C1 | artB | 4 | 
| C2 | artA | 5 | 
| C1 | # | 8 | 
  with 
  a as (select customer_id, p(item_id), p(sales_qty) from v_sales function fn_pivot(sum,null) group by customer_id)
  b as (select * from no_view function fn_unpivot(sum,a)),
  c as (select * from no_view function fn_unpivot(avg,a)),
  select col_type.val, b.val, c.val from no_view,* function fn_merge;
| col_type.val | b.val | c.val | 
|---|
| C1 | 22 | 1.467 | 
| C2 | 18 | 1.200 | 
  
  
  with 
  a as(select item_id, p(sales_date) from v_sales function fn_build_filter(asc,1) and customer_id='C2' group by item_id),
  b as(select item_id, p(sales_date), p(sales_qty) from v_sales function fn_pivot(sum,null) group by item_id),
  c as(select * from no_view function fn_filter_pivot(b,a)),
  d as(select * from no_view function fn_unpivot(sum,c)),
  select col_type.val, d.val from no_view, * function fn_merge;
| col_type.val | d.val | 
|---|
| artA | 15 | 
| artB | 13 | 
 
function fn_custom
  Fn_custom: ...
  select group_by_col_name, function_parameters from view_name
  function fn_custom(custom_function, signature, before_fn, after_fn)
  group by group_by_col_name
  group_by_col_name: optional, a one line matrix is created if not provided
  function_parameters: p(col_name1), p(col_name2), etc..
  custom_function: optional, can be dynamic code or function in stormbase.so
  signature: <fn_num|fn_num_num|fn_key_num|fn_key3_num2>, the custom function signature (if and only if custom function is provided)
  before_fn: optional, code or null, can be dynamic code or fn name in stormbase.so
  after_fn: optional, code or null, can be dynamic code or fn name in stormbase.so
   
./_SO_CODE/Z_doc_fn_custom_step1.c
#include "./common.h" 
static char *my_column = "my_column" ;
int fn_one_column_one_line_before(WITH_INFOS* with_infos) {
  with_infos->out_col_count = 1;
  with_infos->out_types = malloc(with_infos->out_col_count * sizeof(int));
  with_infos->out_headers = malloc(with_infos->out_col_count * sizeof(char*));
  char* s = malloc(strlen(my_column)+1);
  strcpy(s, my_column);
  with_infos->out_headers[0] = s;
  with_infos->out_types[0] = ANALYTIC_FLOAT;
  float *f = malloc(sizeof(float));
  *f = 0.;
  with_infos->out_dyn_fn_context = f;
  with_infos->out_multi_thread_autorized = 'n';
  return OK;
}
int fn_one_column_one_line_after(WITH_INFOS* with_infos) {
  WITH_RESULT *res = with_infos->current_with_result;
  float *f = res->dyn_fn_context;
  res->lines_float[0] = malloc(res->col_count * sizeof(float));
  res->lines_float[0][0] = *f;
  return OK;
}
  set SO_FILE_NAME='Z_doc_fn_custom_step1.so';
  
  select p(sales_qty) from v_sales function fn_custom(
   '(void* c, U_INT thread_pos, U_INT iline, float num){float *f=c; *f+=num; return 0.;}',
   'fn_num',
   fn_one_column_one_line_before,fn_one_column_one_line_after
  )
  ;
  select sum(sales_qty) from v_sales
  ;
  
  
  shutdown;