The Will Will Web

記載著 Will 在網路世界的學習心得與技術分享

直接將 T-SQL 命令透過 STDIN 的方式傳入 SQL Server Linux 容器執行的方法

我先前寫過兩篇文章,一篇是 使用 Docker 執行 SQL Server on Linux 容器之常用工具與命令 分享各種常見的執行命令,另一篇則是 深入剖析 docker run 與 docker exec 的 -i 與 -t 技術細節 幫助我更加理解 Docker 處理 STDIN 與 Virtual Terminal (VT) 之間的關係。這讓我連結到一個長久以來希望被微軟實現的需求,如果我想直接透過 STDIN 將 T-SQL 傳入 SQL Server on Linux 容器執行,不想先把本機的 T-SQL 檔案複製進去,那該如何處理?想不到還真的給我研究出方法了!

image

我當時在 使用 Docker 執行 SQL Server on Linux 容器之常用工具與命令 文章中,所寫的範例都以 SQL Server 2019 為主,這篇文章我打算改用 SQL Server 2022 來做示範,但基本上是一樣的。

以下就是完整的實作過程:

  1. 建立 SQL Server 2022 on Linux 容器

    docker run --rm -e "ACCEPT_EULA=Y" -e "SA_PASSWORD=Ver7CompleXPW" -p 1433:1433 --name sql1 -d mcr.microsoft.com/mssql/server:2022-latest
    
  2. 查看 SQL Server 容器是否順利啟動

    docker logs sql1
    
  3. 查看 SQL Server 容器版本資訊

    docker exec -it sql1 /opt/mssql-tools/bin/sqlcmd -S . -U sa -P Ver7CompleXPW -Q "SELECT @@VERSION"
    
  4. 下載 ContosoUniversity 範例資料庫的 T-SQL 檔案

    curl -sSLO https://gist.github.com/doggy8088/2a2f7075d49b3814d19513426ede3549/raw/ba8c4e7d46597188a17a39de7906d358f18d834d/ContosoUniversity.sql
    
  5. 匯入 ContosoUniversity 範例資料庫到 SQL Server 容器中

    這裡就是本篇文章的精華所在了,因為我不想透過 docker cpContosoUniversity.sql 檔案複製到容器中,再用 sqlcmd-i 參數傳入檔案路徑,所以想方設法看能不能透過 STDIN 傳入 T-SQL 執行,直到我找到 sqlcmd does not accept queries from standard input #2 這篇討論,但微軟在留言中回覆支援 STDIN 不在計畫內,就把 Issue 給關閉了,殘酷的拒絕了這個想法!🔥

    其實透過 STDIN 傳入應用程式在 Linux 世界是非常常見的用法,還好有善心人在這則 Issue 的下方提供了 Workarround (應變措施),想不到還真的有效,實在是太棒了!😍

    在 Linux 作業系統有個特殊的 /dev/stdin 檔案,嚴格說起來它並不是一個「檔案」,而是一個「虛擬裝置」(pseudo-devices),只是你可以把他當成「檔案」來操作。這個 /dev/stdin 檔案的內容,恰巧就是虛擬終端機(VT)中「標準輸入」的內容,而這正好可以拿來當成 sqlcmd 工具的 -i inputfile 參數值!👍

    Windows PowerShell / Command Prompt

    type ContosoUniversity.sql | docker exec -i sql1 /opt/mssql-tools/bin/sqlcmd -S . -U sa -P Ver7CompleXPW -i /dev/stdin
    

    Bash

    cat ContosoUniversity.sql | docker exec -i sql1 /opt/mssql-tools/bin/sqlcmd -S . -U sa -P Ver7CompleXPW -i /dev/stdin
    

改寫原本文章的語法

如果要執行 SELECT sys.databases 命令,以前我會用 -Q 參數,直接在參數傳入執行,但現在我會改成這樣寫了:

echo "SELECT name FROM sys.databases" | docker exec -i sql1 /opt/mssql-tools/bin/sqlcmd -S . -U sa -P Ver7CompleXPW -i /dev/stdin

或是將欄位調整短一點,讓命令列模式下輸出結果比較好看:

echo "SELECT CAST(name as varchar(30)) as name FROM sys.databases" | docker exec -i sql1 /opt/mssql-tools/bin/sqlcmd -S . -U sa -P Ver7CompleXPW -i /dev/stdin

echo "SELECT CAST(name as varchar(30)) as name FROM sys.databases" | docker exec -i sql1 /opt/mssql-tools/bin/sqlcmd -S . -U sa -P Ver7CompleXPW -i /dev/stdin

如果要列出 ContosoUniversity 資料庫中有多少表格,我就會這樣寫:

echo 'SELECT CAST(name as varchar(30)) as name FROM sys.tables' | docker exec -i sql1 /opt/mssql-tools/bin/sqlcmd -S . -U sa -P Ver7CompleXPW -d 'ContosoUniversity' -i /dev/stdin

如果要列出 ContosoUniversity 資料庫中``Course` 表格的內容,我就會這樣寫:

echo 'SELECT * FROM dbo.Course' | docker exec -i sql1 /opt/mssql-tools/bin/sqlcmd -S . -U sa -P Ver7CompleXPW -d 'ContosoUniversity' -i /dev/stdin

基本上顯示中文也不會有問題:

image

總結

學會了這個技巧,我在 CI 與 CD 的 Pipelines 就可以寫的更漂亮些,而且也不會佔用容器中的空間,自動化建置 Container Image 的過程也會更加方便,實在是太棒了!👍

相關連結

留言評論