联合查询(Relation、Linked)

72 阅读4分钟

联合查询(Relation、Linked)

  数据表:

  ​stock_group:

indexname
0全部
1持有

  ​stock_info:

codenameboxhold
123456芯片ETF0
234567测试ETF1
345678新能源1

  ​group_stock_relation:

group_namestock_codeindex
全部1234560
全部2345672
持有1234562
全部3456781

  三个表关系为:stock_info​存股票信息,stock_group​存股票分组信息,group_stock_relation​存分组和分组持有的股票信息及其索引。

  ‍

1.Relation

  relation用在两个表之间。

1.1 定义Relation

  relation分为一对一、一对多(比如一个stock_group​对多个group_stock_relation​)、多对多。

  ​stock_info​与group_stock_relation​是一对多,stock_group​与group_stock_relation​也是一对多,所以可以这样定义:


//stock_info.rs
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {
    #[sea_orm(has_many = "super::group_stock_relation::Entity")]
    GroupStockRs,
}
impl Related<super::group_stock_relation::Entity> for Entity {
    fn to() -> RelationDef {
        Relation::GroupStockRs.def()
    }
}

//stock_group.rs
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {
    #[sea_orm(has_many = "super::group_stock_relation::Entity")]
    GroupStockRs,
}
impl Related<super::group_stock_relation::Entity> for Entity {
    fn to() -> RelationDef {
        Relation::GroupStockRs.def()
    }
}

  两个表关系是相互的,定义完一个表关系后要到另一个表定义反向关系。

  定义group_stock_relation​与stock_group​、stock_info​的反向关系:


//group_stock_relation.rs
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {
    #[sea_orm(
        belongs_to = "super::stock_group::Entity",
        from = "Column::GroupName",
        to = "super::stock_group::Column::Name"
    )]
    StockGroups,
    #[sea_orm(
        belongs_to = "super::stock_info::Entity",
        from = "Column::StockCode",
        to = "super::stock_info::Column::Code"
    )]
    StockInfos,
}
impl Related<super::stock_group::Entity> for Entity {
    fn to() -> RelationDef {
        Relation::StockGroups.def()
    }
}
impl Related<super::stock_info::Entity> for Entity {
    fn to() -> RelationDef {
        Relation::StockInfos.def()
    }
}

1.2 使用Relation

  比如我要查询某个股票在哪些分组里面,可以这样查:

    ///根据股票代码查询所有所在的分组(没有该股票和没有任何分组时返回None)
    pub async fn find_groups_by_stock_code(stock_code: String) -> AppResult<Option<Vec<String>>> {
        let db = crate::entities::DB
            .get()
            .ok_or(anyhow::anyhow!("数据库未初始化"))?;
        let option = StockInfos::find_by_id(stock_code).one(db).await?;
        match option {
            None => {
                return Ok(None);
            }
            Some(model) => {
                let groups = model
                    .find_related(GroupStockRs)
                    .all(db)
                    .await?
                    .into_iter()
                    .map(|model| model.group_name)
                    .collect::<Vec<_>>();
                return Ok(Some(groups));
            }
        }
		//当然下面这种也可以
        // let vec = GroupStockRs::find().filter(<group_stock_relation::Entity as sea_orm::EntityTrait>::Column::StockCode.eq(stock_code)).all(db).await?;
        let vec = GroupStockRs::find()
            .select_only()
            .column(Column::GroupName)
            .filter(Column::StockCode.eq(stock_code))
            .into_tuple::<String>()
            .all(db)
            .await?;
        println!("{:?}", vec);
        // let result = GroupStockRs::find_by_stock_code(stock_code).one(db).await?;
        Ok(None)
    }

  ‍

  Relational Select - SeaORM Tutorials (sea-ql.org)

  One to One | SeaORM 🐚 An async & dynamic ORM for Rust (sea-ql.org)

  ‍

2.Linked

  linked用在多个表之间。

  A Linked​ is composed of a chain of relations, and is useful when:

  1. there exist multiple join paths between a pair of entities
  2. joining across multiple entities in a relational query

  比如我有一个分组名,需要查出其中所有的股票:

  注:当时的表不是这样设计的:当时group_stock_relation​存的是分组id而不是name,stock_group​的主键不是name而是一个id、stock_info​不变。

  当时的问题就是,拿着一个分组名需要获得所有的股票信息,即需要根据分组名在stock_group​获得分组id→拿分组id去group_stock_relation​查股票id→拿股票id去stock_info​查股票信息。使用Linked可以很方便的解决。

pub struct GroupToStockInfo;

impl Linked for GroupToStockInfo {
    type FromEntity = StockGroups;
    type ToEntity = StockInfos;

    fn link(&self) -> Vec<LinkDef> {
        vec![
            group_stock_relation::Relation::StockGroups.def().rev(),
            group_stock_relation::Relation::StockInfos.def(),
        ]
    }
}
    ///根据分组名称查询分组下的所有股票(按照索引排序)(但是这样没排)
    pub async fn find_stocks_by_group_name(group_name: String) -> AppResult<i32> {
        let db = crate::entities::DB
            .get()
            .ok_or(anyhow::anyhow!("数据库未初始化"))?;
        let model = StockGroups::find()
            .filter(stock_group::Column::Name.eq(group_name.clone()))
            .one(db)
            .await?
            .ok_or(anyhow!("未找到{}分组", group_name))?;
        let vec = model.find_linked(GroupToStockInfo).all(db).await?;
	    //vec是[Model { code: "123456", name: "芯片ETF", box: None, hold: false }, Model { code: "234567", name: "测试ETF", box: None, hold: true }, Model { code: "345678", name: "新能源", box: None, hold: true }]
        // let x = StockGroups::find().find_also_linked(GroupToStockInfo).all(db).await?;
        // let x = StockGroups::find().find_with_linked(GroupToStockInfo).all(db).await?;
        // let x = StockGroups::find().find_with_linked(GroupToStockInfo).filter(stock_group::Column::Name.eq(group_name.clone())).all(db).await?;
        Ok(1)
    }

  但是遇到一个问题,就是无法按照分组内的索引排序,因为在GroupToStockInfo​中定义了ToEntity = StockInfos​,可能结果只能是纯粹的stock_info​,查了好多也试了很多不知道怎么解,所以使用Custom Join Condition

  Chained Relations | SeaORM 🐚 An async & dynamic ORM for Rust (sea-ql.org)

  ‍

3.Custom Join Condition

  首先明确我们想要的结果:

#[derive(FromQueryResult, Debug)]
struct MoreStockInfo {
    group_name: String,
    index: i32,
    stock_code: String,
    stock_name: String,
    r#box: Option<String>,
}

  构建查询语句(这个其实也是有id时写的):

    ///根据分组名称查询分组下的所有股票(按照索引排序)
    pub async fn find_stocks_by_group_name(group_name: String) -> AppResult<i32> {
        let db = crate::entities::DB
            .get()
            .ok_or(anyhow::anyhow!("数据库未初始化"))?;
        // let model = StockGroups::find()
        //     .filter(stock_group::Column::Name.eq(group_name.clone()))
        //     .one(db)
        //     .await?
        //     .ok_or(anyhow!("未找到{}分组", group_name))?;
        // let stock_infos = model.find_linked(GroupToStockInfo).all(db).await?;
        // println!("{:?}",stock_infos);
        // let x = StockGroups::find().find_also_linked(GroupToStockInfo).all(db).await?;
        // let x = StockGroups::find().find_with_linked(GroupToStockInfo).all(db).await?;
        // let x = StockGroups::find().find_with_linked(GroupToStockInfo).filter(stock_group::Column::Name.eq(group_name.clone())).all(db).await?;
        let more_infos = GroupStockRs::find()
            // .column_as(stock_info::Column::Code, "stock_code") // 假设stock_code来自stock_info表
            // .column_as(stock_group::Column::Name, "group_name")
            // .column_as(group_stock_relation::Column::Index, "index")
            .column_as(stock_info::Column::Name, "stock_name")
            .column_as(stock_info::Column::Box, "box")
            .join(
                JoinType::InnerJoin,
                group_stock_relation::Relation::StockGroups.def(),
            )
            .join(
                JoinType::InnerJoin,
                group_stock_relation::Relation::StockInfos.def(),
            )
            // .join_rev(JoinType::InnerJoin, stock_group::Relation::GroupStockRs.def())  join_rev是反向连接
            // .join_rev(JoinType::InnerJoin, stock_info::Relation::GroupStockRs.def())
            .filter(stock_group::Column::Name.eq(group_name.clone()))
            .order_by_asc(group_stock_relation::Column::Index)
            .into_model::<MoreStockInfo>()
            // .into_tuple::<(String,String,i32,String,Option<String>)>()
            .all(db)
            .await?;
        println!("{:?}", more_infos);
		//[MoreStockInfo { group_name: "全部", index: 0, stock_code: "123456", stock_name: "芯片ETF", box: None }, MoreStockInfo { group_name: "全部", index: 1, stock_code: "345678", stock_name: "新能源", box: None }, MoreStockInfo { group_name: "全部", index: 2, stock_code: "234567", stock_name: "测试ETF", box: None }]
        Ok(1)
    }

  ‍

  ‍