Skip to content

Commit f4f89d4

Browse files
author
Mario L Gutierrez
committed
add DISTINCT ON
1 parent f679e19 commit f4f89d4

File tree

6 files changed

+133
-3
lines changed

6 files changed

+133
-3
lines changed

select.go

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ type SelectBuilder struct {
55
Execer
66

77
isDistinct bool
8+
distinctColumns []string
89
isInterpolated bool
910
columns []string
1011
table string
@@ -34,6 +35,13 @@ func (b *SelectBuilder) Distinct() *SelectBuilder {
3435
return b
3536
}
3637

38+
// DistinctOn sets the columns for DISTINCT ON
39+
func (b *SelectBuilder) DistinctOn(columns ...string) *SelectBuilder {
40+
b.isDistinct = true
41+
b.distinctColumns = columns
42+
return b
43+
}
44+
3745
// From sets the table to SELECT FROM. JOINs may also be defined here.
3846
func (b *SelectBuilder) From(from string) *SelectBuilder {
3947
b.table = from
@@ -118,7 +126,18 @@ func (b *SelectBuilder) ToSQL() (string, []interface{}) {
118126
buf.WriteString("SELECT ")
119127

120128
if b.isDistinct {
121-
buf.WriteString("DISTINCT ")
129+
if len(b.distinctColumns) == 0 {
130+
buf.WriteString("DISTINCT ")
131+
} else {
132+
buf.WriteString("DISTINCT ON (")
133+
for i, s := range b.distinctColumns {
134+
if i > 0 {
135+
buf.WriteString(", ")
136+
}
137+
buf.WriteString(s)
138+
}
139+
buf.WriteString(") ")
140+
}
122141
}
123142

124143
for i, s := range b.columns {

select_doc.go

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,18 @@ func (b *SelectDocBuilder) ToSQL() (string, []interface{}) {
100100
}
101101

102102
if b.isDistinct {
103-
buf.WriteString("DISTINCT ")
103+
if len(b.distinctColumns) == 0 {
104+
buf.WriteString("DISTINCT ")
105+
} else {
106+
buf.WriteString("DISTINCT ON (")
107+
for i, s := range b.distinctColumns {
108+
if i > 0 {
109+
buf.WriteString(", ")
110+
}
111+
buf.WriteString(s)
112+
}
113+
buf.WriteString(") ")
114+
}
104115
}
105116

106117
for i, s := range b.columns {
@@ -224,6 +235,13 @@ func (b *SelectDocBuilder) Distinct() *SelectDocBuilder {
224235
return b
225236
}
226237

238+
// DistinctOn sets the columns for DISTINCT ON
239+
func (b *SelectDocBuilder) DistinctOn(columns ...string) *SelectDocBuilder {
240+
b.isDistinct = true
241+
b.distinctColumns = columns
242+
return b
243+
}
244+
227245
// From sets the table to SELECT FROM
228246
func (b *SelectDocBuilder) From(from string) *SelectDocBuilder {
229247
b.table = from

select_doc_test.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,3 +130,29 @@ func TestDocScopeWhere(t *testing.T) {
130130
assert.Equal(t, stripWS(expected), stripWS(sql))
131131
assert.Exactly(t, args, []interface{}{1, "published"})
132132
}
133+
134+
func TestDocDistinctOn(t *testing.T) {
135+
published := `
136+
INNER JOIN posts p on (p.author_id = u.id)
137+
WHERE
138+
p.state = $1
139+
`
140+
sql, args := SelectDoc("u.*, p.*").
141+
DistinctOn("aa", "bb").
142+
From(`users u`).
143+
Scope(published, "published").
144+
Where(`u.id = $1`, 1).
145+
ToSQL()
146+
expected := `
147+
SELECT row_to_json(dat__item.*)
148+
FROM (
149+
SELECT DISTINCT ON (aa, bb)
150+
u.*, p.*
151+
FROM users u
152+
INNER JOIN posts p on (p.author_id = u.id)
153+
WHERE (u.id = $1) AND ( p.state = $2 )
154+
) as dat__item
155+
`
156+
assert.Equal(t, stripWS(expected), stripWS(sql))
157+
assert.Exactly(t, args, []interface{}{1, "published"})
158+
}

select_test.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,3 +256,22 @@ func TestScopeJoinOnly(t *testing.T) {
256256
assert.Equal(t, "SELECT u.*, p.* FROM users u INNER JOIN posts p on (p.author_id = u.id) WHERE (u.id = $1)", sql)
257257
assert.Exactly(t, args, []interface{}{1})
258258
}
259+
260+
func TestDistinctOn(t *testing.T) {
261+
published := `
262+
INNER JOIN posts p on (p.author_id = u.id)
263+
`
264+
265+
sql, args := Select("u.*, p.*").
266+
DistinctOn("foo", "bar").
267+
From(`users u`).
268+
Scope(published).
269+
Where(`u.id = $1`, 1).
270+
ToSQL()
271+
assert.Equal(t, stripWS(`
272+
SELECT DISTINCT ON (foo, bar) u.*, p.*
273+
FROM users u
274+
INNER JOIN posts p on (p.author_id = u.id)
275+
WHERE (u.id = $1)`), stripWS(sql))
276+
assert.Exactly(t, args, []interface{}{1})
277+
}

sqlx-runner/select_doc_test.go

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,6 @@ func TestSelectDocRowsNil(t *testing.T) {
154154
assert.Equal(sql.ErrNoRows, err)
155155
}
156156

157-
// Not efficient but it's doable
158157
func TestSelectDoc(t *testing.T) {
159158
assert := assert.New(t)
160159

@@ -176,6 +175,28 @@ func TestSelectDoc(t *testing.T) {
176175
assert.Equal(1, person.ID)
177176
}
178177

178+
func TestSelectDocDistinctOn(t *testing.T) {
179+
assert := assert.New(t)
180+
181+
type Person struct {
182+
ID int
183+
Name string
184+
Posts []*Post
185+
}
186+
187+
var person Person
188+
err := testDB.
189+
SelectDoc("id", "name").
190+
DistinctOn("id").
191+
From("people").
192+
Where("id = $1", 1).
193+
QueryStruct(&person)
194+
195+
assert.NoError(err)
196+
assert.Equal("Mario", person.Name)
197+
assert.Equal(1, person.ID)
198+
}
199+
179200
func TestSelectQueryEmbeddedJSON(t *testing.T) {
180201
s := beginTxWithFixtures()
181202
defer s.AutoRollback()

sqlx-runner/select_exec_test.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,33 @@ func TestSelectQueryStruct(t *testing.T) {
105105
assert.Contains(t, err.Error(), "no rows")
106106
}
107107

108+
func TestSelectQueryDistinctOn(t *testing.T) {
109+
s := beginTxWithFixtures()
110+
defer s.AutoRollback()
111+
112+
// Found:
113+
var person Person
114+
err := s.
115+
Select("id", "name", "email").
116+
DistinctOn("id").
117+
From("people").
118+
Where("email = $1", "[email protected]").
119+
QueryStruct(&person)
120+
assert.NoError(t, err)
121+
assert.True(t, person.ID > 0)
122+
assert.Equal(t, person.Name, "John")
123+
assert.True(t, person.Email.Valid)
124+
assert.Equal(t, person.Email.String, "[email protected]")
125+
126+
// Not found:
127+
var person2 Person
128+
err = s.
129+
Select("id", "name", "email").
130+
From("people").Where("email = $1", "[email protected]").
131+
QueryStruct(&person2)
132+
assert.Contains(t, err.Error(), "no rows")
133+
}
134+
108135
func TestSelectBySqlQueryStructs(t *testing.T) {
109136
s := beginTxWithFixtures()
110137
defer s.AutoRollback()

0 commit comments

Comments
 (0)