【问题标题】:How to add derived variables to a ResultSet如何将派生变量添加到 ResultSet
【发布时间】:2018-03-02 05:13:34
【问题描述】:

我看到的几乎所有指南/教程都只展示了如何解析数据库中直接可用的列中的值。例如,以下是一种非常常见的模式,我理解它在许多方面的用途:

case class Campaign(id: Int, campaign_mode_id: Int, name: String)

class Application @Inject()(db: Database) extends Controller {

  val campaign = {
    get[Int]("campaign.id") ~
    get[Int]("campaign.campaign_mode_id") ~
    get[String]("campaign.name") map {
      case id ~ campaign_mode_id ~ name  => Campaign(id, campaign_mode_id, name)
    }
  }

  def index = Action {
    val data : List[Campaign] = db.withConnection { implicit connection =>
      SQL("SELECT id, campaign_mode_id, name FROM campaign").as(campaign.*)
    }

    Ok(Json.toJson(data))
  }
} 

它会产生如下所示的结果:

[
    {
        id: 2324,
        campaign_mode_id: 13,
        name: "ABC"
    },
    {
        id: 1324,
        campaign_mode_id: 23,
        name: "ABCD"
    }   
]

现在,如果活动表中有一个额外的日期字段,例如 started_on,它指的是活动开始的时间?或者另一个名为num_followers 的字段是一个整数,表示关注者的数量?

假设我想在运行数据库查询之后和返回我的 JSON 之前进行一些计算。例如,我想包含一个 latest_compaign_date,它引用了最新活动的 started_on 日期。或者说我想包含一个average_num_followers,它指的是所有广告系列的平均关注者数量。

我的最终结果如下:

{
    latest_compaign_date: 12 Dec 2018,
    average_num_followers: 123,
    campaigns: [
        {
            id: 2324,
            campaign_mode_id: 13,
            name: "ABC"
        },
        {
            id: 1324,
            campaign_mode_id: 23,
            name: "ABCD"
        }   
    ]
}

我知道,对于我给出的示例,最好在我的数据库查询中而不是在我的应用程序代码中进行这些计算。但是如果我不得不做一些非常复杂的事情并且出于某种原因想要在我的应用程序代码中做这件事怎么办?我应该如何修改我的 ResutSetParser 以促进这一点?

以下是我尝试过的几种方法:

不要使用 ResultSetParser 而是手动执行所有操作

  case class CampaignData(newestCampaignDate: Long, newestCampaignId: Long, averageNumFollowers: Float, campaigns: Seq[Campaign])

  def aggregater(rows: Seq[Row]): CampaignData = {
    val newestCampaignDate: Long = getNewestDate(rows)
    val newestCampaignId: Long = getNewestCampaign(rows)
    val averageNumFollowers: Float = getAverageNumFollowers(rows)

    val campaigns: Seq[Campaign] = rows.map(row => {
      val rowMap: Map[String, Any] = row.asMap

      Campaign(
        rowMap("campaign.id").asInstanceOf[Int],
        rowMap("campaign.campaign_mode_id") match { case None => 0 case Some(value) => value.asInstanceOf[Int]},
        rowMap("campaign.name") match { case None => "" case Some(value) => value.asInstanceOf[String]}
      )
    })

    CampaignData(newestCampaignDate, newestCampaignId, averageNumFollowers, campaigns)
  }

  def index = Action {
    val data : Seq[Row] = db.withConnection { implicit connection =>
      SQL("SELECT id, campaign_mode_id, name, started_on, num_followers FROM campaign")
    }

    Ok(Json.toJson(aggregater(data)))
  }

这种方法闻起来很糟糕,因为必须使用asInstanceOf 和match 处理每个字段非常乏味并且老实说感觉不安全。而且直觉上,我觉得 Anorm 应该有更好的解决方案,因为我可能不是第一个遇到这个问题的人。

将 ResultSetParser 与另一个函数结合使用

case class Campaign(id: Int, campaign_mode_id: Int, name: String)
case class CampaignData(newestCampaignDate: Long, newestCampaignId: Long, averageNumFollowers: Float, campaigns: Seq[Campaign])

val campaign = {
  get[Int]("campaign.id") ~
  get[Int]("campaign.campaign_mode_id") ~
  get[Int]("campaign.num_followers") ~
  get[Long]("campaign.started_on") ~
  get[String]("campaign.name") map {
    case id ~ campaign_mode_id ~ num_followers ~ started_on ~ name  => Map(
      "id" -> id, 
      "campaign_mode_id" -> campaign_mode_id, 
      "num_followers" -> num_followers,
      "started_on" -> started_on,
      "name" -> name
    )
  }
}

def index = Action {
  val data : Map[String, Any] = db.withConnection { implicit connection =>
    SQL("SELECT id, campaign_mode_id, name, started_on, num_followers FROM campaign").as(campaign.*)
  }

  Ok(Json.toJson(aggregator(data)))
}

def aggregator(data: Map[String, Any]): CampaignData = {
  val newestCampaignDate: Long = getNewestDate(data)
  val newestCampaignId: Long = getNewestCampaign(data)
  val averageNumFollowers: Float = getAverageNumFollowers(data)
  val campaigns: Seq[Campaign] = getCampaigns(data)

  CampaignData(newestCampaignDate, newestCampaignId, averageNumFollowers, campaigns)
}

这种方法更好,因为我不必处理isInstanceOf,但是还有一个更大的问题是必须处理中间的Map。它使所有的 getter 函数(例如getCampaigns)变得更加复杂。我觉得 Anorm 必须提供一些我不知道的开箱即用的更好的东西。

【问题讨论】:

  • 你已经尝试了什么?
  • 嘿,我添加了一些我迄今为止尝试过的方法

标签: scala anorm


【解决方案1】:

正如您在第一个代码 sn-p 中发布的那样,以下代码

def index = Action {
    val data : List[Campaign] = db.withConnection { implicit connection =>
      SQL("SELECT id, campaign_mode_id, name FROM campaign").as(campaign.*)
    }

    Ok(Json.toJson(data))
  }

借助异常提取器返回类型安全的活动列表。

通常,您将使用类似这样的类型安全函数对结果进行预处理

case class CampaignAggregateData(campaigns:List[Campaign], averageNumFollowers:Int, newestCampaignId:Option[Long])

def aggregate(f:List[Campaign]):CampaignAggregatedData

def index = Action {
    val manyCampaign : List[Campaign] = db.withConnection { implicit connection =>
      SQL("SELECT id, campaign_mode_id, name FROM campaign").as(campaign.*)
    }

    val aggregatedData:CampaignAggregateData = aggregate(manyCampaign)

    Ok(Json.toJson(data))
  }

如果您需要由数据库引擎执行聚合,您通常会在单个操作中包含多个 db.withConnection 语句

【讨论】:

    猜你喜欢
    • 2014-09-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-04-06
    • 1970-01-01
    • 2018-06-09
    • 2020-11-22
    相关资源
    最近更新 更多