2015-08-12 3 views
1

Я пытаюсь собрать код SQL, который может распространять определенное количество среди ведер с ограниченной емкостью для этого количества в определенном порядке, но в отдельных транзакциях. См. Ниже, например.Выделение величин для разных ковшей с использованием SQL

У меня есть 2 ведра, каждый с определенной мощности (целочисленные):

bucket  capacity 
1   100 
2   50 

Я некоторые величины, которые будут bucketed в ряде сделок:

transaction quantity 
1    50 
2    60 
3    20 
4    40 

Я хочу следующий результат после запуска кода SQL, который должен содержать номер транзакции и рассказать мне, сколько каждого ведра могло удерживать это количество. Ковши должны быть заполнены в порядке убывания числа ковшей, а в порядке убывания количества сделок:

transaction quantity_bucketed bucket_id overage 
1    50     1   0 
2    50     1   0 
2    10     2   0 
3    20     2   0 
4    20     2   20 

Если нет больше ведра, и есть еще некоторое количество, чтобы быть bucketed, он должен идти к " overage ", как в приведенном выше примере.

ответ

1
DROP TABLE IF EXISTS buckets; 
CREATE TABLE buckets (
    bucket_id bigserial primary key, 
    capacity integer); 

-- something to put in the buckets 
DROP TABLE IF EXISTS transactions; 
CREATE TABLE transactions (
    transaction_id bigserial primary key, 
    quantity integer); 

-- create 2 buckets with different capacities 
INSERT INTO buckets (capacity) 
VALUES (100),(50); 

-- create some traffic to put in the buckets 
INSERT INTO transactions (quantity) 
VALUES (50),(60),(20),(40); 


WITH buckets AS (
    -- expand buckets (create a row per bucket capacity) 
    SELECT row_number() OVER() bucket_row_id, * 
    FROM (
     -- slot = a unit of capacity 
     SELECT *, generate_series(1,b.capacity) slot 
     FROM buckets b 
    ) useless_alias 
), xact AS (
    -- expand transactions, creating an id per unit of quantity 
    SELECT row_number() OVER() unit_row_id, * 
    FROM (
     -- an item per transaction quantity 
     SELECT *, generate_series(1,t.quantity) unit 
     FROM transactions t 
    ) useless_alias 
), filled AS (
    -- join buckets to transactions on slots=units 
    -- slots with no units = wasted bucket capacity 
    -- units with no slots = overage 
    SELECT b.*, x.* 
    FROM xact x 
    FULL JOIN buckets b 
    ON b.bucket_row_id = x.unit_row_id 
) 
-- finally, do the do 
SELECT transaction_id, CASE WHEN bucket_id IS NULL THEN 'overage' ELSE bucket_id::text END bucket_id , count(unit_row_id) quantity_bucketed 
FROM filled 
GROUP BY 1,2 
ORDER BY 1,2 

Предостережение: я не пытался сделать лишнюю колонку из «чрезмерного». При заполнении ведер, это не имеет никакого отношения к тому, что ведро переполняет не вписывается в. В примерном случае только 1 транзакция имела избыток. Я предполагаю, что в случае использования транзакций вы действительно хотите, чтобы количество транзакций не было в таблице.

+0

Не могли бы вы немного объяснить, что делает каждая часть SQL? А именно 'with',' over', 'xact',' slot', 'unit' и' generate_series'. – veggie1

+0

Итак, я получил эту работу для своего приложения, однако она очень медленно подходит для любых «больших» количеств (например, для создания большого количества строк потребуется миллионы строк). Есть ли способ, которым вы можете думать о битве с оптимизировать этот запрос? – veggie1

+0

@ veggie1 ... только если чрезмерная или потерянная емкость очень однобокая, и вы можете удалить много строк, выполнив LEFT или RIGHT join, а затем просто подсчитав оставшиеся строки. Я не собираюсь изменять пример для этого, потому что это просто сделало бы его очень трудным для чтения, чтобы разрешить угловой случай. –