Группировка элементов, созданных за определенное время друг от друга - PullRequest
0 голосов
/ 01 октября 2011

У меня есть набор продуктов (500 тыс. Или около того) в базе данных, которые были созданы за последние несколько лет, и я хотел бы сгруппировать их (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.

1 Ответ

2 голосов
/ 01 октября 2011

Это трудно сделать в чистом 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;

Вызовите один раз, затем отбросьте:

SELECT f_p_group();

DROP FUNCTION f_p_group();

Теперь все участникигруппы согласно вашему определению доля group_id.

Редактировать после редактирования вопроса

Я добавил еще пару вещей:

  • Считать таблицу во временную таблицу (упорядочение в процессе), сделать всеобновите там, обрежьте исходную таблицу, добавьте group_id и запишите обновленные строки из временной таблицы за один раз.Должно быть намного быстрее и не нуждаться в вакууме впоследствии.Но вам нужно немного оперативной памяти для этого
  • for_sale, игнорируемого в запросе, после того как его больше нет в вопросе.
  • Подробнее о % ROWTYPE .
  • Прочтите здесь о work_mem и temp_buffers .
  • TRUNCATE, ANALYZE, TEMP TABLE, ALTER TABLE, ... все в прекрасном руководстве
  • Я проверил это с pg 9.0.должен работать в 8.4 - 9.0 и, возможно, более старых версиях.
...