Managing SQL queries in Go

blov · · 699 次点击    
这是一个分享于 的资源,其中的信息可能已经有所发展或是发生改变。
<p>I&#39;m currently building a service that extracts a different data from a database.</p> <p>I have about 15 similar queries (different in data produced, but same exact parameters), and for each one I have a function that look like that:</p> <pre><code>stmt, err := db.Prepare(`MY_SQL_QUERY`) if err != nil { log.Fatal(err) } rows, err := stmt.Query(age) if err != nil { log.Fatal(err) } defer rows.Close() for rows.Next() { var name string if err := rows.Scan(&amp;name); err != nil { log.Fatal(err) } // Do something (can be a function that I pass as a parameter) } if err := rows.Err(); err != nil { log.Fatal(err) } return something </code></pre> <p>However this is very repetitive. I was thinking of storing the queries and their respective data manipulation functions as variables somewhere, but then how would that look like? Not sure how to manage this situation</p> <hr/>**评论:**<br/><br/>DeathstrokePHP: <pre><p>I was in the same boat and now I&#39;m currently using <a href="https://github.com/jmoiron/sqlx/" rel="nofollow">https://github.com/jmoiron/sqlx/</a>. It helps me reduce the queries down.</p></pre>godevops: <pre><p>hmm I&#39;d like to stick with the standard library, and I think there should be a way to do what I want in a nice way. However if I won&#39;t be happy with any other solutions I&#39;ll definitely consider that</p></pre>DeathstrokePHP: <pre><p>sqlx is pretty much extensions to the standard database/sql. I was also doing the same thing and try to write my own solution or wrapper to the standard library. Best of luck!</p></pre>So_Brave: <pre><p>The interfaces are really similar between sqlx and the standard lib. It&#39;s pretty much just giving you a lot of niceties around struct scanning, transactions, IN queries, named queries (was super helpful pre-1.8). It&#39;s all pretty low level--you still are writing your SQL queries out. </p> <p>For example a <a href="https://github.com/jmoiron/sqlx/blob/master/sqlx.go#L171" rel="nofollow">sqlx row</a> is just a wrapped sql.Rows with additional properties/methods to make scanning easier.</p> <pre><code>type Row struct { err error unsafe bool rows *sql.Rows Mapper *reflectx.Mapper } </code></pre> <p>Like Deathstroke mentioned, we were pretty much writing our own solution at first. Would rather use a well documented and tested library written by someone with a strong grasp of the language. It&#39;s a great example of idiomatic Go IMO. I learned a lot about the sql standard lib going through the source.</p></pre>qxclpxzk: <pre><p>You have a few options. I&#39;ll expound on your idea, propose an alternative, and name other possible solutions.</p> <p>As you suggested, you can refactor your prepared statements into a high level struct so they can persist and be reused, then write methods to execute queries using them and return usable results.</p> <p>Here&#39;s a pattern you can use. It doesn&#39;t do much about the repetitiveness, but it does let you share and reuse prepared statements:</p> <pre><code>type QueryStore struct { db *sql.DB prepared map[string]*sql.Stmt // you might want to use an Enum type instead of string for added safety } func NewQueryStore(db *sql.DB) *QueryStore { q := &amp;QueryStore{db: db, prepared: map[string]*sql.Stmt{}} // add your prepared statements to the map stmt, err := db.Prepare(&#34;bar&#34;) checkErr(err) q.prepared[foo] = stmt // etc return q } func (s QueryStore) getNames(param string) []string{ rows, err := s.prepared[foo].Query(param) // error checking var results []string // row scanning // more error checking // etc... return results } </code></pre> <p>Alternatively, you can use an ORM to significantly cut down on your database code.</p> <p>If you use Postgres, there&#39;s <a href="https://github.com/go-pg/pg"><code>go-pg</code></a>. I&#39;m a big fan of this package. Other databases have support from more general ORMs such as <code>gorm</code> or <code>xorm</code>.</p> <p>Here are a couple of examples of code using <code>go-pg</code>:</p> <pre><code>// attempt to select first match into a struct yourDB.Model(&amp;someStruct).Where(&#34;some_field = ?&#34;, someVar).Select() </code></pre> <p>or</p> <pre><code>// select just the name column from all rows in a table into an array err := yourDB.Model(&amp;someArray).Column(&#34;name&#34;).Select() if err != nil { log.Fatal(err) // might be a bad idea if you get an &#39;ErrNoRows&#39; } for x := range someArray { someFunc(someArray[x].Name) } </code></pre> <p>Other solutions you could investigate include <code>sqlx</code> as others have suggested, or even code generation (not my cup of tea).</p></pre>godevops: <pre><p>Hey, thanks for the comment. I like your idea of QueryStore, however, using prepared statements like that is not a good idea in my opinion. Prepared statements need to be closed after they&#39;re used. I have a long-running service and I don&#39;t think keeping those statements open for long period of time is even possible (I might be wrong). Regardless, they shouldn&#39;t be open when the service is not used (could be days)</p> <p>I do want to have all the queries at a centralized place so they could easily be updated / extracted to other usages.</p> <p>Also I should probably mention: The only queries I&#39;m interested in are for data retrieval, never for manipulation</p> <p>How does something like this sound:</p> <pre><code>file queries.go type Query string // queries are global to the package, this is somewhat OK quertyNamesUnderTenYearsOld := Query(` SELECT ... FROM WHERE GROUP BY `) quertyAverageHeightOfTenYearsOldsWithName := Query(` SELECT ... FROM WHERE GROUP BY `) ... ... </code></pre></pre>ryeguy: <pre><p>There is no reason to close prepared statements. They are closed with the connection. </p> <p>Keeping prepared statements open is their entire purpose. If your service is intermittent, then just let the connections die off after no activity. </p></pre>dvk0: <pre><p>To help reduce some of the scan-into-struct repetition, I found <a href="https://github.com/jmoiron/sqlx" rel="nofollow">https://github.com/jmoiron/sqlx</a> to be great. Alternatively, <a href="https://github.com/russross/meddler" rel="nofollow">https://github.com/russross/meddler</a> does just as great a job and supports NULL values too.</p> <p>For the actual query itself, I&#39;d stick to hand-writing your SQL queries. With the scanning delegated to sqlx or meddler, you&#39;ll find most of the functions in your data access layer will only consist of 3-5 lines anyway.</p></pre>PacNinja: <pre><p>sqlx like others mentioned or if you feel like taking ORM route have a look at <a href="https://github.com/vattle/sqlboiler">sqlboiler</a>. It generates an ORM from your DB schema and is performant.</p></pre>thebigredone91: <pre><p>I wrote a library that helps me serialize structs into SQL. It is for postgres but could give you some ideas: <a href="https://github.com/Tebro/gopsql" rel="nofollow">https://github.com/Tebro/gopsql</a></p></pre>pgaleone: <pre><p>Hey, I made something similar some time ago, have a look: <a href="https://github.com/galeone/igor" rel="nofollow">https://github.com/galeone/igor</a></p> <p>It&#39;s basically an abstraction layer over your same implementation</p></pre>quiI: <pre><p>I literally have my editor open working on some SQL queries, this is how we structure it</p> <pre><code>type LegacyStore struct { db *sql.DB logger tools.Logger statsd tools.StatsD statements *statements } type statements struct { insertEmail *sql.Stmt insertMapping *sql.Stmt insertEmailEvent *sql.Stmt insertBounced *sql.Stmt } </code></pre> <p>Then there are methods on <code>LegacyStore</code> called things like <code>InsertEmail</code> which use those stored statements. I have a function called <code>NewLegacyStore</code> which returns me a <code>LegacyStore</code> and inside that it prepares all the statements for future use. </p></pre>Growlizing: <pre><p>Thanks for the idea, I will do this next time I write queries!</p></pre>kd7nyq: <pre><p>I took another route. Instead of trying to make SQL code simple, I conceded that SQL code will probably always be plentiful and repetitive. I also don&#39;t like to use ORMs because I appreciate transparency and I want to optimize SQL as necessary.</p> <p>The solution I came up with is an SQL code generator. It generates the database schema and SQL bindings (SELECT, UPDATE, INSERT, DELETE) based on struct definitions. It&#39;s not a beautiful solution that requires zero work, but it saves a boat load of time.</p> <p><a href="https://github.com/jackmanlabs/codegen" rel="nofollow">https://github.com/jackmanlabs/codegen</a></p> <p>If you have questions about using it, let me know. I&#39;m happy to get feedback and make improvements. Most of the development has been defined by what I need <strong>right now</strong>.</p></pre>pgaleone: <pre><p>If you use PostgreSQL you can use a library I wrote: <a href="https://github.com/galeone/igor" rel="nofollow">https://github.com/galeone/igor</a></p></pre>vagmi: <pre><p>If your queries are complicated, I would suggest something like <a href="https://github.com/nleof/goyesql" rel="nofollow">goyesql</a> along with something like <a href="https://github.com/jmoiron/sqlx" rel="nofollow">sqlx</a>. This lets SQL live in its own file rather than as strings in a <code>.go</code>file. This helps with syntax highlighting and formatting SQL the way you would want it. Also, if you are using PostgreSQL, I cannot recommend <a href="https://github.com/mgutz/dat" rel="nofollow">dat</a> enough. It supports a nice query builder DSL that allows you to have query fragments and make progressive modifications on the base query. </p></pre>koalefant: <pre><p>If you&#39;re going to pass this data straight to the client, sometimes I just json_agg the result of the query, scan into a string var and pass that back.</p></pre>knq: <pre><p>We have a tool published, <a href="https://github.com/knq/xo" rel="nofollow">xo</a> that makes this workflow a breeze. You can write your own Go templates that can then be used with the types from your database. Alternatively, you can write your queries in something like <code>.sh</code> script, and then have <code>xo</code> generate the wrapper code around it (again, using custom templates if you&#39;d like).</p> <p>Also, if you&#39;re doing any kind of database work, check out <a href="https://github.com/knq/usql" rel="nofollow">usql</a>.</p> <p>Cheers, and good luck!</p></pre>brianketelsen: <pre><p>Check out the way go-micro does it: <a href="https://github.com/micro/user-srv/blob/master/db/db.go" rel="nofollow">https://github.com/micro/user-srv/blob/master/db/db.go</a></p></pre>peterbourgon: <pre><p>

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

699 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传