5

日付と日付を持つSessionモデルがあり、どちらも としてデータベースに保存されています。私は現在、巨大なテーブルに一連の結果を吐き出し、ユーザーがスコープを使用して単一の日付とオプションの時間範囲で結果をフィルタリングできるようにしています。:created_at:start_time:time

class Session < ActiveRecord::Base
  ...

  scope :filter_by_date, lambda { |date|
    date = date.split(",")[0]
    where(:created_at =>
      DateTime.strptime(date, '%m/%d/%Y')..DateTime.strptime(date, '%m/%d/%Y').end_of_day
    )
  }
  scope :filter_by_time, lambda { |date, time|
    to = time[:to]
    from = time[:from]
    where(:start_time =>
      DateTime.strptime("#{date} #{from[:digits]} #{from[:meridian]}", '%m/%d/%Y %r')..
      DateTime.strptime("#{date} #{to[:digits]} #{to[:meridian]}", '%m/%d/%Y %r')
    )
  }

end

コントローラーは多かれ少なかれ次のようになります。

class SessionController < ApplicationController

  def index
    if params.include?(:date) ||
       params.include?(:time) &&
     ( params[:time][:from][:digits].present? && params[:time][:to][:digits].present? )

      i = Session.scoped
      i = i.filter_by_date(params[:date]) unless params[:date].blank?
      i = i.filter_by_time(params[:date], params[:time]) unless params[:time].blank? || params[:time][:from][:digits].blank? || params[:time][:to][:digits].blank?

      @items = i
      @items.sort_by! &params[:sort].to_sym if params[:sort].present?
    else
      @items = Session.find(:all, :order => :created_at)
    end
  end

end

ユーザーが複数の日付を使用して結果をフィルタリングできるようにする必要があります。パラメータを文字列形式のコンマ区切りリストとして受け取っています。たとえば"07/12/2012,07/13/2012,07/17/2012"、 、いくつかの異なる日付範囲、およびそれらの日付範囲内の時間範囲についてデータベースにクエリを実行し、それらの結果をマージできる必要があるため、たとえばすべて7/12、7/13、7/17 のセッションのうち、午後 6 時 30 分~午後 7 時 30 分の間。

私はどこでも探していて、いくつかの異なることを試しましたが、実際にこれを行う方法がわかりません。これはスコープを使用して可能ですか? そうでない場合、これを行う最良の方法は何ですか?

私の最も近い推測は次のようになりますが、何も返されないので、間違っていることはわかっています。

scope :filter_by_date, lambda { |date|
  date = date.split(",")
  date.each do |i|
    where(:created_at =>
      DateTime.strptime(i, '%m/%d/%Y')..DateTime.strptime(i, '%m/%d/%Y').end_of_day
    )
  end
}
scope :filter_by_time, lambda { |date, time|
  date = date.split(",")
  to = time[:to]
  from = time[:from]
  date.each do |i|
    where(:start_time =>
      DateTime.strptime("#{i} #{from[:digits]} #{from[:meridian]}", '%m/%d/%Y %r')..
      DateTime.strptime("#{i} #{to[:digits]} #{to[:meridian]}", '%m/%d/%Y %r')
    )
  end
}

もう 1 つの複雑な点は、開始時刻がすべて DateTime オブジェクトとして格納されているため、既に固定の日付が含まれていることです。そのため、任意の日付の午後 6 時 30 分から午後 7 時 30 分の間に開始されたすべてのセッションを返したい場合は、別の方法を見つける必要があります。それも。サード パーティがデータの責任を負うため、データの構造や保存方法を変更することはできません。これらすべての複雑なクエリを実行する方法を理解する必要があるだけです。助けてください!


編集:

以下の Kenichi と Chuck Vose のアドバイスを組み合わせて、私が思いついた解決策を次に示します。

scope :filter_by_date, lambda { |dates|
  clauses = []
  args = []
  dates.split(',').each do |date|
    m, d, y = date.split '/'
    b = "#{y}-#{m}-#{d} 00:00:00"
    e = "#{y}-#{m}-#{d} 23:59:59"
    clauses << '(created_at >= ? AND created_at <= ?)'
    args.push b, e
  end
  where clauses.join(' OR '), *args
}

scope :filter_by_time, lambda { |times|
  args = []
  [times[:from], times[:to]].each do |time|
    h, m, s = time[:digits].split(':')
    h = (h.to_i + 12).to_s if time[:meridian] == 'pm'
    h = '0' + h if h.length == 1
    s = '00' if s.nil?
    args.push "#{h}:#{m}:#{s}"
  end
  where("CAST(start_time AS TIME) >= ? AND
         CAST(start_time AS TIME) <= ?", *args)
}

このソリューションを使用すると、複数の連続しない日付からセッションを返すか、日付にまったく依存せずに時間範囲内の任意のセッションを返すか、2 つのスコープを組み合わせて、それらの日付内の連続しない日付と時刻でフィルター処理できます。わーい!

私が見落としていた重要な点は、whereステートメントが最後に来なければならないということです。お二方、ご協力ありがとうございました!私は今より賢く感じます。

4

2 に答える 2

4

何かのようなもの:

scope :filter_by_date, lambda { |dates|
  clauses = []
  args = []
  dates.split(',').each do |date|
    m, d, y = date.split '/'
    b = "#{y}-#{m}-#{d} 00:00:00"
    e = "#{y}-#{m}-#{d} 23:59:59"
    clauses << '(start_time >= ? AND start_time <= ?)'
    args.push b, e
  end
  where clauses.join(' OR '), *args
}

scope :filter_by_time, lambda { |dates, time|
  clauses = []
  args = []
  dates.split(',').each do |date|
    m, d, y = date.split '/'
    f = time[:from] # convert to '%H:%M:%S'
    t = time[:to]   # again, same
    b = "#{y}-#{m}-#{d} #{f}"
    e = "#{y}-#{m}-#{d} #{t}"
    clauses << '(start_time >= ? AND start_time <= ?)'
    args.push b, e
  end
  where clauses.join(' OR '), *args
}
于 2012-08-01T18:04:40.347 に答える
1

したがって、質問の簡単な部分は、日時をどうするかということです。DateTimes の良いところは、次のように簡単に時間にキャストできることです。

CAST(datetime_col AS TIME)

したがって、次のようなことができます。

i.where("CAST(start_time AS TIME) IN(?)", times.join(", "))

さて、難しいのは、なぜ結果が得られないのかということです。最初に試すことは、 を使用i.to_sqlして、範囲指定されたクエリが適切に見えるかどうかを判断することです。私の推測では、それを印刷すると、すべての場所が AND で連結されていることがわかると思います。したがって、日付が 7/12、7/13、および 7/21 のオブジェクトを求めています。

ここでの最後の部分は、SQL インジェクションと過度に熱心な strptime という 2 つの懸念事項があることです。

#{}クエリで絶対に使用してはならない場所を実行する場合。その入力が同僚からどこから来ているかを知っていても、知らないかもしれません。したがって、?上記のように使用していることを確認してください。

次に、どの言語でも strptime は非常にコストがかかります。あなたはこれを知っているべきではありませんが、そうです。可能な限り日付の解析を避けてください。この場合、その日付で gsub / into - するだけで、すべてがうまくいきます。とにかく、MySQL は m/d/y 形式の日付を想定しています。それでもまだ問題があり、DateTime オブジェクトが本当に必要な場合はDate.new(2001,2,3)、CPU を消費することなく、同じように簡単に行うことができます。

于 2012-08-01T17:07:53.713 に答える