从 PostgreSQL 9.2 以来, 可以用特定的操作符和函数存储和查询一个值的范围. 这是一个非常不错的特性, 比如在电子商务应用程序方面, 可以用一个字段
price_range
来替代min_price
,max_price
表示一个价格范围.
在Ecto中使用范围数据类型
价格范围的表示方法如下
[0, 45.67]
[30.04, 98.50]
[100, 500]
我们可以用范围操作符来查询一个值的范围
2.5 <@ numrange(1.50,7)
Postgresql 支持如下范围类型
int4range
整数方位int8range
— 大整数范围numrange
— 十进制数字范围(浮点)tsrange
— 不带时区的时间戳范围tstzrange
— 带时区的时间戳范围daterange
— 日期范围
Ecto 本身为了兼容各个数据库, 并没有支持Postgresql 特有的Range数据类型, 但Ecto为我们提供了一个自定义数据类型的方法. 下面我们创建一个 NumRange
类型, 底层用了Postgrex适配器的%Postgrex.Range{}
结构
defmodule App.NumRange do
@behaviour Ecto.Type
def type, do: :numrange
def cast([lower, upper]) do
{:ok, [lower, upper]}
end
def cast(_), do: :error
def load(%Postgrex.Range{lower: lower, upper: nil}) do
{lower, _} = lower |> to_float
{:ok, [lower, nil]}
end
def load(%Postgrex.Range{lower: lower, upper: upper}) do
{lower, _} = lower |> to_float
{upper, _} = upper |> to_float
{:ok, [lower, upper]}
end
def dump([lower, upper]) do
{:ok, %Postgrex.Range{lower: lower, upper: upper, upper_inclusive: false}}
end
def dump(_), do: :error
defp to_float(value) do
value |> Decimal.to_string |> Float.parse
end
end
在模型和迁移脚本中中使用这个自定义数据类型
模型
defmodule App.Product do
use App.Web, :model
schema "products" do
field :price_range, App.NumRange
timestamps
end
end
迁移脚本
defmodule App.Repo.Migrations.Product do
use Ecto.Migration
def change do
create table(:products) do
add :price_range, :numrange
timestamps
end
end
end
范围查询
defmodule App.ProductQuery do
import Ecto.Query
alias App.{Repo, Product}
def within_price_range(price) do
query =
from p in Product,
where: fragment("?::numeric <@ ?", ^price, p.price_range)
query |> Repo.all
end
end
关于方位边界的问题, 和数学中的范围符号是一样的:
[
包括下边界(
不包括下边界]
包括上边界)
不包括上边界
例子
# 创建表
create table products(id int, price_range numrange);
# 插入数据
INSERT INTO products VALUES(1, '[10.0, 100.0]')
INSERT INTO products VALUES(1, '[200.0, 300.0]')
INSERT INTO products VALUES(1, '[400.0, 500.0]')
INSERT INTO products VALUES(1, '[600.0, 700.0]')
# 查询
SELECT * FROM products;
+------+--------------------------------------------------------+
| id | price_range |
|------+--------------------------------------------------------|
| 1 | NumericRange(Decimal('10.0'), Decimal('100.0'), '[]') |
| 1 | NumericRange(Decimal('200.0'), Decimal('300.0'), '[]') |
| 1 | NumericRange(Decimal('400.0'), Decimal('500.0'), '[]') |
| 1 | NumericRange(Decimal('600.0'), Decimal('700.0'), '[]') |
+------+--------------------------------------------------------+
# 过滤
SELECT * FROM products WHERE price_range::numrange @> 99.0
+------+-------------------------------------------------------+
| id | price_range |
|------+-------------------------------------------------------|
| 1 | NumericRange(Decimal('10.0'), Decimal('100.0'), '[]') |
+------+-------------------------------------------------------+
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。