0

過去数年間に作成されたデータベースに多数の製品 (500k 程度) があり、それらをグループ化したいと考えています (Rails 2.3.14)

理想的には、次の場合に同じグループと見なされます。

  1. それらは同じ company_id によって作成されました
  2. それらは互いに10分以内に作成されました

私が達成しようとしていることの大まかなパス:

def self.package_products
  Company.each do |company|
   package = Package.new
   products = Product.find(:all, :conditions => [:company_id = company && created_around_similar_times])
   package.contents = first_few_product_descriptions
   package.save!
   products.update_all(:package_id => package.id)
 end
end

私には臭いですが。私は会社を巡回するのが好きではなく、それを行うためのより良い方法があると思わずにはいられません。同様のアイテムをグループ化できるsql-fuを持っている人はいますか? 基本的に、互いに10分以内に作成された同じ会社の製品を見つけて、同じpackage_idを割り当てます。

4

1 に答える 1

2

これは、純粋な SQL では困難です。私はplpgsqlプロシージャに頼ります。
あなたのテーブルは次のようになっているとしましょ
う:

create table p (
  id serial primary key     -- or whatever your primary key is!
, company_id int4 NOT NULL
, create_time timestamp NOT NULL
, for_sale bool NOT NULL
);

次のような plpgsql 関数を使用します。

CREATE OR REPLACE FUNCTION f_p_group()
  RETURNS void AS
$BODY$
DECLARE
    g_id             integer := 1;
    last_time        timestamp;
    last_company_id  integer;
    r                p%ROWTYPE;
BEGIN

-- If the table is huge, special settings for these parameters will help
SET temp_buffers = '100MB';   -- more RAM for temp table, adjust to actual size of p
SET work_mem = '100MB';       -- more RAM for sorting

-- create temp table just like original.
CREATE TEMP TABLE tmp_p ON COMMIT DROP AS
SELECT * FROM p LIMIT 0;      -- no rows yet

-- add group_id.
ALTER TABLE tmp_p ADD column group_id integer;

-- loop through table, write row + group_id to temp table
FOR r IN
    SELECT *                  -- get the whole row!
      FROM p
--   WHERE for_sale       -- commented out, after it vanished from the question
     ORDER BY company_id, create_time -- group by company_id first, there could be several groups intertwined

LOOP
    IF r.company_id <> last_company_id OR (r.create_time - last_time) > interval '10 min' THEN
        g_id := g_id + 1;
    END IF;

    INSERT INTO tmp_p SELECT r.*, g_id;

    last_time       := r.create_time;
    last_company_id := r.company_id;
END LOOP;

TRUNCATE p;
ALTER TABLE p ADD column group_id integer; -- add group_id now

INSERT INTO p
SELECT * FROM tmp_p;          -- ORDER BY something?

ANALYZE p;                    -- table has been rewritten, no VACUUM is needed.

END;
$BODY$
  LANGUAGE plpgsql;

1 回呼び出してから破棄します。

SELECT f_p_group();

DROP FUNCTION f_p_group();

これで、定義に従ってグループのすべてのメンバーが を共有しgroup_idます。

質問編集後に編集

さらにいくつかのものを入れました:

  • テーブルを一時テーブルに読み取り (プロセスの順序付け)、そこですべての更新を行い、元のテーブルを切り捨てて group_id を追加し、一時テーブルから更新された行を一度に書き込みます。はるかに高速で、後で真空にする必要はありません。ただし、そのためにはRAMが必要です
  • for_sale質問に含まれなくなった後、クエリで無視されます。
  • %ROWTYPEについて読んでください。
  • ここでwork_mem と temp_buffersについて読んでください。
  • TRUNCATE、ANALYZE、TEMP TABLE、ALTER TABLE、...すべて細かいマニュアルに記載
  • pg 9.0でテストしました。8.4 - 9.0 およびおそらく古いバージョンでも動作するはずです。
于 2011-10-01T03:10:32.797 に答える