使用 bash 或 shell 對 csv 檔案進行 SQL 操作

使用 bash 或 shell 對 csv 檔案進行 SQL 操作

這是我的輸入文件

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 進行分組並透過 shell 腳本尋找 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 分解為基本的流程操作,並在 shell 腳本中複製它們。

這當然不是一個好主意,因為聲明性語言(如 SQL)的優點之一是它們向開發人員隱藏了過程實現的冗長和複雜性,使他們能夠專注於資料。 (最佳化是聲明性語言的第二個巨大優勢,如果您使用過程程序複製它們,則該優勢就會消失)。
另外,這種方法是有問題的,因為在 shell 循環中處理文字通常被認為是不好的做法

然而,這裡是一個 shell 腳本的範例,它利用了許多系統上預先安裝的標準實用程式(數組構造除外 - 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

相關內容