Операция SQL над CSV-файлом с использованием bash или shell

Операция SQL над CSV-файлом с использованием bash или shell

Это мой входной файл

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

Я хочу, чтобы мой результат был таким же

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

сгруппировать с помощью col1 и найти min(col2) и max(col3)
с помощью скрипта оболочки.

решение1

С использованиемcsvkit,

$ 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

Это загрузит данные CSV во временную базу данных (я полагаю, по умолчанию SQLite), а затем применит к ней данный SQL-запрос. Таблица по умолчанию будет иметь то же имя, что и входной файл (без суффикса), и поскольку у данных нет заголовков столбцов, имена полей по умолчанию будут в алфавитном порядке.

Параметры -Hуказывают csvsql, что данные не имеют заголовков столбцов.

Чтобы удалить сгенерированный заголовок в выходных данных, передайте результат через что-то вроде sed '1d'.

Чтобы получить целые числа, заполненные нулями:

$ 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"

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

$ 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

Опять же, заголовок вывода можно удалить, передав результат через sed '1d'.

решение2

С использованиемcsvkit:

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

Обратите внимание, что это приведет к обрезанию начального 0.

Выход:

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

решение3

С Миллером (http://johnkerl.org/miller/doc), с использованием

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'

у вас есть

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

решение4

Вы можете разбить свой SQL на базовые процедурные операции и воспроизвести их в скрипте оболочки.

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

Однако вот пример скрипта оболочки, который использует стандартные утилиты, которые вы найдете предустановленными во многих системах (за исключением конструкции массива — не указанной в POSIX, но широко доступной и наверняка доступной вам, поскольку вы спрашиваете о ней 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

Он предназначен для вызова как

./script file

Этот скрипт эквивалентен

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

Связанный контент