我正在尝试在遗留数据库(使用现有数据和表结构)上的Amber(使用Granite ORM)中构建JOIN查询,并想知道是否可以自定义查询的SELECT FROM部分以支持交叉表JOIN。
以下是名为vehicles
的表的当前表结构:
-----------------------------------------------
| vehicleid | year | makeid | modelid |
-----------------------------------------------
| 1 | 1999 | 54 | 65 |
| 2 | 2000 | 55 | 72 |
| ... | ... | ... | ... |
-----------------------------------------------
等。
makeid
和modelid
是makes
和models
表的外键引用。这些表中的命名列分别为makename
和modelname
。
我正在尝试生成一个JOIN查询以引入名称:
SELECT vehicle.yearid, make.makename AS make, model.modelname AS model FROM vehicles JOIN....
(剪掉JOIN细节)。
因此,当查询返回时,我有一个Vehicle
对象,可以访问:
Vehicle.yearid
,
Vehicle.make
和
Vehicle.model
使用Granite可以吗?
我可以通过使用原始SQL来获取查询的JOIN部分,但我无法弄清楚如何自定义表和& SELECT部分中的列名称。我试过创建一个对象:
class Vehicle < Granite::ORM::Base
adapter pg
primary vehicleid : Int32
field yearid : Int32
field make : String
field model : String
end
但是Granite正在生成以下SQL:
SELECT vehicle.yearid, vehicle.make, vehicle.model FROM vehicle JOIN...
因为vehicle.make
和vehicle.model
实际上不存在而导致错误。
我想要的是这个SQL:
SELECT vehicle.yearid, make.makename AS make, model.modelname AS model FROM vehicles JOIN....
有没有办法让这项工作?
答案 0 :(得分:4)
根据this issue,Granite还没有一对一的关系,但作者提到使用has_many
宏并定义调用方法的方法有一个临时的解决方法由宏定义但返回该方法返回的Array中的第一个元素(因为它只能是一个元素)。
首先,您需要为其他两个表model
和make
创建模型:
class Model < Granite::ORM::Base
adapter pg
belongs_to :vehicle
primary modelid : Int32
field modelname : String
end
class Make < Granite::ORM::Base
adapter pg
belongs_to :vehicle
primary makeid : Int32
field makename : String
end
如果您的字段数不仅仅是modelname
或makename
,请务必添加这些字段。
最后,您需要将has_many
关系添加到原始Vehicle
类,并定义make
和model
方法:
class Vehicle < Granite::ORM::Base
adapter pg
primary vehicleid : Int32
field yearid : Int32
has_many :makes
has_many :models
def make
makes.first["makename"]
end
def model
models.first["modelname"]
end
end
查询就像这样简单:
vehicle = Vehicle.find 2
puts vehicle.model
不幸的是,我不相信Granite在没有完全绕过ORM的情况下支持列别名(AS
),所以你必须明确地返回这些列(上面的代码所做的)或直接用{访问属性{1}}。
注意:我可能已经让Granite返回的Hash类型错了,因为它们的源代码没有任何类型的注释,完全依赖于Crystal的类型推断,这使得很难导航。但我认为这是vehicle.model["modelname"]
,但我可能是错的。如果您收到编译器错误,请尝试使用{} of String => DB::Any
而不是Symbol
。
答案 1 :(得分:4)
感谢@svenskunganka给了我一个思考这条路线的想法,我想出了一个与Granite精神相近的解决方案,它与原始SQL保持接近,让ORM坚持将字段映射到对象。
我在模型定义中添加了一个sql
类方法,该方法与all
几乎完全相同,但剥离了更多结构。我还必须向pg适配器添加一个新的query
方法以支持它,但这现在适用于我的用例。这是猴子修补的代码:
class Granite::Adapter::Pg < Granite::Adapter::Base
def query(statement = "", params = [] of DB::Any, &block)
statement = _ensure_clause_template(statement)
log statement, params
open do |db|
db.query statement, params do |rs|
yield rs
end
end
end
end
module Granite::ORM::Querying
def sql(clause = "", params = [] of DB::Any)
rows = [] of self
@@adapter.query(clause, params) do |results|
results.each do
rows << from_sql(results)
end
end
return rows
end
end
它有点难看(我愿意接受清理的建议)但我现在可以编写以下代码:
vehicles = Vehicle.sql("SELECT vehicle.vehicleid, vehicle.yearid, make.makename AS make,
model.modelname AS model FROM vehicle JOIN ...<snip>")
然后我可以做类似的事情:
vehicles.each do |v|
puts "#{v.yearid} #{v.make} #{v.model}"
end
它按预期工作。