【问题标题】:Customizing the generated SELECT in a query? [Granite ORM]在查询中自定义生成的 SELECT? [花岗岩ORM]
【发布时间】:2018-04-23 22:28:24
【问题描述】:

我正在尝试在遗留数据库(具有现有数据和表结构)上在 Amber 中构建一个 JOIN 查询(​​使用 Granite ORM),并想知道是否可以自定义查询的 SELECT FROM 部分以支持交叉-table JOIN。

这是名为vehicles 的表的当前表结构:

-----------------------------------------------
| vehicleid | year | makeid | modelid |
-----------------------------------------------
| 1 | 1999 | 54 | 65 |
| 2 | 2000 | 55 | 72 |
| ... | ... | ... | ... |
-----------------------------------------------

等等。

其中makeidmodelid 是对makesmodels 表的外键引用。在这些表中是命名列(分别为makenamemodelname)。

我正在尝试生成一个 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.makevehicle.model 实际上并不存在。

我想要的是这个 SQL:

SELECT vehicle.yearid, make.makename AS make, model.modelname AS model FROM vehicles JOIN....

有没有办法让它工作?

【问题讨论】:

    标签: crystal-lang amber-framework


    【解决方案1】:

    根据this issue,Granite 还没有一对一的关系,但是作者提到有一个临时的解决方法是使用has_many 宏并定义一个调用宏定义的方法的方法但返回该方法返回的 Array 中的第一个元素(因为它只能是一个元素)。

    首先您需要为另外两个表创建模型,modelmake

    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
    

    如果您的字段不止 modelnamemakename,请务必同时添加这些字段。

    最后,您需要将has_many 关系添加到原始Vehicle 类中,并定义makemodel 方法:

    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),因此您必须显式返回这些列(上面的代码就是这样做的)或使用 vehicle.model["modelname"] 直接访问该属性.

    注意:我可能弄错了 Granite 返回的 Hash 类型,因为他们的源代码没有任何类型注释并且完全依赖 Crystal 的类型推断,这使得导航变得困难。但我认为是{} of String =&gt; DB::Any,但我可能是错的。如果您遇到编译器错误,请尝试使用 Symbol 而不是 String

    【讨论】:

    • 这是一个非常有用的答案 - 谢谢!不幸的是,它并没有完全解决这个特殊问题,因为 has_many 和 belongs_to 的当前实现在它们的命名中是固定的(外键必须是 make_id 和 model_id 的形式——带有下划线)。但是我给了它一个赞成票,并感谢您添加此信息,因为我确信它对其他人会有所帮助。我需要在不依赖关系助手的情况下找出解决方案。
    • @nlh 哦,这太糟糕了。由于这似乎是一个极端情况,您不能复制他们的宏实现并进行更改吗?这样的事情应该可以解决它:gist.github.com/Svenskunganka/9458ed05097b5bcbeb0ecdd4ed2acd77
    • 这是个好主意 - 谢谢!当你写这个答案时,我最终采取了一条略有不同(但同样是猴子补丁)的路线。稍后将分享一个单独的答案...
    【解决方案2】:

    感谢@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
    

    它按预期工作。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-11-13
      • 2022-01-13
      • 2021-07-02
      • 1970-01-01
      相关资源
      最近更新 更多