1

当我执行find_by_sql查询时,日期时间选择返回 'nil' 作为值:

红宝石:3.0.0p0 轨道:6.1.3.1 postgresql:10

架构:

  create_table "availabilities", force: :cascade do |t|
    t.bigint "timeslot_id", null: false
    t.bigint "option_id", null: false
    t.integer "qty"
    t.integer "stock"
    t.datetime "created_at", precision: 6, null: false
    t.datetime "updated_at", precision: 6, null: false
    t.index ["option_id"], name: "index_availabilities_on_option_id"
    t.index ["timeslot_id"], name: "index_availabilities_on_timeslot_id"
  end

  create_table "locales", force: :cascade do |t|
    t.string "code", null: false
    t.datetime "created_at", precision: 6, null: false
    t.datetime "updated_at", precision: 6, null: false
  end

  create_table "option_translations", force: :cascade do |t|
    t.bigint "option_id", null: false
    t.bigint "locale_id", null: false
    t.string "name"
    t.string "description"
    t.datetime "created_at", precision: 6, null: false
    t.datetime "updated_at", precision: 6, null: false
    t.index ["locale_id"], name: "index_option_translations_on_locale_id"
    t.index ["option_id"], name: "index_option_translations_on_option_id"
  end

  create_table "options", force: :cascade do |t|
    t.bigint "variation_id", null: false
    t.bigint "vat_id", null: false
    t.string "neptis_key", null: false
    t.string "external_id"
    t.decimal "price", precision: 10, scale: 2, null: false
    t.decimal "external_price", precision: 10, scale: 2, null: false
    t.integer "min_sell_qty"
    t.integer "max_sell_qty"
    t.integer "capacity"
    t.datetime "sales_begin_at"
    t.datetime "sales_end_at"
    t.string "duration_type"
    t.string "duration_value"
    t.integer "booking_notice_period"
    t.integer "sort_index"
    t.datetime "created_at", precision: 6, null: false
    t.datetime "updated_at", precision: 6, null: false
    t.index ["neptis_key"], name: "unique_neptis_key", unique: true
    t.index ["variation_id"], name: "index_options_on_variation_id"
    t.index ["vat_id"], name: "index_options_on_vat_id"
  end

  create_table "products", force: :cascade do |t|
    t.bigint "user_ticket_office_id", null: false
    t.string "external_id", null: false
    t.string "external_ref"
    t.string "external_supplier_id", default: ""
    t.float "rating_summary", default: 0.0
    t.integer "reviews_count", default: 0
    t.jsonb "appointment_types", default: []
    t.boolean "need_appointment", default: false
    t.string "duration_type", default: "hour"
    t.jsonb "duration_values", default: []
    t.integer "booking_notice_period", default: 0
    t.jsonb "ticket_opening_times", default: []
    t.jsonb "extra"
    t.datetime "created_at", precision: 6, null: false
    t.datetime "updated_at", precision: 6, null: false
    t.index ["user_ticket_office_id"], name: "index_products_on_user_ticket_office_id"
  end

  create_table "timeslots", force: :cascade do |t|
    t.bigint "variation_id", null: false
    t.datetime "begin_at"
    t.boolean "available"
    t.integer "qty_available"
    t.integer "capacity"
    t.datetime "end_booking_at"
    t.integer "hours_before_booking"
    t.datetime "created_at", precision: 6, null: false
    t.datetime "updated_at", precision: 6, null: false
    t.index ["variation_id"], name: "index_timeslots_on_variation_id"
  end

  create_table "variation_translations", force: :cascade do |t|
    t.bigint "variation_id", null: false
    t.bigint "locale_id", null: false
    t.string "name"
    t.datetime "created_at", precision: 6, null: false
    t.datetime "updated_at", precision: 6, null: false
    t.index ["locale_id"], name: "index_variation_translations_on_locale_id"
    t.index ["variation_id"], name: "index_variation_translations_on_variation_id"
  end

  create_table "variations", force: :cascade do |t|
    t.bigint "product_id", null: false
    t.string "external_id"
    t.date "first_date"
    t.date "last_date"
    t.jsonb "available_dates"
    t.datetime "created_at", precision: 6, null: false
    t.datetime "updated_at", precision: 6, null: false
    t.index ["product_id"], name: "index_variations_on_product_id"
  end

  create_table "vats", force: :cascade do |t|
    t.float "value", null: false
    t.datetime "created_at", precision: 6, null: false
    t.datetime "updated_at", precision: 6, null: false
  end

询问:

module Catalog
  class AllBookingsAvailableQuery

    def self.call( user: , product:, locale: nil, begin_at: nil, end_at: nil)
      query = Catalog::AllBookingsAvailableQuery.new(  user: user, product: product, locale: locale, begin_at: begin_at, end_at: end_at )
      query.exec_sql
    end

    def initialize( user: , product: ,  locale: , begin_at: Time.now, end_at: Time.now + 3.months)
      @locale_id   = locale.id
      user.switch_tenant!
      @user_id     = user.id
      @product_id  = product.id
      @begin_at    = begin_at
      @end_at      = end_at
    end

    def exec_sql
      Availability.find_by_sql [Arel.sql(build_sql), { begin_at: @begin_at, end_at: @end_at} ]
    end

    def build_sql
      <<~SQL
        SELECT 
               (vs.id || '') as lvl0,
               (vs.id || '.' || os.id) as lvl1,
               tims.begin_at as option_event_dt,
               to_char(tims.begin_at, 'HH24' || ':' || 'MI' ) as event_hour,
               to_char(tims.begin_at, 'DD/MM/YYYY' ) as event_date,
               vs.id as variation_id, coalesce(locale_variation_trans.name, first_variation_trans.name) as variation_name,
               os.id as option_id, coalesce(locale_option_trans.name, first_option_trans.name) as option_name,
               os.price, vats.value as vat, os.min_sell_qty, os.max_sell_qty, os.sort_index,
               avs.qty, avs.stock
        FROM "user_#{@user_id}".availabilities avs
            LEFT JOIN "user_#{@user_id}".timeslots tims on tims.id = avs.timeslot_id AND tims.begin_at > :begin_at AND tims.begin_at < :end_at
            LEFT JOIN "user_#{@user_id}".options     os on os.id   = avs.option_id
            LEFT JOIN "public".vats           on os.id   = avs.option_id
            LEFT JOIN "user_#{@user_id}".variations  vs on vs.id   = os.variation_id
            LEFT JOIN LATERAL (
                SELECT vts1.name
                FROM "user_#{@user_id}".variation_translations vts1
                WHERE vts1.variation_id = vs.id AND vts1.locale_id = #{@locale_id}
                LIMIT 1
            ) locale_variation_trans on TRUE
             LEFT JOIN LATERAL (
                 SELECT vts2.name
                 FROM "user_#{@user_id}".variation_translations vts2
                 WHERE vts2.variation_id = vs.id
                 LIMIT 1
                 ) first_variation_trans on TRUE
            LEFT JOIN LATERAL (
                SELECT ots1.name
                FROM "user_#{@user_id}".option_translations ots1
                WHERE ots1.option_id = os.id AND ots1.locale_id = #{@locale_id}
                LIMIT 1
            ) as locale_option_trans on TRUE
             LEFT JOIN LATERAL (
                    SELECT ots2.name
                    FROM "user_#{@user_id}".option_translations ots2
                    WHERE ots2.option_id = os.id
                    LIMIT 1
                 ) as first_option_trans on TRUE
        WHERE  vs.product_id = #{@product_id}
      SQL
    end


  end
end

跑:

u = User.find(1)
p = Product.find(1)
l = locale.find(3)
::Catalog::AllBookingsAvailableQuery.call(  user: u, product: p, locale: l).first.attributes
# => {"lvl0"=>"1", "lvl1"=>"1.1", "option_event_dt"=>nil, "event_hour"=>nil, "event_date"=>nil, "variation_id"=>1, "variation_name"=>"Normal", "option_id"=>1, "option_name"=>"Adulte", "price"=>0.2e2, "vat"=>5.0, "min_sell_qty"=>0, "max_sell_qty"=>0, "sort_index"=>0, "qty"=>350, "stock"=>350, "id"=>nil}

如您所见,与事件日期时间相关的所有属性都是空值。当我在 psql 控制台中运行查询时,我有这些日期时间: 在此处输入图像描述

有谁知道 find_by_sql 和缺少事件的日期时间属性的属性是什么?

编辑 1:@engineersmnky 要求查询(没有 Arel.sql )。不知道是不是租客的副作用...

irb(main):011:0> a = Catalog::AllBooking.call( user: u, product: Product.find(1), locale: Locale.find(3))
  [api_billetteries_development] [user_1]   Product Load (0.4ms)  SELECT "products".* FROM "products" WHERE "products"."id" = $1 LIMIT $2  [["id", 1], ["LIMIT", 1]]
  [api_billetteries_development] [user_1]   Locale Load (0.3ms)  SELECT "public"."locales".* FROM "public"."locales" WHERE "public"."locales"."id" = $1 LIMIT $2  [["id", 3], ["LIMIT", 1]]
  [api_billetteries_development] [user_1]   Availability Load (4.2ms)  SELECT 
       (vs.id || '') as lvl0,
       (vs.id || '.' || os.id) as lvl1,
       tims.begin_at as option_event_dt,
       to_char(tims.begin_at, 'HH24' || ':' || 'MI' ) as event_hour,
       to_char(tims.begin_at, 'DD/MM/YYYY' ) as event_date,
       vs.id as variation_id, coalesce(locale_variation_trans.name, first_variation_trans.name) as variation_name,
       os.id as option_id, coalesce(locale_option_trans.name, first_option_trans.name) as option_name,
       os.price, vats.value as vat, os.min_sell_qty, os.max_sell_qty, os.sort_index,
       avs.qty, avs.stock
FROM "user_1".availabilities avs
    LEFT JOIN "user_1".timeslots tims on tims.id = avs.timeslot_id AND tims.begin_at > NULL AND tims.begin_at < NULL
    LEFT JOIN "user_1".options     os on os.id   = avs.option_id
    LEFT JOIN "public".vats           on os.id   = avs.option_id
    LEFT JOIN "user_1".variations  vs on vs.id   = os.variation_id
    LEFT JOIN LATERAL (
        SELECT vts1.name
        FROM "user_1".variation_translations vts1
        WHERE vts1.variation_id = vs.id AND vts1.locale_id = 3
        LIMIT 1
    ) locale_variation_trans on TRUE
     LEFT JOIN LATERAL (
         SELECT vts2.name
         FROM "user_1".variation_translations vts2
         WHERE vts2.variation_id = vs.id
         LIMIT 1
         ) first_variation_trans on TRUE
    LEFT JOIN LATERAL (
        SELECT ots1.name
        FROM "user_1".option_translations ots1
        WHERE ots1.option_id = os.id AND ots1.locale_id = 3
        LIMIT 1
    ) as locale_option_trans on TRUE
     LEFT JOIN LATERAL (
            SELECT ots2.name
            FROM "user_1".option_translations ots2
            WHERE ots2.option_id = os.id
            LIMIT 1
         ) as first_option_trans on TRUE
WHERE  vs.product_id = 1

=> [#<Availability id: nil, option_id: 1, qty: 350, stock: 350>, #<Availability id: nil, option_id: 1, qty: 350, stock: 350>, #<Availability id: nil, option_id: 1, qty: 350, stock: 350>, #<A...
irb(main):012:0> a[0].option_event_dt
=> nil

编辑 2

我通过执行“传统”查询得到了一个运行结果,lateral_joins inside joins:

module Catalog
  class AllBookingsAvailableQuery

    SELECT_SQL = <<~SQL
            "availabilities".id,
            "availabilities".timeslot_id,
            "availabilities".option_id,
            "variations".id as variation_id,
            "timeslots".begin_at,
            coalesce(locale_variation_trans.name, first_variation_trans.name) as variation_name,
            "options".id as option_id,
            coalesce(locale_option_trans.name, first_option_trans.name) as option_name,
            "options".price,
            "vats".value as option_vat,
            "options".min_sell_qty,
            "options".max_sell_qty,
            "options".sort_index,
            "availabilities".qty,
            "availabilities".stock
    SQL

    def self.call( user: , product:, locale: nil, begin_at: nil, end_at: nil)
      query = Catalog::AllBookingsAvailableQuery.new(  user: user, product: product, locale: locale, begin_at: begin_at, end_at: end_at )
      query.fetch_availabilities
    end

    def initialize( user: , product: ,  locale: , begin_at: Time.now, end_at: Time.now + 2.weeks)
      @locale_id   = locale.id
      user.switch_tenant!
      @user_id     = user.id
      @product_id  = product.id
      @begin_at    = begin_at
      @end_at      = end_at
    end

    def fetch_availabilities
      Availability.joins( :timeslot, option: [ :vat, :option_translations, variation: [:product, :variation_translations]]).
        joins( lat_join_names).
        select( SELECT_SQL ).
        where( options: { variations: { products: { id: @product_id } } }).
        where('"timeslots".begin_at BETWEEN ? AND ?', @begin_at, @end_at).
        order('variation_id, sort_index, begin_at')    end

    def lat_join_names
      <<~SQL
            LEFT JOIN LATERAL (
                SELECT vts1.name
                FROM "user_#{@user_id}".variation_translations vts1
                WHERE vts1.variation_id = "variations".id AND vts1.locale_id = #{@locale_id}
                LIMIT 1
            ) locale_variation_trans on TRUE
             LEFT JOIN LATERAL (
                 SELECT vts2.name
                 FROM "user_#{@user_id}".variation_translations vts2
                 WHERE vts2.variation_id = "variations".id
                 LIMIT 1
                 ) first_variation_trans on TRUE
            LEFT JOIN LATERAL (
                SELECT ots1.name
                FROM "user_#{@user_id}".option_translations ots1
                WHERE ots1.option_id = "options".id AND ots1.locale_id = #{@locale_id}
                LIMIT 1
            ) as locale_option_trans on TRUE
             LEFT JOIN LATERAL (
                    SELECT ots2.name
                    FROM "user_#{@user_id}".option_translations ots2
                    WHERE ots2.option_id = "options".id
                    LIMIT 1
                 ) as first_option_trans on TRUE
      SQL
    end

  end
end
4

1 回答 1

0

好的,我看到了你的问题。begin_at并且end_atnil。仅仅因为您设置了默认值并不意味着这些不能被nil. 例如:

def foo(bar:'baz') 
  bar.inspect
end 
foo
#=> "/"baz/"" 
foo(bar:nil)
#=> "nil" 

您的问题在于self.call哪些设置begins_atends_at默认设置nil,然后传递这些值。

如果您想使用此功能,我将更initialize改为:

 def initialize( user: , product: ,  locale: , begin_at: nil, end_at: nil)
      @locale_id   = locale.id
      user.switch_tenant!
      @user_id     = user.id
      @product_id  = product.id
      @begin_at    = begin_at || Time.now
      @end_at      = end_at || @begin_at + 3.months
    end

这种方式如果begin_atnil它将使用Time.now,如果end_atnil那么它将使用@begin_at + 3.months

于 2021-04-20T18:16:13.830 回答