sequel 5.83.0 → 5.84.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/sequel/adapters/shared/sqlite.rb +3 -1
- data/lib/sequel/database/connecting.rb +1 -1
- data/lib/sequel/database/misc.rb +8 -3
- data/lib/sequel/database/schema_methods.rb +2 -0
- data/lib/sequel/extensions/pg_json_ops.rb +328 -1
- data/lib/sequel/sql.rb +8 -5
- data/lib/sequel/version.rb +1 -1
- metadata +2 -236
- data/CHANGELOG +0 -1393
- data/README.rdoc +0 -936
- data/doc/advanced_associations.rdoc +0 -884
- data/doc/association_basics.rdoc +0 -1859
- data/doc/bin_sequel.rdoc +0 -146
- data/doc/cheat_sheet.rdoc +0 -255
- data/doc/code_order.rdoc +0 -104
- data/doc/core_extensions.rdoc +0 -405
- data/doc/dataset_basics.rdoc +0 -96
- data/doc/dataset_filtering.rdoc +0 -222
- data/doc/extensions.rdoc +0 -77
- data/doc/fork_safety.rdoc +0 -84
- data/doc/mass_assignment.rdoc +0 -98
- data/doc/migration.rdoc +0 -660
- data/doc/model_dataset_method_design.rdoc +0 -129
- data/doc/model_hooks.rdoc +0 -254
- data/doc/model_plugins.rdoc +0 -270
- data/doc/mssql_stored_procedures.rdoc +0 -43
- data/doc/object_model.rdoc +0 -563
- data/doc/opening_databases.rdoc +0 -439
- data/doc/postgresql.rdoc +0 -611
- data/doc/prepared_statements.rdoc +0 -144
- data/doc/querying.rdoc +0 -1070
- data/doc/reflection.rdoc +0 -120
- data/doc/release_notes/5.0.0.txt +0 -159
- data/doc/release_notes/5.1.0.txt +0 -31
- data/doc/release_notes/5.10.0.txt +0 -84
- data/doc/release_notes/5.11.0.txt +0 -83
- data/doc/release_notes/5.12.0.txt +0 -141
- data/doc/release_notes/5.13.0.txt +0 -27
- data/doc/release_notes/5.14.0.txt +0 -63
- data/doc/release_notes/5.15.0.txt +0 -39
- data/doc/release_notes/5.16.0.txt +0 -110
- data/doc/release_notes/5.17.0.txt +0 -31
- data/doc/release_notes/5.18.0.txt +0 -69
- data/doc/release_notes/5.19.0.txt +0 -28
- data/doc/release_notes/5.2.0.txt +0 -33
- data/doc/release_notes/5.20.0.txt +0 -89
- data/doc/release_notes/5.21.0.txt +0 -87
- data/doc/release_notes/5.22.0.txt +0 -48
- data/doc/release_notes/5.23.0.txt +0 -56
- data/doc/release_notes/5.24.0.txt +0 -56
- data/doc/release_notes/5.25.0.txt +0 -32
- data/doc/release_notes/5.26.0.txt +0 -35
- data/doc/release_notes/5.27.0.txt +0 -21
- data/doc/release_notes/5.28.0.txt +0 -16
- data/doc/release_notes/5.29.0.txt +0 -22
- data/doc/release_notes/5.3.0.txt +0 -121
- data/doc/release_notes/5.30.0.txt +0 -20
- data/doc/release_notes/5.31.0.txt +0 -148
- data/doc/release_notes/5.32.0.txt +0 -46
- data/doc/release_notes/5.33.0.txt +0 -24
- data/doc/release_notes/5.34.0.txt +0 -40
- data/doc/release_notes/5.35.0.txt +0 -56
- data/doc/release_notes/5.36.0.txt +0 -60
- data/doc/release_notes/5.37.0.txt +0 -30
- data/doc/release_notes/5.38.0.txt +0 -28
- data/doc/release_notes/5.39.0.txt +0 -19
- data/doc/release_notes/5.4.0.txt +0 -80
- data/doc/release_notes/5.40.0.txt +0 -40
- data/doc/release_notes/5.41.0.txt +0 -25
- data/doc/release_notes/5.42.0.txt +0 -136
- data/doc/release_notes/5.43.0.txt +0 -98
- data/doc/release_notes/5.44.0.txt +0 -32
- data/doc/release_notes/5.45.0.txt +0 -34
- data/doc/release_notes/5.46.0.txt +0 -87
- data/doc/release_notes/5.47.0.txt +0 -59
- data/doc/release_notes/5.48.0.txt +0 -14
- data/doc/release_notes/5.49.0.txt +0 -59
- data/doc/release_notes/5.5.0.txt +0 -61
- data/doc/release_notes/5.50.0.txt +0 -78
- data/doc/release_notes/5.51.0.txt +0 -47
- data/doc/release_notes/5.52.0.txt +0 -87
- data/doc/release_notes/5.53.0.txt +0 -23
- data/doc/release_notes/5.54.0.txt +0 -27
- data/doc/release_notes/5.55.0.txt +0 -21
- data/doc/release_notes/5.56.0.txt +0 -51
- data/doc/release_notes/5.57.0.txt +0 -23
- data/doc/release_notes/5.58.0.txt +0 -31
- data/doc/release_notes/5.59.0.txt +0 -73
- data/doc/release_notes/5.6.0.txt +0 -31
- data/doc/release_notes/5.60.0.txt +0 -22
- data/doc/release_notes/5.61.0.txt +0 -43
- data/doc/release_notes/5.62.0.txt +0 -132
- data/doc/release_notes/5.63.0.txt +0 -33
- data/doc/release_notes/5.64.0.txt +0 -50
- data/doc/release_notes/5.65.0.txt +0 -21
- data/doc/release_notes/5.66.0.txt +0 -24
- data/doc/release_notes/5.67.0.txt +0 -32
- data/doc/release_notes/5.68.0.txt +0 -61
- data/doc/release_notes/5.69.0.txt +0 -26
- data/doc/release_notes/5.7.0.txt +0 -108
- data/doc/release_notes/5.70.0.txt +0 -35
- data/doc/release_notes/5.71.0.txt +0 -21
- data/doc/release_notes/5.72.0.txt +0 -33
- data/doc/release_notes/5.73.0.txt +0 -66
- data/doc/release_notes/5.74.0.txt +0 -45
- data/doc/release_notes/5.75.0.txt +0 -35
- data/doc/release_notes/5.76.0.txt +0 -86
- data/doc/release_notes/5.77.0.txt +0 -63
- data/doc/release_notes/5.78.0.txt +0 -67
- data/doc/release_notes/5.79.0.txt +0 -28
- data/doc/release_notes/5.8.0.txt +0 -170
- data/doc/release_notes/5.80.0.txt +0 -40
- data/doc/release_notes/5.81.0.txt +0 -31
- data/doc/release_notes/5.82.0.txt +0 -61
- data/doc/release_notes/5.83.0.txt +0 -56
- data/doc/release_notes/5.9.0.txt +0 -99
- data/doc/schema_modification.rdoc +0 -679
- data/doc/security.rdoc +0 -443
- data/doc/sharding.rdoc +0 -286
- data/doc/sql.rdoc +0 -648
- data/doc/testing.rdoc +0 -204
- data/doc/thread_safety.rdoc +0 -15
- data/doc/transactions.rdoc +0 -250
- data/doc/validations.rdoc +0 -558
- data/doc/virtual_rows.rdoc +0 -265
data/doc/querying.rdoc
DELETED
@@ -1,1070 +0,0 @@
|
|
1
|
-
= Querying in Sequel
|
2
|
-
|
3
|
-
This guide is based on http://guides.rubyonrails.org/active_record_querying.html
|
4
|
-
|
5
|
-
== Purpose of this Guide
|
6
|
-
|
7
|
-
Sequel is a flexible and powerful database library
|
8
|
-
that supports a wide variety of different querying methods. This guide
|
9
|
-
aims to be a introduction to Sequel's querying support.
|
10
|
-
|
11
|
-
While you can use raw SQL with Sequel, a large part of the
|
12
|
-
advantage you get from using Sequel is Sequel's ability to abstract
|
13
|
-
SQL from you and give you a pure-ruby interface. Sequel also ships with
|
14
|
-
a {core_extensions extension}[rdoc-ref:doc/core_extensions.rdoc],
|
15
|
-
which adds methods to core ruby types to work with Sequel.
|
16
|
-
|
17
|
-
== Retrieving Objects
|
18
|
-
|
19
|
-
Sequel provides a few separate methods for retrieving objects from the
|
20
|
-
database. The underlying method is Sequel::Dataset#each, which yields each
|
21
|
-
row as the Sequel::Database provides it. However, while Dataset#each can and
|
22
|
-
often is used directly, in many cases there is a more convenient retrieval
|
23
|
-
method you can use.
|
24
|
-
|
25
|
-
=== Sequel::Dataset
|
26
|
-
|
27
|
-
If you are new to Sequel and aren't familiar with Sequel, you should probably
|
28
|
-
read the {"Dataset Basics" guide}[rdoc-ref:doc/dataset_basics.rdoc],
|
29
|
-
then come back here.
|
30
|
-
|
31
|
-
=== Retrieving a Single Object
|
32
|
-
|
33
|
-
Sequel offers quite a few ways to to retrieve a single object.
|
34
|
-
|
35
|
-
==== Using a Primary Key [Sequel::Model]
|
36
|
-
|
37
|
-
The <tt>Sequel::Model.[]</tt> is the easiest method to use to find a model instance
|
38
|
-
by its primary key value:
|
39
|
-
|
40
|
-
# Find artist with primary key (id) 1
|
41
|
-
artist = Artist[1]
|
42
|
-
# SELECT * FROM artists WHERE (id = 1)
|
43
|
-
# => #<Artist @values={:name=>"YJM", :id=>1}>
|
44
|
-
|
45
|
-
If there is no record with the given primary key, nil will be returned. If you want
|
46
|
-
to raise an exception if no record is found, you can use <tt>Sequel::Model.with_pk!</tt>:
|
47
|
-
|
48
|
-
artist = Artist.with_pk!(1)
|
49
|
-
|
50
|
-
==== Using +first+
|
51
|
-
|
52
|
-
If you want the first record in the dataset,
|
53
|
-
<tt>Sequel::Dataset#first</tt> is probably the most obvious method to use:
|
54
|
-
|
55
|
-
artist = Artist.first
|
56
|
-
# SELECT * FROM artists LIMIT 1
|
57
|
-
# => #<Artist @values={:name=>"YJM", :id=>1}>
|
58
|
-
|
59
|
-
Any options you pass to +first+ will be used as a filter:
|
60
|
-
|
61
|
-
artist = Artist.first(name: 'YJM')
|
62
|
-
# SELECT * FROM artists WHERE (name = 'YJM') LIMIT 1
|
63
|
-
# => #<Artist @values={:name=>"YJM", :id=>1}>
|
64
|
-
|
65
|
-
artist = Artist.first(Sequel.like(:name, 'Y%'))
|
66
|
-
# SELECT * FROM artists WHERE (name LIKE 'Y%' ESCAPE '\') LIMIT 1
|
67
|
-
# => #<Artist @values={:name=>"YJM", :id=>1}>
|
68
|
-
|
69
|
-
If there is no matching row, +first+ will return nil. If you want to
|
70
|
-
raise an exception instead, use <tt>first!</tt>.
|
71
|
-
|
72
|
-
<tt>Sequel::Dataset#[]</tt> is basically an alias for +first+, except it
|
73
|
-
requires an argument:
|
74
|
-
|
75
|
-
DB[:artists][{name: 'YJM'}]
|
76
|
-
# SELECT * FROM artists WHERE (name = 'YJM') LIMIT 1
|
77
|
-
# => {:name=>"YJM", :id=>1}
|
78
|
-
|
79
|
-
Note that while Model.[] allows you to pass a primary key directly,
|
80
|
-
Dataset#[] does not (unless it is a model dataset).
|
81
|
-
|
82
|
-
==== Using +last+
|
83
|
-
|
84
|
-
If you want the last record in the dataset,
|
85
|
-
<tt>Sequel::Dataset#last</tt> is an obvious method to use. +last+ requires the
|
86
|
-
dataset be ordered, unless the dataset is a model dataset in which case +last+
|
87
|
-
will do a reverse order by the primary key field:
|
88
|
-
|
89
|
-
artist = Artist.last
|
90
|
-
# SELECT * FROM artists ORDER BY id DESC LIMIT 1
|
91
|
-
# => #<Artist @values={:name=>"YJM", :id=>1}>
|
92
|
-
|
93
|
-
Note:
|
94
|
-
|
95
|
-
1. +last+ is equivalent to running a +reverse.first+ query, in other words it reverses the order of the dataset and then calls +first+. This is why +last+ raises a Sequel::Error when there is no order on a plain dataset - because it will provide the same record as +first+, and most users will find that confusing.
|
96
|
-
2. +last+ is not necessarily going to give you the last record in the dataset unless you give the dataset an unambiguous order.
|
97
|
-
3. +last+ will ignore +limit+ if chained together in a query because it sets a limit of 1 if no arguments are given.
|
98
|
-
|
99
|
-
==== Retrieving a Single Column Value
|
100
|
-
|
101
|
-
Sometimes, instead of wanting an entire row, you only want the value of
|
102
|
-
a specific column. For this <tt>Sequel::Dataset#get</tt> is the method
|
103
|
-
you want:
|
104
|
-
|
105
|
-
artist_name = Artist.get(:name)
|
106
|
-
# SELECT name FROM artists LIMIT 1
|
107
|
-
# => "YJM"
|
108
|
-
|
109
|
-
==== Retrieving a Multiple Column Values
|
110
|
-
|
111
|
-
If you want the value for multiple columns, you can pass an array to
|
112
|
-
<tt>Sequel::Dataset#get</tt>:
|
113
|
-
|
114
|
-
artist_id, artist_name = Artist.get([:id, :name])
|
115
|
-
# SELECT id, name FROM artists LIMIT 1
|
116
|
-
# => [1, "YJM"]
|
117
|
-
|
118
|
-
=== Retrieving Multiple Objects
|
119
|
-
|
120
|
-
==== As an Array of Hashes or Model Objects
|
121
|
-
|
122
|
-
In many cases, you want an array of all of the rows associated with the
|
123
|
-
dataset, in which case <tt>Sequel::Dataset#all</tt> is the method you
|
124
|
-
want to use:
|
125
|
-
|
126
|
-
artists = Artist.all
|
127
|
-
# SELECT * FROM artists
|
128
|
-
# => [#<Artist @values={:name=>"YJM", :id=>1}>,
|
129
|
-
# #<Artist @values={:name=>"AS", :id=>2}>]
|
130
|
-
|
131
|
-
==== Using an Enumerable Interface
|
132
|
-
|
133
|
-
<tt>Sequel::Dataset</tt> uses an Enumerable Interface, so it provides a
|
134
|
-
method named each that yields hashes or model objects as they are retrieved
|
135
|
-
from the database:
|
136
|
-
|
137
|
-
Artist.each{|x| p x.name}
|
138
|
-
# SELECT * FROM artists
|
139
|
-
"YJM"
|
140
|
-
"AS"
|
141
|
-
|
142
|
-
This means that all of the methods in the Enumerable module are available,
|
143
|
-
such as +map+:
|
144
|
-
|
145
|
-
artist_names = Artist.map{|x| x.name}
|
146
|
-
# SELECT * FROM artists
|
147
|
-
# => ["YJM", "AS"]
|
148
|
-
|
149
|
-
==== As an Array of Column Values
|
150
|
-
|
151
|
-
Sequel also has an extended +map+ method that takes an argument. If you
|
152
|
-
provide an argument to +map+, it will return an array of values for the
|
153
|
-
given column. So the previous example can be handled more easily with:
|
154
|
-
|
155
|
-
artist_names = Artist.map(:name)
|
156
|
-
# SELECT * FROM artists
|
157
|
-
# => ["YJM", "AS"]
|
158
|
-
|
159
|
-
One difference between these two ways of returning an array of values is
|
160
|
-
that providing +map+ with an argument is really doing:
|
161
|
-
|
162
|
-
artist_names = Artist.map{|x| x[:name]} # not x.name
|
163
|
-
|
164
|
-
Note that regardless of whether you provide +map+ with an argument, it
|
165
|
-
does not modify the columns selected. If you only want to select a
|
166
|
-
single column and return an array of the columns values, you can use
|
167
|
-
+select_map+:
|
168
|
-
|
169
|
-
artist_names = Artist.select_map(:name)
|
170
|
-
# SELECT name FROM artists
|
171
|
-
# => ["YJM", "AS"]
|
172
|
-
|
173
|
-
It's also common to want to order such a map, so Sequel provides a
|
174
|
-
+select_order_map+ method as well:
|
175
|
-
|
176
|
-
artist_names = Artist.select_order_map(:name)
|
177
|
-
# SELECT name FROM artists ORDER BY name
|
178
|
-
# => ["AS", "YJM"]
|
179
|
-
|
180
|
-
In all of these cases, you can provide an array of column symbols and
|
181
|
-
an array of arrays of values will be returned:
|
182
|
-
|
183
|
-
artist_names = Artist.select_map([:id, :name])
|
184
|
-
# SELECT id, name FROM artists
|
185
|
-
# => [[1, "YJM"], [2, "AS"]]
|
186
|
-
|
187
|
-
==== As a Hash
|
188
|
-
|
189
|
-
Sequel makes it easy to take an SQL query and return it as a ruby hash,
|
190
|
-
using the +as_hash+ method:
|
191
|
-
|
192
|
-
artist_names = Artist.as_hash(:id, :name)
|
193
|
-
# SELECT * FROM artists
|
194
|
-
# => {1=>"YJM", 2=>"AS"}
|
195
|
-
|
196
|
-
As you can see, the +as_hash+ method uses the first symbol as the key
|
197
|
-
and the second symbol as the value. So if you swap the two arguments the hash
|
198
|
-
will have its keys and values transposed:
|
199
|
-
|
200
|
-
artist_names = Artist.as_hash(:name, :id)
|
201
|
-
# SELECT * FROM artists
|
202
|
-
# => {"YJM"=>1, "AS"=>2}
|
203
|
-
|
204
|
-
Now what if you have multiple values for the same key? By default, +as_hash+
|
205
|
-
will just have the last matching value. If you care about all matching values,
|
206
|
-
use +to_hash_groups+, which makes the values of the array an array of matching
|
207
|
-
values, in the order they were received:
|
208
|
-
|
209
|
-
artist_names = Artist.to_hash_groups(:name, :id)
|
210
|
-
# SELECT * FROM artists
|
211
|
-
# => {"YJM"=>[1, 10, ...], "AS"=>[2, 20, ...]}
|
212
|
-
|
213
|
-
If you only provide one argument to +as_hash+, it uses the entire hash
|
214
|
-
or model object as the value:
|
215
|
-
|
216
|
-
artist_names = DB[:artists].as_hash(:name)
|
217
|
-
# SELECT * FROM artists
|
218
|
-
# => {"YJM"=>{:id=>1, :name=>"YJM"}, "AS"=>{:id=>2, :name=>"AS"}}
|
219
|
-
|
220
|
-
and +to_hash_groups+ works similarly:
|
221
|
-
|
222
|
-
artist_names = DB[:artists].to_hash_groups(:name)
|
223
|
-
# SELECT * FROM artists
|
224
|
-
# => {"YJM"=>[{:id=>1, :name=>"YJM"}, {:id=>10, :name=>"YJM"}], ...}
|
225
|
-
|
226
|
-
Model datasets have a +as_hash+ method that can be called without any
|
227
|
-
arguments, in which case it will use the primary key as the key and
|
228
|
-
the model object as the value. This can be used to easily create an
|
229
|
-
identity map:
|
230
|
-
|
231
|
-
artist_names = Artist.as_hash
|
232
|
-
# SELECT * FROM artists
|
233
|
-
# => {1=>#<Artist @values={:id=>1, :name=>"YGM"}>,
|
234
|
-
# 2=>#<Artist @values={:id=>2, :name=>"AS"}>}
|
235
|
-
|
236
|
-
There is no equivalent handling to +to_hash_groups+, since there would
|
237
|
-
only be one matching record, as the primary key must be unique.
|
238
|
-
|
239
|
-
Note that +as_hash+ never modifies the columns selected. However, just
|
240
|
-
like Sequel has a +select_map+ method to modify the columns selected and
|
241
|
-
return an array, Sequel also has a +select_hash+ method to modify the
|
242
|
-
columns selected and return a hash:
|
243
|
-
|
244
|
-
artist_names = Artist.select_hash(:name, :id)
|
245
|
-
# SELECT name, id FROM artists
|
246
|
-
# => {"YJM"=>1, "AS"=>2}
|
247
|
-
|
248
|
-
Likewise, +select_hash_groups+ also exists:
|
249
|
-
|
250
|
-
artist_names = Artist.select_hash_groups(:name, :id)
|
251
|
-
# SELECT name, id FROM artists
|
252
|
-
# => {"YJM"=>[1, 10, ...], "AS"=>[2, 20, ...]}
|
253
|
-
|
254
|
-
== Modifying datasets
|
255
|
-
|
256
|
-
Note that the retrieval methods discussed above just return
|
257
|
-
the row(s) included in the existing dataset. In most cases,
|
258
|
-
you aren't interested in every row in a table, but in a subset
|
259
|
-
of the rows, based on some criteria. In Sequel, filtering
|
260
|
-
the dataset is generally done separately than retrieving
|
261
|
-
the records.
|
262
|
-
|
263
|
-
There are really two types of dataset methods that you will
|
264
|
-
be using:
|
265
|
-
|
266
|
-
1. Methods that return row(s), discussed above
|
267
|
-
2. Methods that return modified datasets, discussed below
|
268
|
-
|
269
|
-
Sequel datasets are frozen and use a method chaining, functional style API
|
270
|
-
that returns modified datasets. Let's start with a simple example.
|
271
|
-
|
272
|
-
This is a basic dataset that includes all records in the
|
273
|
-
table +artists+:
|
274
|
-
|
275
|
-
ds1 = DB[:artists]
|
276
|
-
# SELECT * FROM artists
|
277
|
-
|
278
|
-
Let's say we are only interested in the artists whose names
|
279
|
-
start with "A":
|
280
|
-
|
281
|
-
ds2 = ds1.where(Sequel.like(:name, 'A%'))
|
282
|
-
# SELECT * FROM artists WHERE (name LIKE 'A%' ESCAPE '\')
|
283
|
-
|
284
|
-
Here we see that +where+ returns a dataset that adds a +WHERE+
|
285
|
-
clause to the query. It's important to note that +where+ does
|
286
|
-
not modify the receiver:
|
287
|
-
|
288
|
-
ds1
|
289
|
-
# SELECT * FROM artists
|
290
|
-
ds2
|
291
|
-
# SELECT * FROM artists WHERE (name LIKE 'A%' ESCAPE '\')
|
292
|
-
|
293
|
-
In Sequel, dataset methods do not modify the dataset itself, so you can freely use the dataset in multiple
|
294
|
-
places without worrying that its usage in one place will affect its usage
|
295
|
-
in another place. This is what is meant by a functional style API.
|
296
|
-
|
297
|
-
Let's say we only want to select the id and name columns, and that
|
298
|
-
we want to order by name:
|
299
|
-
|
300
|
-
ds3 = ds2.order(:name).select(:id, :name)
|
301
|
-
# SELECT id, name FROM artists WHERE (name LIKE 'A%' ESCAPE '\') ORDER BY name
|
302
|
-
|
303
|
-
Note how you don't need to assign the returned value of order to a variable,
|
304
|
-
and then call select on that. Because order just returns a dataset, you can
|
305
|
-
call select directly on the returned dataset. This is what is meant by a
|
306
|
-
method chaining API.
|
307
|
-
|
308
|
-
Also note how you can call methods that modify different clauses in any order.
|
309
|
-
In this case, the WHERE clause was added first, then the ORDER clause, then the
|
310
|
-
SELECT clause was modified. This makes for a flexible API, where you can modify
|
311
|
-
any part of the query at any time.
|
312
|
-
|
313
|
-
== Filters
|
314
|
-
|
315
|
-
Filtering is probably the most common dataset modifying action done in Sequel.
|
316
|
-
Both the +where+ and +filter+ methods filter the dataset by modifying the
|
317
|
-
dataset's WHERE clause. Both accept a wide variety of input formats, discussed
|
318
|
-
below.
|
319
|
-
|
320
|
-
=== Hashes
|
321
|
-
|
322
|
-
The most common format for providing filters is via a hash. In general, Sequel
|
323
|
-
treats conditions specified with a hash as equality, inclusion, or identity. What type
|
324
|
-
of condition is used depends on the values in the hash.
|
325
|
-
|
326
|
-
Unless Sequel has special support for the value's class, it uses a simple
|
327
|
-
equality statement:
|
328
|
-
|
329
|
-
Artist.where(id: 1)
|
330
|
-
# SELECT * FROM artists WHERE (id = 1)
|
331
|
-
|
332
|
-
Artist.where(name: 'YJM')
|
333
|
-
# SELECT * FROM artists WHERE (name = 'YJM')
|
334
|
-
|
335
|
-
For arrays, Sequel uses the IN operator with a value list:
|
336
|
-
|
337
|
-
Artist.where(id: [1, 2])
|
338
|
-
# SELECT * FROM artists WHERE (id IN (1, 2))
|
339
|
-
|
340
|
-
For datasets, Sequel uses the IN operator with a subselect:
|
341
|
-
|
342
|
-
Artist.where(id: Album.select(:artist_id))
|
343
|
-
# SELECT * FROM artists WHERE (id IN (
|
344
|
-
# SELECT artist_id FROM albums))
|
345
|
-
|
346
|
-
For boolean values such as nil, true, and false, Sequel uses the IS operator:
|
347
|
-
|
348
|
-
Artist.where(id: nil)
|
349
|
-
# SELECT * FROM artists WHERE (id IS NULL)
|
350
|
-
|
351
|
-
For ranges, Sequel uses a pair of inequality statements:
|
352
|
-
|
353
|
-
Artist.where(id: 1..5)
|
354
|
-
# SELECT * FROM artists WHERE ((id >= 1) AND (id <= 5))
|
355
|
-
|
356
|
-
Artist.where(id: 1...5)
|
357
|
-
# SELECT * FROM artists WHERE ((id >= 1) AND (id < 5))
|
358
|
-
|
359
|
-
Finally, for regexps, Sequel uses an SQL regular expression. Note that this
|
360
|
-
is only supported by default on PostgreSQL and MySQL. It can also be supported
|
361
|
-
on SQLite when using the sqlite adapter with the :setup_regexp_function
|
362
|
-
Database option.
|
363
|
-
|
364
|
-
Artist.where(name: /JM$/)
|
365
|
-
# SELECT * FROM artists WHERE (name ~ 'JM$')
|
366
|
-
|
367
|
-
If there are multiple arguments in the hash, the filters are ANDed together:
|
368
|
-
|
369
|
-
Artist.where(id: 1, name: /JM$/)
|
370
|
-
# SELECT * FROM artists WHERE ((id = 1) AND (name ~ 'JM$'))
|
371
|
-
|
372
|
-
This works the same as if you used two separate +where+ calls:
|
373
|
-
|
374
|
-
Artist.where(id: 1).where(name: /JM$/)
|
375
|
-
# SELECT * FROM artists WHERE ((id = 1) AND (name ~ 'JM$'))
|
376
|
-
|
377
|
-
=== Array of Two Element Arrays
|
378
|
-
|
379
|
-
If you use an array of two element arrays, it is treated as a hash. The only
|
380
|
-
advantage to using an array of two element arrays is that it allows you to
|
381
|
-
duplicate keys, so you can do:
|
382
|
-
|
383
|
-
Artist.where([[:name, /JM$/], [:name, /^YJ/]])
|
384
|
-
# SELECT * FROM artists WHERE ((name ~ 'JM$')) AND ((name ~ '^YJ'))
|
385
|
-
|
386
|
-
=== Virtual Row Blocks
|
387
|
-
|
388
|
-
If a block is passed to a filter, it is treated as a virtual row block:
|
389
|
-
|
390
|
-
Artist.where{id > 5}
|
391
|
-
# SELECT * FROM artists WHERE (id > 5)
|
392
|
-
|
393
|
-
You can learn more about virtual row blocks in the {"Virtual Rows" guide}[rdoc-ref:doc/virtual_rows.rdoc].
|
394
|
-
|
395
|
-
You can provide both regular arguments and a block, in which case the results
|
396
|
-
will be ANDed together:
|
397
|
-
|
398
|
-
Artist.where(name: 'A'...'M'){id > 5}
|
399
|
-
# SELECT * FROM artists WHERE ((name >= 'A') AND (name < 'M') AND (id > 5))
|
400
|
-
|
401
|
-
Using virtual row blocks, what you can do with single entry hash or an array with
|
402
|
-
a single two element array can also be done using the =~ method:
|
403
|
-
|
404
|
-
Artist.where{id =~ 5}
|
405
|
-
# SELECT * FROM artists WHERE (id = 5)
|
406
|
-
|
407
|
-
=== Symbols
|
408
|
-
|
409
|
-
If you have a boolean column in the database, and you want only true
|
410
|
-
values, you can just provide the column symbol to filter:
|
411
|
-
|
412
|
-
Artist.where(:retired)
|
413
|
-
# SELECT * FROM artists WHERE retired
|
414
|
-
|
415
|
-
=== SQL::Expression
|
416
|
-
|
417
|
-
Sequel has a DSL that allows easily creating SQL expressions. These SQL
|
418
|
-
expressions are instances of subclasses of Sequel::SQL::Expression. You've
|
419
|
-
already seen an example earlier:
|
420
|
-
|
421
|
-
Artist.where(Sequel.like(:name, 'Y%'))
|
422
|
-
# SELECT * FROM artists WHERE name LIKE 'Y%' ESCAPE '\'
|
423
|
-
|
424
|
-
In this case Sequel.like returns a Sequel::SQL::BooleanExpression object,
|
425
|
-
which is used directly in the filter.
|
426
|
-
|
427
|
-
You can use the DSL to create arbitrarily complex expressions. SQL::Expression
|
428
|
-
objects can be created via singleton methods on the Sequel module. The most common
|
429
|
-
method is Sequel.[], which takes any object and wraps it in a SQL::Expression
|
430
|
-
object. In most cases, the SQL::Expression returned supports the & operator for
|
431
|
-
+AND+, the | operator for +OR+, and the ~ operator for inversion:
|
432
|
-
|
433
|
-
Artist.where(Sequel.like(:name, 'Y%') & (Sequel[{b: 1}] | Sequel.~(c: 3)))
|
434
|
-
# SELECT * FROM artists WHERE ((name LIKE 'Y%' ESCAPE '\') AND ((b = 1) OR (c != 3)))
|
435
|
-
|
436
|
-
You can combine these expression operators with the virtual row support:
|
437
|
-
|
438
|
-
Artist.where{(a > 1) & ~((b(c) < 1) | d)}
|
439
|
-
# SELECT * FROM artists WHERE ((a > 1) AND (b(c) >= 1) AND NOT d)
|
440
|
-
|
441
|
-
Note the use of parentheses when using the & and | operators, as they have lower
|
442
|
-
precedence than other operators. The following will not work:
|
443
|
-
|
444
|
-
Artist.where{a > 1 & ~(b(c) < 1 | d)}
|
445
|
-
# Raises a TypeError
|
446
|
-
|
447
|
-
=== Strings with Placeholders
|
448
|
-
|
449
|
-
Assuming you want to get your hands dirty and use SQL fragments in filters, Sequel allows you
|
450
|
-
to do so if you explicitly mark the strings as literal strings using +Sequel.lit+. You can
|
451
|
-
use placeholders in the string and pass arguments for the placeholders:
|
452
|
-
|
453
|
-
Artist.where(Sequel.lit("name LIKE ?", 'Y%'))
|
454
|
-
# SELECT * FROM artists WHERE (name LIKE 'Y%')
|
455
|
-
|
456
|
-
This is the most common type of placeholder, where each question mark is substituted
|
457
|
-
with the next argument:
|
458
|
-
|
459
|
-
Artist.where(Sequel.lit("name LIKE ? AND id = ?", 'Y%', 5))
|
460
|
-
# SELECT * FROM artists WHERE (name LIKE 'Y%' AND id = 5)
|
461
|
-
|
462
|
-
You can also use named placeholders with a hash, where the named placeholders use
|
463
|
-
colons before the placeholder names:
|
464
|
-
|
465
|
-
Artist.where(Sequel.lit("name LIKE :name AND id = :id", name: 'Y%', id: 5))
|
466
|
-
# SELECT * FROM artists WHERE (name LIKE 'Y%' AND id = 5)
|
467
|
-
|
468
|
-
You don't have to provide any placeholders if you don't want to:
|
469
|
-
|
470
|
-
Artist.where(Sequel.lit("id = 2"))
|
471
|
-
# SELECT * FROM artists WHERE id = 2
|
472
|
-
|
473
|
-
However, if you are using any untrusted input, you should definitely be using placeholders.
|
474
|
-
In general, unless you are hardcoding values in the strings, you should use placeholders.
|
475
|
-
You should never pass a string that has been built using interpolation, unless you are
|
476
|
-
sure of what you are doing.
|
477
|
-
|
478
|
-
Artist.where(Sequel.lit("id = #{params[:id]}")) # Don't do this!
|
479
|
-
Artist.where(Sequel.lit("id = ?", params[:id])) # Do this instead
|
480
|
-
Artist.where(id: params[:id].to_i) # Even better
|
481
|
-
|
482
|
-
=== Inverting
|
483
|
-
|
484
|
-
You may be wondering how to specify a not equals condition in Sequel, or the NOT IN
|
485
|
-
operator. Sequel has generic support for inverting conditions, so to write a not
|
486
|
-
equals condition, you write an equals condition, and invert it:
|
487
|
-
|
488
|
-
Artist.where(id: 5).invert
|
489
|
-
# SELECT * FROM artists WHERE (id != 5)
|
490
|
-
|
491
|
-
Note that +invert+ inverts the entire filter:
|
492
|
-
|
493
|
-
Artist.where(id: 5).where{name > 'A'}.invert
|
494
|
-
# SELECT * FROM artists WHERE ((id != 5) OR (name <= 'A'))
|
495
|
-
|
496
|
-
In general, +invert+ is used rarely, since +exclude+ allows you to invert only specific
|
497
|
-
filters:
|
498
|
-
|
499
|
-
Artist.exclude(id: 5)
|
500
|
-
# SELECT * FROM artists WHERE (id != 5)
|
501
|
-
|
502
|
-
Artist.where(id: 5).exclude{name > 'A'}
|
503
|
-
# SELECT * FROM artists WHERE ((id = 5) AND (name <= 'A')
|
504
|
-
|
505
|
-
So to do a NOT IN with an array:
|
506
|
-
|
507
|
-
Artist.exclude(id: [1, 2])
|
508
|
-
# SELECT * FROM artists WHERE (id NOT IN (1, 2))
|
509
|
-
|
510
|
-
Or to use the NOT LIKE operator:
|
511
|
-
|
512
|
-
Artist.exclude(Sequel.like(:name, '%J%'))
|
513
|
-
# SELECT * FROM artists WHERE (name NOT LIKE '%J%' ESCAPE '\')
|
514
|
-
|
515
|
-
You can use Sequel.~ to negate expressions:
|
516
|
-
|
517
|
-
Artist.where(Sequel.~(id: 5))
|
518
|
-
# SELECT * FROM artists WHERE id != 5
|
519
|
-
|
520
|
-
On Sequel expression objects, you can use ~ to negate them:
|
521
|
-
|
522
|
-
Artist.where(~Sequel.like(:name, '%J%'))
|
523
|
-
# SELECT * FROM artists WHERE (name NOT LIKE '%J%' ESCAPE '\')
|
524
|
-
|
525
|
-
You can use !~ on Sequel expressions to create negated expressions:
|
526
|
-
|
527
|
-
Artist.where{id !~ 5}
|
528
|
-
# SELECT * FROM artists WHERE (id != 5)
|
529
|
-
|
530
|
-
=== Removing
|
531
|
-
|
532
|
-
To remove all existing filters, use +unfiltered+:
|
533
|
-
|
534
|
-
Artist.where(id: 1).unfiltered
|
535
|
-
# SELECT * FROM artists
|
536
|
-
|
537
|
-
== Ordering
|
538
|
-
|
539
|
-
Sequel offers quite a few methods to manipulate the SQL ORDER BY clause. The
|
540
|
-
most basic of these is +order+:
|
541
|
-
|
542
|
-
Artist.order(:id)
|
543
|
-
# SELECT * FROM artists ORDER BY id
|
544
|
-
|
545
|
-
You can specify multiple arguments to order by more than one column:
|
546
|
-
|
547
|
-
Album.order(:artist_id, :id)
|
548
|
-
# SELECT * FROM album ORDER BY artist_id, id
|
549
|
-
|
550
|
-
Note that unlike +where+, +order+ replaces an existing order, it does not
|
551
|
-
append to an existing order:
|
552
|
-
|
553
|
-
Artist.order(:id).order(:name)
|
554
|
-
# SELECT * FROM artists ORDER BY name
|
555
|
-
|
556
|
-
If you want to add a column to the end of the existing order:
|
557
|
-
|
558
|
-
Artist.order(:id).order_append(:name)
|
559
|
-
# SELECT * FROM artists ORDER BY id, name
|
560
|
-
|
561
|
-
If you want to add a column to the beginning of the existing order:
|
562
|
-
|
563
|
-
Artist.order(:id).order_prepend(:name)
|
564
|
-
# SELECT * FROM artists ORDER BY name, id
|
565
|
-
|
566
|
-
=== Reversing
|
567
|
-
|
568
|
-
Just like you can invert an existing filter, you can reverse an existing
|
569
|
-
order, using +reverse+ without an order:
|
570
|
-
|
571
|
-
Artist.order(:id).reverse
|
572
|
-
# SELECT FROM artists ORDER BY id DESC
|
573
|
-
|
574
|
-
Alternatively, you can provide reverse with the order:
|
575
|
-
|
576
|
-
Artist.reverse(:id)
|
577
|
-
# SELECT FROM artists ORDER BY id DESC
|
578
|
-
|
579
|
-
To specify a single entry be reversed, <tt>Sequel.desc</tt> can be used:
|
580
|
-
|
581
|
-
Artist.order(Sequel.desc(:id))
|
582
|
-
# SELECT FROM artists ORDER BY id DESC
|
583
|
-
|
584
|
-
This allows you to easily use both ascending and descending orders:
|
585
|
-
|
586
|
-
Artist.order(:name, Sequel.desc(:id))
|
587
|
-
# SELECT FROM artists ORDER BY name, id DESC
|
588
|
-
|
589
|
-
=== Removing
|
590
|
-
|
591
|
-
Just like you can remove filters with +unfiltered+, you can remove
|
592
|
-
orders with +unordered+:
|
593
|
-
|
594
|
-
Artist.order(:name).unordered
|
595
|
-
# SELECT * FROM artists
|
596
|
-
|
597
|
-
== Selected Columns
|
598
|
-
|
599
|
-
Sequel offers a few methods to manipulate the columns selected. As
|
600
|
-
you may be able to guess, the main method used is +select+:
|
601
|
-
|
602
|
-
Artist.select(:id, :name)
|
603
|
-
# SELECT id, name FROM artists
|
604
|
-
|
605
|
-
You just specify all of the columns that you are selecting as
|
606
|
-
arguments to the method.
|
607
|
-
|
608
|
-
If you are dealing with model objects, you'll want to include the
|
609
|
-
primary key if you want to update or destroy the object. You'll
|
610
|
-
also want to include any keys (primary or foreign) related to
|
611
|
-
associations you plan to use.
|
612
|
-
|
613
|
-
If a column is not selected, and you attempt to access it, you will
|
614
|
-
get nil:
|
615
|
-
|
616
|
-
artist = Artist.select(:name).first
|
617
|
-
# SELECT name FROM artists LIMIT 1
|
618
|
-
|
619
|
-
artist[:id]
|
620
|
-
# => nil
|
621
|
-
|
622
|
-
Like +order+, +select+ replaces the existing selected columns:
|
623
|
-
|
624
|
-
Artist.select(:id).select(:name)
|
625
|
-
# SELECT name FROM artists
|
626
|
-
|
627
|
-
To append to the existing selected columns, use +select_append+:
|
628
|
-
|
629
|
-
Artist.select(:id).select_append(:name)
|
630
|
-
# SELECT id, name FROM artists
|
631
|
-
|
632
|
-
To prepend to the existing selected columns, use +select_prepend+:
|
633
|
-
|
634
|
-
Artist.select(:id).select_prepend(:name)
|
635
|
-
# SELECT name, id FROM artists
|
636
|
-
|
637
|
-
To remove specifically selected columns, and default back to all
|
638
|
-
columns, use +select_all+:
|
639
|
-
|
640
|
-
Artist.select(:id).select_all
|
641
|
-
# SELECT * FROM artists
|
642
|
-
|
643
|
-
To select all columns from a given table, provide an argument to
|
644
|
-
+select_all+:
|
645
|
-
|
646
|
-
Artist.select_all(:artists)
|
647
|
-
# SELECT artists.* FROM artists
|
648
|
-
|
649
|
-
=== Distinct
|
650
|
-
|
651
|
-
To treat duplicate rows as a single row when retrieving the records,
|
652
|
-
use +distinct+:
|
653
|
-
|
654
|
-
Artist.distinct.select(:name)
|
655
|
-
# SELECT DISTINCT name FROM artists
|
656
|
-
|
657
|
-
Note that DISTINCT is a separate SQL clause, it's not a function
|
658
|
-
that you pass to select.
|
659
|
-
|
660
|
-
== Limit and Offset
|
661
|
-
|
662
|
-
You can limit the dataset to a given number of rows using +limit+:
|
663
|
-
|
664
|
-
Artist.limit(5)
|
665
|
-
# SELECT * FROM artists LIMIT 5
|
666
|
-
|
667
|
-
You can provide a second argument to +limit+ to specify an offset:
|
668
|
-
|
669
|
-
Artist.limit(5, 10)
|
670
|
-
# SELECT * FROM artists LIMIT 5 OFFSET 10
|
671
|
-
|
672
|
-
You can also call the +offset+ method separately:
|
673
|
-
|
674
|
-
Artist.limit(5).offset(10)
|
675
|
-
# SELECT * FROM artists LIMIT 5 OFFSET 10
|
676
|
-
|
677
|
-
Either of these would return the 11th through 15th records in the original
|
678
|
-
dataset.
|
679
|
-
|
680
|
-
To remove a limit and offset from a dataset, use +unlimited+:
|
681
|
-
|
682
|
-
Artist.limit(5, 10).unlimited
|
683
|
-
# SELECT * FROM artists
|
684
|
-
|
685
|
-
== Grouping
|
686
|
-
|
687
|
-
The SQL GROUP BY clause is used to combine multiple rows based on
|
688
|
-
the values of a given group of columns.
|
689
|
-
|
690
|
-
To modify the GROUP BY clause of the SQL statement, you use +group+:
|
691
|
-
|
692
|
-
Album.group(:artist_id)
|
693
|
-
# SELECT * FROM albums GROUP BY artist_id
|
694
|
-
|
695
|
-
You can remove an existing grouping using +ungrouped+:
|
696
|
-
|
697
|
-
Album.group(:artist_id).ungrouped
|
698
|
-
# SELECT * FROM albums
|
699
|
-
|
700
|
-
If you want to add a column to the end of the existing grouping columns:
|
701
|
-
|
702
|
-
Album.group(:artist_id).group_append(:name)
|
703
|
-
# SELECT * FROM albums GROUP BY artist_id, name
|
704
|
-
|
705
|
-
A common use of grouping is to count based on the number of grouped rows,
|
706
|
-
and Sequel provides a +group_and_count+ method to make this easier:
|
707
|
-
|
708
|
-
Album.group_and_count(:artist_id)
|
709
|
-
# SELECT artist_id, count(*) AS count FROM albums GROUP BY artist_id
|
710
|
-
|
711
|
-
This will return the number of albums for each artist_id.
|
712
|
-
|
713
|
-
If you want to select and group on the same columns, you can use +select_group+:
|
714
|
-
|
715
|
-
Album.select_group(:artist_id)
|
716
|
-
# SELECT artist_id FROM albums GROUP BY artist_id
|
717
|
-
|
718
|
-
Usually you would add a +select_append+ call after that, to add some sort of
|
719
|
-
aggregation:
|
720
|
-
|
721
|
-
Album.select_group(:artist_id).select_append{sum(num_tracks).as(tracks)}
|
722
|
-
# SELECT artist_id, sum(num_tracks) AS tracks FROM albums GROUP BY artist_id
|
723
|
-
|
724
|
-
== Having
|
725
|
-
|
726
|
-
The SQL HAVING clause is similar to the WHERE clause, except that
|
727
|
-
filters the results after the grouping has been applied, instead of
|
728
|
-
before. One possible use is if you only wanted to return artists
|
729
|
-
who had at least 10 albums:
|
730
|
-
|
731
|
-
Album.group_and_count(:artist_id).having{count.function.* >= 10}
|
732
|
-
# SELECT artist_id, count(*) AS count FROM albums
|
733
|
-
# GROUP BY artist_id HAVING (count(*) >= 10)
|
734
|
-
|
735
|
-
Both the WHERE clause and the HAVING clause are removed by +unfiltered+:
|
736
|
-
|
737
|
-
Album.group_and_count(:artist_id).having{count.function.* >= 10}.
|
738
|
-
where(:name.like('A%')).unfiltered
|
739
|
-
# SELECT artist_id, count(*) AS count FROM albums GROUP BY artist_id
|
740
|
-
|
741
|
-
== Joins
|
742
|
-
|
743
|
-
Sequel has support for many different SQL join types.
|
744
|
-
The underlying method used is +join_table+:
|
745
|
-
|
746
|
-
Album.join_table(:inner, :artists, id: :artist_id)
|
747
|
-
# SELECT * FROM albums
|
748
|
-
# INNER JOIN artists ON (artists.id = albums.artist_id)
|
749
|
-
|
750
|
-
In most cases, you won't call +join_table+ directly, as Sequel provides
|
751
|
-
shortcuts for all common (and most uncommon) join types. For example
|
752
|
-
+join+ does an inner join:
|
753
|
-
|
754
|
-
Album.join(:artists, id: :artist_id)
|
755
|
-
# SELECT * FROM albums
|
756
|
-
# INNER JOIN artists ON (artists.id = albums.artist_id)
|
757
|
-
|
758
|
-
And +left_join+ does a LEFT JOIN:
|
759
|
-
|
760
|
-
Album.left_join(:artists, id: :artist_id)
|
761
|
-
# SELECT * FROM albums
|
762
|
-
# LEFT JOIN artists ON (artists.id = albums.artist_id)
|
763
|
-
|
764
|
-
=== Table/Dataset to Join
|
765
|
-
|
766
|
-
For all of these specialized join methods, the first argument is
|
767
|
-
generally the name of the table to which you are joining. However, you
|
768
|
-
can also provide a dataset, in which case a subselect is used:
|
769
|
-
|
770
|
-
Album.join(Artist.where{name < 'A'}, id: :artist_id)
|
771
|
-
# SELECT * FROM albums
|
772
|
-
# INNER JOIN (SELECT * FROM artists WHERE (name < 'A')) AS t1
|
773
|
-
# ON (t1.id = albums.artist_id)
|
774
|
-
|
775
|
-
=== Join Conditions
|
776
|
-
|
777
|
-
The second argument to the specialized join methods is the conditions
|
778
|
-
to use when joining, which is similar to a filter expression, with
|
779
|
-
a few minor exceptions.
|
780
|
-
|
781
|
-
==== Implicit Qualification
|
782
|
-
|
783
|
-
A hash used as the join conditions operates similarly to a filter,
|
784
|
-
except that unqualified symbol keys are automatically qualified
|
785
|
-
with the table from the first argument, and unqualified symbol values
|
786
|
-
are automatically qualified with the last table joined (or the first
|
787
|
-
table in the dataset if there hasn't been a previous join):
|
788
|
-
|
789
|
-
Album.join(:artists, id: :artist_id)
|
790
|
-
# SELECT * FROM albums
|
791
|
-
# INNER JOIN artists ON (artists.id = albums.artist_id)
|
792
|
-
|
793
|
-
Note how the +id+ symbol is automatically qualified with +artists+,
|
794
|
-
while the +artist_id+ symbol is automatically qualified with +albums+.
|
795
|
-
|
796
|
-
Because Sequel uses the last joined table for implicit qualifications
|
797
|
-
of values, you can do things like:
|
798
|
-
|
799
|
-
Album.join(:artists, id: :artist_id).
|
800
|
-
join(:members, artist_id: :id)
|
801
|
-
# SELECT * FROM albums
|
802
|
-
# INNER JOIN artists ON (artists.id = albums.artist_id)
|
803
|
-
# INNER JOIN members ON (members.artist_id = artists.id)
|
804
|
-
|
805
|
-
Note that when joining to the +members+ table, +artist_id+ is qualified
|
806
|
-
with +members+ and +id+ is qualified with +artists+.
|
807
|
-
|
808
|
-
While a good default, implicit qualification is not always correct:
|
809
|
-
|
810
|
-
Album.join(:artists, id: :artist_id).
|
811
|
-
join(:tracks, album_id: :id)
|
812
|
-
# SELECT * FROM albums
|
813
|
-
# INNER JOIN artists ON (artists.id = albums.artist_id)
|
814
|
-
# INNER JOIN tracks ON (tracks.album_id = artists.id)
|
815
|
-
|
816
|
-
Note here how +id+ is qualified with +artists+ instead of +albums+. This
|
817
|
-
is wrong as the foreign key <tt>tracks.album_id</tt> refers to <tt>albums.id</tt>, not
|
818
|
-
<tt>artists.id</tt>. To fix this, you need to explicitly qualify when joining:
|
819
|
-
|
820
|
-
Album.join(:artists, id: :artist_id).
|
821
|
-
join(:tracks, album_id: Sequel[:albums][:id])
|
822
|
-
# SELECT * FROM albums
|
823
|
-
# INNER JOIN artists ON (artists.id = albums.artist_id)
|
824
|
-
# INNER JOIN tracks ON (tracks.album_id = albums.id)
|
825
|
-
|
826
|
-
Just like in filters, an array of two element arrays is treated the same
|
827
|
-
as a hash, but allows for duplicate keys:
|
828
|
-
|
829
|
-
Album.join(:artists, [[:id, :artist_id], [:id, 1..5]])
|
830
|
-
# SELECT * FROM albums INNER JOIN artists
|
831
|
-
# ON ((artists.id = albums.artist_id)
|
832
|
-
# AND (artists.id >= 1) AND (artists.id <= 5))
|
833
|
-
|
834
|
-
And just like in the hash case, unqualified symbol elements in the
|
835
|
-
array are implicitly qualified.
|
836
|
-
|
837
|
-
By default, Sequel only qualifies unqualified symbols in the conditions. However,
|
838
|
-
You can provide an options hash with a <tt>qualify: :deep</tt> option to do a deep
|
839
|
-
qualification, which can qualify subexpressions. For example, let's say you are doing
|
840
|
-
a JOIN using case insensitive string comparison:
|
841
|
-
|
842
|
-
Album.join(:artists, {Sequel.function(:lower, :name) =>
|
843
|
-
Sequel.function(:lower, :artist_name)},
|
844
|
-
qualify: :deep)
|
845
|
-
# SELECT * FROM albums INNER JOIN artists
|
846
|
-
# ON (lower(artists.name) = lower(albums.artist_name))
|
847
|
-
|
848
|
-
Note how the arguments to lower were qualified correctly in both cases.
|
849
|
-
|
850
|
-
==== USING Joins
|
851
|
-
|
852
|
-
The most common type of join conditions is a JOIN ON, as displayed
|
853
|
-
above. However, the SQL standard allows for join conditions to be
|
854
|
-
specified with JOIN USING, assuming the column name is the same in
|
855
|
-
both tables.
|
856
|
-
|
857
|
-
For example, if instead of having a primary
|
858
|
-
column named +id+ in all of your tables, you use +artist_id+ in your
|
859
|
-
+artists+ table and +album_id+ in your +albums+ table, you could do:
|
860
|
-
|
861
|
-
Album.join(:artists, [:artist_id])
|
862
|
-
# SELECT * FROM albums INNER JOIN artists USING (artist_id)
|
863
|
-
|
864
|
-
See here how you specify the USING columns as an array of symbols.
|
865
|
-
|
866
|
-
==== NATURAL Joins
|
867
|
-
|
868
|
-
NATURAL joins take it one step further than USING joins, by assuming
|
869
|
-
that all columns with the same names in both tables should be
|
870
|
-
used for joining:
|
871
|
-
|
872
|
-
Album.natural_join(:artists)
|
873
|
-
# SELECT * FROM albums NATURAL JOIN artists
|
874
|
-
|
875
|
-
In this case, you don't even need to specify any conditions.
|
876
|
-
|
877
|
-
==== Join Blocks
|
878
|
-
|
879
|
-
You can provide a block to any of the join methods that accept
|
880
|
-
conditions. This block should accept 3 arguments: the table alias
|
881
|
-
for the table currently being joined, the table alias for the last
|
882
|
-
table joined (or first table), and an array of previous
|
883
|
-
<tt>Sequel::SQL::JoinClause</tt>s.
|
884
|
-
|
885
|
-
This allows you to qualify columns similar to how the implicit
|
886
|
-
qualification works, without worrying about the specific aliases
|
887
|
-
being used. For example, let's say you wanted to join the albums
|
888
|
-
and artists tables, but only want albums where the artist's name
|
889
|
-
comes before the album's name.
|
890
|
-
|
891
|
-
Album.join(:artists, id: :artist_id) do |j, lj, js|
|
892
|
-
Sequel[j][:name] < Sequel[lj][:name]
|
893
|
-
end
|
894
|
-
# SELECT * FROM albums INNER JOIN artists
|
895
|
-
# ON ((artists.id = albums.artist_id)
|
896
|
-
# AND (artists.name < albums.name))
|
897
|
-
|
898
|
-
Because greater than can't be expressed with a hash in Sequel, you
|
899
|
-
need to use a block and qualify the tables manually.
|
900
|
-
|
901
|
-
== From
|
902
|
-
|
903
|
-
In general, the FROM table is the first clause populated when creating
|
904
|
-
a dataset. For a standard Sequel::Model, the dataset already has the
|
905
|
-
FROM clause populated, and the most common way to create datasets is
|
906
|
-
with the <tt>Database#[]</tt> method, which populates the FROM clause.
|
907
|
-
|
908
|
-
However, you can modify the tables you are selecting FROM using +from+:
|
909
|
-
|
910
|
-
Album.from(:albums, :old_albums)
|
911
|
-
# SELECT * FROM albums, old_albums
|
912
|
-
|
913
|
-
Be careful with this, as multiple tables in the FROM clause use a cross
|
914
|
-
join by default, so the number of rows will be number of albums times the
|
915
|
-
number of old albums.
|
916
|
-
|
917
|
-
Using multiple FROM tables and setting conditions in the WHERE clause is
|
918
|
-
an old-school way of joining tables:
|
919
|
-
|
920
|
-
DB.from(:albums, :artists).where{{artists[:id]=>albums[:artist_id]}}
|
921
|
-
# SELECT * FROM albums, artists WHERE (artists.id = albums.artist_id)
|
922
|
-
|
923
|
-
=== Using the current dataset in a subselect
|
924
|
-
|
925
|
-
In some cases, you may want to wrap the current dataset in a subselect.
|
926
|
-
Here's an example using +from_self+:
|
927
|
-
|
928
|
-
Album.order(:artist_id).limit(100).from_self.group(:artist_id)
|
929
|
-
# SELECT * FROM (SELECT * FROM albums ORDER BY artist_id LIMIT 100)
|
930
|
-
# AS t1 GROUP BY artist_id
|
931
|
-
|
932
|
-
This is different than without +from_self+:
|
933
|
-
|
934
|
-
Album.order(:artist_id).limit(100).group(:artist_id)
|
935
|
-
# SELECT * FROM albums GROUP BY artist_id ORDER BY name LIMIT 100
|
936
|
-
|
937
|
-
Without +from_self+, you are doing the grouping, and limiting the number
|
938
|
-
of grouped records returned to 100. So assuming you have albums by more
|
939
|
-
than 100 artists, you'll end up with 100 results.
|
940
|
-
|
941
|
-
With +from_self+, you are limiting the number of records before grouping.
|
942
|
-
So if the artist with the lowest id had 100 albums, you'd get 1 result,
|
943
|
-
not 100.
|
944
|
-
|
945
|
-
== Locking for Update
|
946
|
-
|
947
|
-
Sequel allows you to easily add a FOR UPDATE clause to your queries so
|
948
|
-
that the records returned can't be modified by another query until the
|
949
|
-
current transaction commits. You just use the +for_update+ dataset
|
950
|
-
method when returning the rows:
|
951
|
-
|
952
|
-
DB.transaction do
|
953
|
-
album = Album.for_update.first(id: 1)
|
954
|
-
# SELECT * FROM albums WHERE (id = 1) FOR UPDATE
|
955
|
-
album.num_tracks += 1
|
956
|
-
album.save
|
957
|
-
end
|
958
|
-
|
959
|
-
This will ensure that no other connection modifies the row between when you select
|
960
|
-
it and when the transaction ends.
|
961
|
-
|
962
|
-
=== Optimistic Locking
|
963
|
-
|
964
|
-
One of the model plugins that ships with Sequel is an optimistic locking plugin, which provides
|
965
|
-
a database independent way to detect and raise an error if two different connections
|
966
|
-
modify the same row. It's useful for things like web forms where you cannot keep a
|
967
|
-
transaction open while the user is looking at the form, because of the web's
|
968
|
-
stateless nature.
|
969
|
-
|
970
|
-
== Custom SQL
|
971
|
-
|
972
|
-
Sequel makes it easy to use custom SQL for the query by providing it to the <tt>Database#[]</tt>
|
973
|
-
method as a string:
|
974
|
-
|
975
|
-
DB["SELECT * FROM artists"]
|
976
|
-
# SELECT * FROM artists
|
977
|
-
|
978
|
-
You can also use the +with_sql+ dataset method to return a dataset that uses that
|
979
|
-
exact SQL:
|
980
|
-
|
981
|
-
DB[:albums].with_sql("SELECT * FROM artists")
|
982
|
-
# SELECT * FROM artists
|
983
|
-
|
984
|
-
With either of these methods, you can use placeholders:
|
985
|
-
|
986
|
-
DB["SELECT * FROM artists WHERE id = ?", 5]
|
987
|
-
# SELECT * FROM artists WHERE id = 5
|
988
|
-
|
989
|
-
DB[:albums].with_sql("SELECT * FROM artists WHERE id = :id", id: 5)
|
990
|
-
# SELECT * FROM artists WHERE id = 5
|
991
|
-
|
992
|
-
Note that if you specify the dataset using custom SQL, you can still call the dataset
|
993
|
-
modification methods, but in many cases they will appear to have no affect:
|
994
|
-
|
995
|
-
DB["SELECT * FROM artists"].select(:name).order(:id)
|
996
|
-
# SELECT * FROM artists
|
997
|
-
|
998
|
-
You can use the implicit_subquery extension to automatically wrap queries that use
|
999
|
-
custom SQL in subqueries if a method is called that would modify the SQL:
|
1000
|
-
|
1001
|
-
DB.extension :implicit_subquery
|
1002
|
-
DB["SELECT * FROM artists"].select(:name).order(:id)
|
1003
|
-
# SELECT name FROM (SELECT * FROM artists) AS t1 ORDER BY id"
|
1004
|
-
|
1005
|
-
If you must drop down to using custom SQL, it's recommended that you only do so for
|
1006
|
-
specific parts of a query. For example, if the reason you are using custom SQL is
|
1007
|
-
to use a custom operator in the database in the SELECT clause:
|
1008
|
-
|
1009
|
-
DB["SELECT name, (foo !@# ?) AS baz FROM artists", 'bar']
|
1010
|
-
|
1011
|
-
it's better to use Sequel's DSL, and use a literal string for the custom operator:
|
1012
|
-
|
1013
|
-
DB[:artists].select(:name, Sequel.lit("(foo !@# ?)", 'bar').as(:baz))
|
1014
|
-
|
1015
|
-
That way Sequel's method chaining still works, and it increases Sequel's ability to
|
1016
|
-
introspect the code.
|
1017
|
-
|
1018
|
-
== Checking for Records
|
1019
|
-
|
1020
|
-
If you just want to know whether the current dataset would return any rows, use <tt>empty?</tt>:
|
1021
|
-
|
1022
|
-
Album.empty?
|
1023
|
-
# SELECT 1 FROM albums LIMIT 1
|
1024
|
-
# => false
|
1025
|
-
|
1026
|
-
Album.where(id: 0).empty?
|
1027
|
-
# SELECT 1 FROM albums WHERE (id = 0) LIMIT 1
|
1028
|
-
# => true
|
1029
|
-
|
1030
|
-
Album.where(Sequel.like(:name, 'R%')).empty?
|
1031
|
-
# SELECT 1 FROM albums WHERE (name LIKE 'R%' ESCAPE '\') LIMIT 1
|
1032
|
-
# => false
|
1033
|
-
|
1034
|
-
== Aggregate Calculations
|
1035
|
-
|
1036
|
-
The SQL standard defines a few helpful methods to get aggreate information about
|
1037
|
-
datasets, such as +count+, +sum+, +avg+, +min+, and +max+. There are dataset methods
|
1038
|
-
for each of these aggregate functions.
|
1039
|
-
|
1040
|
-
+count+ just returns the number of records in the dataset.
|
1041
|
-
|
1042
|
-
Album.count
|
1043
|
-
# SELECT count(*) AS count FROM albums LIMIT 1
|
1044
|
-
# => 2
|
1045
|
-
|
1046
|
-
If you pass an expression to count, it will return the number of records where
|
1047
|
-
that expression in not NULL:
|
1048
|
-
|
1049
|
-
Album.count(:artist_id)
|
1050
|
-
# SELECT count(artist_id) AS count FROM albums LIMIT 1
|
1051
|
-
# => 1
|
1052
|
-
|
1053
|
-
The other methods take a column argument and call the aggregate function with
|
1054
|
-
the argument:
|
1055
|
-
|
1056
|
-
Album.sum(:id)
|
1057
|
-
# SELECT sum(id) AS sum FROM albums LIMIT 1
|
1058
|
-
# => 3
|
1059
|
-
|
1060
|
-
Album.avg(:id)
|
1061
|
-
# SELECT avg(id) AS avg FROM albums LIMIT 1
|
1062
|
-
# => 1.5
|
1063
|
-
|
1064
|
-
Album.min(:id)
|
1065
|
-
# SELECT min(id) AS min FROM albums LIMIT 1
|
1066
|
-
# => 1
|
1067
|
-
|
1068
|
-
Album.max(:id)
|
1069
|
-
# SELECT max(id) AS max FROM albums LIMIT 1
|
1070
|
-
# => 2
|