Operação SQL no arquivo csv usando bash ou shell

Operação SQL no arquivo csv usando bash ou shell

Este é meu arquivo de entrada

0164318,001449,001452,001922  
0164318,001456,001457,001922  
0842179,002115,002118,001485  
0846354,001512,001513,001590  
0841422,001221,001224,001860  
0841422,001227,001228,001860

Eu quero meu resultado como

0164318,001449,001457,001922  
0842179,002115,002118,001485  
0846354,001512,001513,001590  
0841422,001221,001228,001860 

agrupar usando col1 e encontrar min(col2) e max(col3)
por meio de shell script.

Responder1

Usandocsvkit,

$ csvsql -H --query 'SELECT a,min(b),max(c),d FROM file GROUP BY a' file.csv
a,min(b),max(c),d
164318,1449,1457,1922
841422,1221,1228,1860
842179,2115,2118,1485
846354,1512,1513,1590

Isso carregaria os dados CSV em um banco de dados temporário (SQLite por padrão, acredito) e, em seguida, aplicaria a consulta SQL fornecida a ele. A tabela terá, por padrão, o mesmo nome do arquivo de entrada (sem sufixo) e, como os dados não possuem cabeçalhos de coluna, os nomes dos campos padrão serão em ordem alfabética.

As -Hopções informam csvsqlque os dados não possuem cabeçalhos de coluna.

Para excluir o cabeçalho gerado na saída, canalize o resultado por meio de algo como sed '1d'.

Para obter inteiros preenchidos com zero:

$ csvsql -H --query 'SELECT printf("%07d,%06d,%06d,%06d",a,min(b),max(c),d) FROM file GROUP BY a' file.csv
"printf(""%07d,%06d,%06d,%06d"",a,min(b),max(c),d)"
"0164318,001449,001457,001922"
"0841422,001221,001228,001860"
"0842179,002115,002118,001485"
"0846354,001512,001513,001590"

Aqui, as linhas são citadas, pois na verdade estamos solicitando apenas um único campo de saída para cada registro de resultado (e ele contém vírgulas). Outra forma de fazer isso, que envolve um pouco mais de digitação, mas não gera aspas duplas extras:

$ csvsql -H --query 'SELECT printf("%07d",a),printf("%06d",min(b)),printf("%06d",max(c)),printf("%06d",d) FROM file GROUP BY a' file.csv
"printf(""%07d"",a)","printf(""%06d"",min(b))","printf(""%06d"",max(c))","printf(""%06d"",d)"
0164318,001449,001457,001922
0841422,001221,001228,001860
0842179,002115,002118,001485
0846354,001512,001513,001590

Novamente, o cabeçalho de saída pode ser removido canalizando o resultado por meio de sed '1d'.

Responder2

Usandocsvkit:

csvsql -H --query "select a,min(b),max(c),d from file group by a,d" file.csv

Observe que isso truncará o 0 inicial.

Saída:

a,min(b),max(c),d
164318,1449,1457,1922
841422,1221,1228,1860
842179,2115,2118,1485
846354,1512,1513,1590

Responder3

Com Miller (http://johnkerl.org/miller/doc), usando

mlr --ocsv --quote-all --inidx --ifs , cat inputFile | \
mlr --ocsv --quote-none  --icsvlite stats1 -g '"1"' -a min,max,min -f '"2","3","4"' \
then cut -f '"1","2"_min,"3"_max,"4"_min' \
then label id,col2,col3,col4 | sed 's/"//g'

você tem

id,col2,col3,col4
0164318,001449,001457,001922
0842179,002115,002118,001485
0846354,001512,001513,001590
0841422,001221,001228,001860

Responder4

Você pode dividir seu SQL em operações processuais básicas e replicá-las em um script de shell.

É claro que esta não é uma boa ideia, já que uma das vantagens das linguagens declarativas (como SQL) é que elas escondem a verbosidade e a complexidade da implementação processual para os desenvolvedores, permitindo-lhes concentrar-se nos dados. (A otimização é uma segunda grande vantagem das linguagens declarativas que se perde se você as replicar com um programa processual).
Além disso, esta abordagem é problemática porqueprocessar texto em shell loops geralmente é considerado uma má prática.

No entanto, aqui está um exemplo de shell script que aproveita utilitários padrão que você encontrará pré-instalados em muitos sistemas (exceto para a construção de array - não especificada em POSIX, mas amplamente disponível e certamente disponível para você, já que você está perguntando sobre bash) :

#!/bin/bash

# The input file will be passed as the first argument
file="$1"

# For each input line:
# We take only the values of the first field, sort them, remove duplicates
for i in $(cut -d ',' -f 1 "$file" | sort -n -u); do

    # Resetting the array is not really needed; we do it for safety
    out=()

    # The first field of the output row is the key of the loop
    out[0]="$i"

    # We only consider the rows whose first field is equal
    # to the current key (grep) and...

    # ... we sort the values of the second field
    # in ascending order and take only the first one
    out[1]="$(grep "^${out[0]}" "$file" | cut -d ',' -f 2 | sort -n | head -n 1)"

    # ... we sort the values of the third field in
    # ascending order and take only the last one
    out[2]="$(grep "^${out[0]}" "$file" | cut -d ',' -f 3 | sort -n | tail -n 1)"

    # ... we sort the values of the fourth field in
    # ascending order and take only the first one
    out[3]="$(grep "^${out[0]}" "$file" | cut -d ',' -f 4 | sort -n | head -n 1)"

    # Finally we print out the output, separating fields with ','
    printf '%s,%s,%s,%s\n' "${out[@]}"

done

Deve ser invocado como

./script file

Este script é equivalente a

SELECT col1, MIN(col2), MAX(col3), MIN(col4)
FROM text
GROUP BY col1
ORDER BY col1

informação relacionada