sequelize.js

How to write ONE sequelize SQL to select all 3 cases including other 2 tables?


There are 3 tables: artwork, sale, trade in sequelizejs 6.x. Both sale and trade belongs to artwork (artwork_id in both sale and trade table). The app needs to select artworks with sale or trade or both. There is no good support of union in sequelize and I have difficulty to come out with a single SQL to select in all 3 cases (artwork with sale, artwork with trade, or artwork with both sale and trade). Here is the sql to select artwork with both sale and trade:

let dataset = await Artwork.findAll(
  include:[
     {model:Sale},
     {model:Trade}. //<<==both sale and trade are required
  ],
);

For selecting artwork with sale (only), the sql may look like:

let dataset = await Artwork.findAll(
  include:[
     {model:Sale, required:true},
     {model:Trade, required:false}. //<<==trade not required
  ],
);

And with trade (only):

 let dataset = await Artwork.findAll(
      include:[
         {model:Sale, required:false},  //<<==sale not required
         {model:Trade, required:true}
      ],
    );

Is there a way above 3 select can be combined into one SQL in sequelize?

UPDATE:

Here is model def:

Artwork.init({
    name: {
        type: DataTypes.STRING,
        validate:{
             len:{
               args:[1,55],
               msg:"max 55 chars",
             },
           },
    },
    author: {
        type:DataTypes.STRING
    },
    category_id:{  //such as paiting, photo, calligraphy
      type:DataTypes.INTEGER, 
    },
    wt_g: {
      type:DataTypes.DECIMAL(10,2),
    },
    production_year: {
      type: DataTypes.STRING,
    },
    dimension: {
        type:DataTypes.STRING,
    },
    uploader_id: {
        type: DataTypes.INTEGER,
        notNull:true,
    },
    description: {
        type: DataTypes.TEXT,
    },
    note: {
      type:DataTypes.TEXT
    },
    tag: {
        type:DataTypes.ARRAY(DataTypes.STRING),
    },
    deleted: {
      type:DataTypes.BOOLEAN,
      defaultValue:false,
    },
    status: { 
      type: DataTypes.STRING,
      defaultValue:"active"
    },
    artwork_data: {
      type: DataTypes.JSONB
    },
    last_updated_by_id: {type: DataTypes.INTEGER},
    createdAt: DataTypes.DATE,
    updatedAt: DataTypes.DATE
  }

Here is forsale:

ForSale.init({
    buyer_id: {
        type:DataTypes.INTEGER
    },
    artwork_id: {
        type:DataTypes.INTEGER,
        allowNull:false,
    },
    status: {
        type:DataTypes.STRING,
    },
    price: {
        type:DataTypes.INTEGER,
        allowNull:false
    },
    shipping_cost: {
        type:DataTypes.INTEGER,
    },
    shipping_method: {
      type:DataTypes.INTEGER, //1-express, 2-air, 3-ground
    },
    transaction_closed:{
      type: DataTypes.BOOLEAN,
      defaultValue: false,
    },
    seller_refund_value:{type: DataTypes.INTEGER},
    buyer_refund_value:{type: DataTypes.INTEGER},
    escrow_value:{type: DataTypes.INTEGER},
    deposit_date:{type: DataTypes.DATE},
    buy_date:{type: DataTypes.DATE},
    dispute_date:{type: DataTypes.DATE},
    active_date:{type: DataTypes.DATE},
    close_date:{type: DataTypes.DATE},
    seller_receive_payment_date:{type: DataTypes.DATE},
    buyer_receive_product_date:{type: DataTypes.DATE},
    forsale_data: {
        type: DataTypes.JSONB,  //buyer_hashaddress, seller_hashaddress, trade_expense ...
    },
    deployed_address: {
      type: DataTypes.STRING
    },
    smartcontract_name: {
      type: DataTypes.STRING,
    },
    last_updated_by_id: {type: DataTypes.INTEGER},
    createdAt: DataTypes.DATE,
    updatedAt: DataTypes.DATE
 }

Here is for trade:

ForTrade.init({
    bidder_id: {
        type:DataTypes.INTEGER
    },
    artwork_id: {
        type:DataTypes.INTEGER,
        allowNull:false,
    },
    status: {
        type:DataTypes.STRING,
    },
    price: {
        type:DataTypes.INTEGER,
        allowNull:false
    },
    shipping_method: {   //1-express, 2-air, 3-ground
        type:DataTypes.INTEGER,
    },
    shipping_cost: {
      type:DataTypes.INTEGER,
    },
    transaction_closed:{
      type: DataTypes.BOOLEAN,
      defaultValue:false,
    },
    poster_refund_value:{type: DataTypes.INTEGER},
    bidder_refund_value:{type: DataTypes.INTEGER},
    escrow_value:{type: DataTypes.INTEGER},  //total deposit is 2*escrow_value
    poster_deposit_date:{type: DataTypes.DATE},
    bidder_deposit_date:{type: DataTypes.DATE},
    bid_date:{type: DataTypes.DATE},
    active_date:{type: DataTypes.DATE},
    poster_receive_product_date:{type: DataTypes.DATE},
    poster_ship_product_date:{type: DataTypes.DATE},
    bidder_receive_product_date:{type: DataTypes.DATE},
    bidder_ship_product_date:{type: DataTypes.DATE},
    close_date:{type: DataTypes.DATE},
    fortrade_data: {
        type: DataTypes.JSONB,  //buyer_hashaddress, seller_hashaddress, trade_expense ...
    },
    deployed_address: {
      type: DataTypes.STRING
    },
    smartcontract_name: {
      type: DataTypes.STRING,
    },
    last_updated_by_id: {type: DataTypes.INTEGER},
    createdAt: DataTypes.DATE,
    updatedAt: DataTypes.DATE
 }

Here is association:

  Artwork.hasMany(ForSale, {foreignKey: 'artwork_id'});
  Artwork.hasMany(ForTrade, {foreignKey: 'artwork_id'});
  ForSale.belongsTo(Artwork, {foreignKey: "artwork_id"});
  ForTrade.belongsTo(Artwork, {foreignKey: "artwork_id"})

Solution

  • You should indicate required: false for both included models and add conditions for them on the root level like this:

    let dataset = await Artwork.findAll(
          include:[
             {model:Sale, required:false},
             {model:Trade, required:false}
          ],
          where: {
           // either sale record exists or trade record exists or both exist.
           [Op.or]: [{
             '$Sale.id$': {
               [Op.ne]: null
             }
           },{
             '$Trade.id$': {
               [Op.ne]: null
             }
           }]
          }
        );