<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Sqlx on Tarragon</title><link>https://tarrragon.github.io/blog/tags/sqlx/</link><description>Recent content in Sqlx on Tarragon</description><generator>Hugo -- gohugo.io</generator><language>zh-TW</language><copyright>Tarragon (CC BY 4.0)</copyright><lastBuildDate>Tue, 06 Oct 2026 00:00:00 +0800</lastBuildDate><atom:link href="https://tarrragon.github.io/blog/tags/sqlx/index.xml" rel="self" type="application/rss+xml"/><item><title>10.3 sqlx：手寫 SQL 加上 struct 對映</title><link>https://tarrragon.github.io/blog/programming/go/basic/10-database-access/sqlx/</link><pubDate>Tue, 06 Oct 2026 00:00:00 +0800</pubDate><guid>https://tarrragon.github.io/blog/programming/go/basic/10-database-access/sqlx/</guid><description>&lt;p>這篇整理 &lt;a href="https://github.com/jmoiron/sqlx">sqlx&lt;/a> v1.4.0 的用法。範例沿用 &lt;a href="https://tarrragon.github.io/blog/programming/go/basic/10-database-access/database-sql/#%e7%af%84%e4%be%8b%e8%b3%87%e6%96%99%e8%a1%a8" data-link-title="10.1 database/sql：連線池、查詢結果的讀取與交易" data-link-desc="Go 標準庫存取資料庫的模型：*sql.DB 是連線池、驅動怎麼註冊、Scan 與 NULL、查詢結果的讀取物件（*sql.Rows）沒關閉時的連線洩漏、交易的寫法，以及本模組其餘工具各自接手這一層的哪些工作">10.1 database/sql：連線池、查詢結果的讀取與交易&lt;/a> 定義的 &lt;code>customers&lt;/code> 與 &lt;code>orders&lt;/code> 兩張表。在 &lt;a href="https://tarrragon.github.io/blog/backend/01-database/data-access-layers/" data-link-title="1.17 應用程式存取資料庫的工具分層：驅動、結果對映、從 SQL 產生程式碼、Query Builder 與 ORM" data-link-desc="應用程式存取關聯式資料庫的工作（寫 SQL、取得連線與交易、把結果對映成程式的值），驅動、結果對映、從 SQL 產生程式碼、Query Builder 與 ORM 各接手哪幾件、接手之後哪些行為看不見，ORM 的 N&amp;#43;1 與交易邊界、Go、gunicorn 與 PHP-FPM 各自對資料庫的連線總數與 max_connections、唯一約束違反時各語言讀取約束名稱的位置、讀寫分離時各框架把查詢送到主庫或副本的規則、資料拆到多個資料庫時跨不過的邊界，以及 Go、PHP（Laravel）與 Python 的對應工具與選型軸">1.17 應用程式存取資料庫的工具分層&lt;/a> 的分類裡，sqlx 屬於結果對映：SQL 仍然由程式手寫，它接手的是把查詢結果的每一列放進 struct。取得套件的指令是 &lt;code>go get github.com/jmoiron/sqlx@v1.4.0&lt;/code>。&lt;/p>
&lt;h2 id="sqlxdb-與-databasesql-的關係">sqlx.DB 與 database/sql 的關係&lt;/h2>
&lt;p>&lt;code>*sqlx.DB&lt;/code> 內嵌了 &lt;code>*sql.DB&lt;/code>，所以 &lt;a href="https://tarrragon.github.io/blog/programming/go/basic/10-database-access/database-sql/" data-link-title="10.1 database/sql：連線池、查詢結果的讀取與交易" data-link-desc="Go 標準庫存取資料庫的模型：*sql.DB 是連線池、驅動怎麼註冊、Scan 與 NULL、查詢結果的讀取物件（*sql.Rows）沒關閉時的連線洩漏、交易的寫法，以及本模組其餘工具各自接手這一層的哪些工作">10.1 database/sql：連線池、查詢結果的讀取與交易&lt;/a> 的內容都照樣成立：同一個連線池、同樣的 &lt;code>SetMaxOpenConns&lt;/code>、同樣要關閉的讀取物件、同樣的交易規則。sqlx 只在上面加方法，驅動也沿用 &lt;code>database/sql&lt;/code> 的那一個：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">	&lt;span class="nx">_&lt;/span> &lt;span class="s">&amp;#34;github.com/jackc/pgx/v5/stdlib&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">	&lt;span class="s">&amp;#34;github.com/jmoiron/sqlx&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">6&lt;/span>&lt;span class="cl">&lt;span class="nx">db&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">err&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">sqlx&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">ConnectContext&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">ctx&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">&amp;#34;pgx&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">dsn&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1">// Open 加上 Ping&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;code>sqlx.ConnectContext&lt;/code> 等於 &lt;code>sql.Open&lt;/code> 再 &lt;code>PingContext&lt;/code>，設定錯誤在這一行就回報。手上已經有一個 &lt;code>*sql.DB&lt;/code> 時，用 &lt;code>sqlx.NewDb(db, &amp;quot;pgx&amp;quot;)&lt;/code> 包起來，兩者共用同一個連線池。&lt;/p>
&lt;h2 id="get-與-select依-db-tag-對映">Get 與 Select：依 db tag 對映&lt;/h2>
&lt;p>sqlx 依 struct 欄位上的 &lt;code>db&lt;/code> tag 對應查詢結果的欄位名稱：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="ln"> 1&lt;/span>&lt;span class="cl">&lt;span class="kd">type&lt;/span> &lt;span class="nx">Order&lt;/span> &lt;span class="kd">struct&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 2&lt;/span>&lt;span class="cl">	&lt;span class="nx">ID&lt;/span> &lt;span class="kt">int64&lt;/span> &lt;span class="s">`db:&amp;#34;id&amp;#34;`&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 3&lt;/span>&lt;span class="cl">	&lt;span class="nx">CustomerID&lt;/span> &lt;span class="kt">int64&lt;/span> &lt;span class="s">`db:&amp;#34;customer_id&amp;#34;`&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 4&lt;/span>&lt;span class="cl">	&lt;span class="nx">Amount&lt;/span> &lt;span class="kt">int&lt;/span> &lt;span class="s">`db:&amp;#34;amount&amp;#34;`&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 5&lt;/span>&lt;span class="cl">	&lt;span class="nx">Note&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="kt">string&lt;/span> &lt;span class="s">`db:&amp;#34;note&amp;#34;`&lt;/span> &lt;span class="c1">// NULL 時是 nil&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 6&lt;/span>&lt;span class="cl">	&lt;span class="nx">OrderedAt&lt;/span> &lt;span class="nx">time&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">Time&lt;/span> &lt;span class="s">`db:&amp;#34;ordered_at&amp;#34;`&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 7&lt;/span>&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 8&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln"> 9&lt;/span>&lt;span class="cl">&lt;span class="kd">var&lt;/span> &lt;span class="nx">orders&lt;/span> &lt;span class="p">[]&lt;/span>&lt;span class="nx">Order&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">10&lt;/span>&lt;span class="cl">&lt;span class="nx">err&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">db&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">SelectContext&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">ctx&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="o">&amp;amp;&lt;/span>&lt;span class="nx">orders&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">11&lt;/span>&lt;span class="cl">	&lt;span class="s">&amp;#34;SELECT id, customer_id, amount, note, ordered_at FROM orders WHERE customer_id = $1 ORDER BY id&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">12&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">13&lt;/span>&lt;span class="cl">&lt;span class="kd">var&lt;/span> &lt;span class="nx">o&lt;/span> &lt;span class="nx">Order&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">14&lt;/span>&lt;span class="cl">&lt;span class="nx">err&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nx">db&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">GetContext&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">ctx&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="o">&amp;amp;&lt;/span>&lt;span class="nx">o&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">&amp;#34;SELECT * FROM orders WHERE id = $1&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">999&lt;/span>&lt;span class="p">)&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>




&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">select: 2 &amp;lt;nil&amp;gt;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">get none: sql: no rows in result set&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;code>SelectContext&lt;/code> 讀出多列放進 slice，內部做完 &lt;code>database/sql&lt;/code> 讀多列的那一整套「&lt;code>Next&lt;/code>、&lt;code>Scan&lt;/code>、&lt;code>Close&lt;/code>、&lt;code>Err&lt;/code>」；&lt;code>GetContext&lt;/code> 讀一列，查不到時回傳的仍然是 &lt;code>sql.ErrNoRows&lt;/code>，判斷「找不到」的寫法和 &lt;code>database/sql&lt;/code> 相同。沒有 &lt;code>db&lt;/code> tag 的欄位，sqlx 預設用欄位名稱的小寫去比對（&lt;code>CustomerID&lt;/code> 比對 &lt;code>customerid&lt;/code>），和資料庫慣用的 &lt;code>customer_id&lt;/code> 對不上，所以每個欄位都寫 tag。&lt;/p>
&lt;h2 id="查詢結果的欄位多於-struct-時">查詢結果的欄位多於 struct 時&lt;/h2>
&lt;p>對映發生在執行時。查詢結果有一個 struct 裡沒有的欄位，那一次呼叫就回傳錯誤：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-go" data-lang="go">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="kd">type&lt;/span> &lt;span class="nx">Small&lt;/span> &lt;span class="kd">struct&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">	&lt;span class="nx">ID&lt;/span> &lt;span class="kt">int64&lt;/span> &lt;span class="s">`db:&amp;#34;id&amp;#34;`&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">&lt;span class="kd">var&lt;/span> &lt;span class="nx">s&lt;/span> &lt;span class="p">[]&lt;/span>&lt;span class="nx">Small&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl">&lt;span class="nx">err&lt;/span> &lt;span class="o">:=&lt;/span> &lt;span class="nx">db&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nf">SelectContext&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">ctx&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="o">&amp;amp;&lt;/span>&lt;span class="nx">s&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s">&amp;#34;SELECT id, amount FROM orders&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>




&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">extra column: missing destination name amount in *[]main.Small&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>這個行為在查詢寫 &lt;code>SELECT *&lt;/code> 時會變成部署事故：資料表新增一個欄位之後，所有用 &lt;code>SELECT *&lt;/code> 讀進同一個 struct 的查詢，從 migration 套用的那一刻起同時開始失敗，而 Go 程式一行都沒改。反方向（struct 有、查詢結果沒有的欄位）不會報錯，那個欄位保持零值，同樣不會被發現。所以用 sqlx 時查詢明確列出欄位，struct 與欄位清單一起改。&lt;/p></description><content:encoded><![CDATA[<p>這篇整理 <a href="https://github.com/jmoiron/sqlx">sqlx</a> v1.4.0 的用法。範例沿用 <a href="/blog/programming/go/basic/10-database-access/database-sql/#%e7%af%84%e4%be%8b%e8%b3%87%e6%96%99%e8%a1%a8" data-link-title="10.1 database/sql：連線池、查詢結果的讀取與交易" data-link-desc="Go 標準庫存取資料庫的模型：*sql.DB 是連線池、驅動怎麼註冊、Scan 與 NULL、查詢結果的讀取物件（*sql.Rows）沒關閉時的連線洩漏、交易的寫法，以及本模組其餘工具各自接手這一層的哪些工作">10.1 database/sql：連線池、查詢結果的讀取與交易</a> 定義的 <code>customers</code> 與 <code>orders</code> 兩張表。在 <a href="/blog/backend/01-database/data-access-layers/" data-link-title="1.17 應用程式存取資料庫的工具分層：驅動、結果對映、從 SQL 產生程式碼、Query Builder 與 ORM" data-link-desc="應用程式存取關聯式資料庫的工作（寫 SQL、取得連線與交易、把結果對映成程式的值），驅動、結果對映、從 SQL 產生程式碼、Query Builder 與 ORM 各接手哪幾件、接手之後哪些行為看不見，ORM 的 N&#43;1 與交易邊界、Go、gunicorn 與 PHP-FPM 各自對資料庫的連線總數與 max_connections、唯一約束違反時各語言讀取約束名稱的位置、讀寫分離時各框架把查詢送到主庫或副本的規則、資料拆到多個資料庫時跨不過的邊界，以及 Go、PHP（Laravel）與 Python 的對應工具與選型軸">1.17 應用程式存取資料庫的工具分層</a> 的分類裡，sqlx 屬於結果對映：SQL 仍然由程式手寫，它接手的是把查詢結果的每一列放進 struct。取得套件的指令是 <code>go get github.com/jmoiron/sqlx@v1.4.0</code>。</p>
<h2 id="sqlxdb-與-databasesql-的關係">sqlx.DB 與 database/sql 的關係</h2>
<p><code>*sqlx.DB</code> 內嵌了 <code>*sql.DB</code>，所以 <a href="/blog/programming/go/basic/10-database-access/database-sql/" data-link-title="10.1 database/sql：連線池、查詢結果的讀取與交易" data-link-desc="Go 標準庫存取資料庫的模型：*sql.DB 是連線池、驅動怎麼註冊、Scan 與 NULL、查詢結果的讀取物件（*sql.Rows）沒關閉時的連線洩漏、交易的寫法，以及本模組其餘工具各自接手這一層的哪些工作">10.1 database/sql：連線池、查詢結果的讀取與交易</a> 的內容都照樣成立：同一個連線池、同樣的 <code>SetMaxOpenConns</code>、同樣要關閉的讀取物件、同樣的交易規則。sqlx 只在上面加方法，驅動也沿用 <code>database/sql</code> 的那一個：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="ln">1</span><span class="cl"><span class="kn">import</span> <span class="p">(</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">	<span class="nx">_</span> <span class="s">&#34;github.com/jackc/pgx/v5/stdlib&#34;</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">	<span class="s">&#34;github.com/jmoiron/sqlx&#34;</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">
</span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="nx">db</span><span class="p">,</span> <span class="nx">err</span> <span class="o">:=</span> <span class="nx">sqlx</span><span class="p">.</span><span class="nf">ConnectContext</span><span class="p">(</span><span class="nx">ctx</span><span class="p">,</span> <span class="s">&#34;pgx&#34;</span><span class="p">,</span> <span class="nx">dsn</span><span class="p">)</span> <span class="c1">// Open 加上 Ping</span></span></span></code></pre></div><p><code>sqlx.ConnectContext</code> 等於 <code>sql.Open</code> 再 <code>PingContext</code>，設定錯誤在這一行就回報。手上已經有一個 <code>*sql.DB</code> 時，用 <code>sqlx.NewDb(db, &quot;pgx&quot;)</code> 包起來，兩者共用同一個連線池。</p>
<h2 id="get-與-select依-db-tag-對映">Get 與 Select：依 db tag 對映</h2>
<p>sqlx 依 struct 欄位上的 <code>db</code> tag 對應查詢結果的欄位名稱：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kd">type</span> <span class="nx">Order</span> <span class="kd">struct</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">	<span class="nx">ID</span>         <span class="kt">int64</span>     <span class="s">`db:&#34;id&#34;`</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">	<span class="nx">CustomerID</span> <span class="kt">int64</span>     <span class="s">`db:&#34;customer_id&#34;`</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">	<span class="nx">Amount</span>     <span class="kt">int</span>       <span class="s">`db:&#34;amount&#34;`</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">	<span class="nx">Note</span>       <span class="o">*</span><span class="kt">string</span>   <span class="s">`db:&#34;note&#34;`</span> <span class="c1">// NULL 時是 nil</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">	<span class="nx">OrderedAt</span>  <span class="nx">time</span><span class="p">.</span><span class="nx">Time</span> <span class="s">`db:&#34;ordered_at&#34;`</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="kd">var</span> <span class="nx">orders</span> <span class="p">[]</span><span class="nx">Order</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="nx">err</span> <span class="o">:=</span> <span class="nx">db</span><span class="p">.</span><span class="nf">SelectContext</span><span class="p">(</span><span class="nx">ctx</span><span class="p">,</span> <span class="o">&amp;</span><span class="nx">orders</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">	<span class="s">&#34;SELECT id, customer_id, amount, note, ordered_at FROM orders WHERE customer_id = $1 ORDER BY id&#34;</span><span class="p">,</span> <span class="mi">1</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="kd">var</span> <span class="nx">o</span> <span class="nx">Order</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="nx">err</span> <span class="p">=</span> <span class="nx">db</span><span class="p">.</span><span class="nf">GetContext</span><span class="p">(</span><span class="nx">ctx</span><span class="p">,</span> <span class="o">&amp;</span><span class="nx">o</span><span class="p">,</span> <span class="s">&#34;SELECT * FROM orders WHERE id = $1&#34;</span><span class="p">,</span> <span class="mi">999</span><span class="p">)</span></span></span></code></pre></div>




<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">select: 2 &lt;nil&gt;
</span></span><span class="line"><span class="ln">2</span><span class="cl">get none: sql: no rows in result set</span></span></code></pre></div><p><code>SelectContext</code> 讀出多列放進 slice，內部做完 <code>database/sql</code> 讀多列的那一整套「<code>Next</code>、<code>Scan</code>、<code>Close</code>、<code>Err</code>」；<code>GetContext</code> 讀一列，查不到時回傳的仍然是 <code>sql.ErrNoRows</code>，判斷「找不到」的寫法和 <code>database/sql</code> 相同。沒有 <code>db</code> tag 的欄位，sqlx 預設用欄位名稱的小寫去比對（<code>CustomerID</code> 比對 <code>customerid</code>），和資料庫慣用的 <code>customer_id</code> 對不上，所以每個欄位都寫 tag。</p>
<h2 id="查詢結果的欄位多於-struct-時">查詢結果的欄位多於 struct 時</h2>
<p>對映發生在執行時。查詢結果有一個 struct 裡沒有的欄位，那一次呼叫就回傳錯誤：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="ln">1</span><span class="cl"><span class="kd">type</span> <span class="nx">Small</span> <span class="kd">struct</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">	<span class="nx">ID</span> <span class="kt">int64</span> <span class="s">`db:&#34;id&#34;`</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="kd">var</span> <span class="nx">s</span> <span class="p">[]</span><span class="nx">Small</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="nx">err</span> <span class="o">:=</span> <span class="nx">db</span><span class="p">.</span><span class="nf">SelectContext</span><span class="p">(</span><span class="nx">ctx</span><span class="p">,</span> <span class="o">&amp;</span><span class="nx">s</span><span class="p">,</span> <span class="s">&#34;SELECT id, amount FROM orders&#34;</span><span class="p">)</span></span></span></code></pre></div>




<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">extra column: missing destination name amount in *[]main.Small</span></span></code></pre></div><p>這個行為在查詢寫 <code>SELECT *</code> 時會變成部署事故：資料表新增一個欄位之後，所有用 <code>SELECT *</code> 讀進同一個 struct 的查詢，從 migration 套用的那一刻起同時開始失敗，而 Go 程式一行都沒改。反方向（struct 有、查詢結果沒有的欄位）不會報錯，那個欄位保持零值，同樣不會被發現。所以用 sqlx 時查詢明確列出欄位，struct 與欄位清單一起改。</p>
<p>sqlx 提供 <code>db.Unsafe()</code>，回傳一個會略過多出來欄位的 <code>*sqlx.DB</code>。它讓上面那種事故不發生，代價是 struct 和查詢對不上時一律安靜通過。</p>
<h2 id="具名參數與-in">具名參數與 IN</h2>
<p>sqlx 補了兩個 <code>database/sql</code> 沒有的參數寫法。具名參數用 <code>:名稱</code>，值從 map 或 struct（依 <code>db</code> tag）取：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="ln">1</span><span class="cl"><span class="nx">res</span><span class="p">,</span> <span class="nx">err</span> <span class="o">:=</span> <span class="nx">db</span><span class="p">.</span><span class="nf">NamedExecContext</span><span class="p">(</span><span class="nx">ctx</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">	<span class="s">&#34;UPDATE orders SET note = :note WHERE id = :id&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">	<span class="kd">map</span><span class="p">[</span><span class="kt">string</span><span class="p">]</span><span class="kt">any</span><span class="p">{</span><span class="s">&#34;id&#34;</span><span class="p">:</span> <span class="mi">103</span><span class="p">,</span> <span class="s">&#34;note&#34;</span><span class="p">:</span> <span class="s">&#34;改地址&#34;</span><span class="p">})</span></span></span></code></pre></div><p><code>IN</code> 後面要放一組值時，佔位符的數量要跟著值的數量變。<code>sqlx.In</code> 把一個 slice 展開成對應數量的 <code>?</code>，<code>Rebind</code> 再把 <code>?</code> 換成驅動用的佔位符寫法：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="ln">1</span><span class="cl"><span class="nx">q</span><span class="p">,</span> <span class="nx">args</span><span class="p">,</span> <span class="nx">err</span> <span class="o">:=</span> <span class="nx">sqlx</span><span class="p">.</span><span class="nf">In</span><span class="p">(</span><span class="s">&#34;SELECT id FROM orders WHERE id IN (?)&#34;</span><span class="p">,</span> <span class="p">[]</span><span class="kt">int64</span><span class="p">{</span><span class="mi">101</span><span class="p">,</span> <span class="mi">103</span><span class="p">})</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="nx">q</span> <span class="p">=</span> <span class="nx">db</span><span class="p">.</span><span class="nf">Rebind</span><span class="p">(</span><span class="nx">q</span><span class="p">)</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="kd">var</span> <span class="nx">ids</span> <span class="p">[]</span><span class="kt">int64</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="nx">err</span> <span class="p">=</span> <span class="nx">db</span><span class="p">.</span><span class="nf">SelectContext</span><span class="p">(</span><span class="nx">ctx</span><span class="p">,</span> <span class="o">&amp;</span><span class="nx">ids</span><span class="p">,</span> <span class="nx">q</span><span class="p">,</span> <span class="nx">args</span><span class="o">...</span><span class="p">)</span></span></span></code></pre></div>




<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">in: SELECT id FROM orders WHERE id IN (?, ?) [101 103] &lt;nil&gt;
</span></span><span class="line"><span class="ln">2</span><span class="cl">rebind: SELECT id FROM orders WHERE id IN ($1, $2)
</span></span><span class="line"><span class="ln">3</span><span class="cl">ids: [101 103] &lt;nil&gt;</span></span></code></pre></div><p>只連 PostgreSQL 時另一種寫法是 <code>WHERE id = ANY($1)</code>，把整個 slice 當成一個陣列參數傳，佔位符的數量固定，查詢文字也不隨值的數量改變。</p>
<h2 id="sqlx-留給程式的同步工作struct-與查詢的欄位清單">sqlx 留給程式的同步工作：struct 與查詢的欄位清單</h2>
<p>sqlx 接手了逐欄 <code>Scan</code> 與讀取物件的收尾，而 struct 的欄位與每一段查詢列出的欄位是兩份清單，要在兩處同步改：查詢多一欄是執行時的錯誤，struct 多一欄是安靜的零值，兩者都要測試實際跑過才看得到。把這類檢查提前到產生程式碼時（<code>sqlc generate</code>）的是 <a href="/blog/programming/go/basic/10-database-access/sqlc/" data-link-title="10.4 sqlc：從 SQL 產生型別安全的 Go 程式碼" data-link-desc="sqlc 的工作流程：設定檔指向 schema 與查詢檔、查詢上方的註記決定產生的函式、產生出來的參數與結果型別、欄位寫錯在產生時就被擋下、NULL 與時間欄位的型別對應（預設的 pgtype 型別與改成指標的設定）、選擇性篩選條件的寫法，以及產生步驟帶進建置流程的代價">10.4 sqlc：從 SQL 產生型別安全的 Go 程式碼</a>；只連 PostgreSQL、不需要 <code>*sql.DB</code> 相容性的話，<a href="/blog/programming/go/basic/10-database-access/pgx/" data-link-title="10.2 pgx：PostgreSQL 驅動的原生介面、連線池與 COPY 大量寫入" data-link-desc="pgx 當 database/sql 驅動與用原生介面的差別、pgxpool 連線池、依欄名把查詢結果對映進 struct、從 *pgconn.PgError 讀出約束名稱、交易輔助函式、CopyFrom 大量寫入，以及語句快取與連線池代理的關係">10.2 pgx：PostgreSQL 驅動的原生介面、連線池與 COPY 大量寫入</a> 的 <code>RowToStructByName</code> 提供同樣依欄名的對映，不必再加一個函式庫；它在 struct 有、查詢結果沒有的欄位上會報錯，sqlx 則讓那個欄位保持零值。</p>
]]></content:encoded></item></channel></rss>