2013-04-29 4 views
4

У меня есть набор таблиц, содержащих недели, продукты, инвентарь и еженедельные прогнозы, из которых я хочу выбрать инвентарь и инвентарь недели X и последние прогнозы. Но я просто не могу получить мои руки вокруг SQL:во внутреннем соединении, как выбрать только одну строку из таблицы справа на основе критериев скольжения?

create table products (
    product_id integer 
); 
create table inventory (
    product_id integer, 
    asof_week integer, 
    qoh float8 
); 
create table forecast (
    product_id integer, 
    for_week integer, 
    asof_week integer, 
    projection float8 
); 
create table weeks (
    wkno integer 
); 
insert into weeks values (4),(5),(6),(7); 
insert into products values(1),(2); 
insert into inventory values(1,5,10),(1,6,20),(2,6,200); 
insert into forecast values(1,4,1,10),(1,4,2,11),(1,4,3,12),(1,4,4,13), 
          (1,5,1,11),(1,5,2,11),(1,5,3,21),(1,5,4,31), 
--corr:one too many  (1,6,1,10),(1,6,2,11),(1,6,3,12),(1,6,4,22),(1,6,5,32),(1,6,5,42),(1,6,6,42), 
          (1,6,1,10),(1,6,2,11),(1,6,3,12),(1,6,4,22),(1,6,5,42),(1,6,6,42), 
          (1,7,1,10),(1,7,6,16), 
          (2,6,5,2000),(2,7,5,2100),(2,8,5,30); 

И запрос:

select p.product_id "product", 
     i.asof_week "inven asof", 
     i.qoh "qoh", 
     f.for_week "fcast for", 
     f.projection "fcast qty", 
     f.asof_week "fcast asof" 
from weeks w, products p 
    left join inventory i on(p.product_id = i.product_id) 
    left join forecast f on(p.product_id = f.product_id) 
where 
    (i.asof_week is null or i.asof_week = w.wkno) 
    and (f.for_week is null or f.for_week = w.wkno) 
    and (f.asof_week is null 
     or f.asof_week = (select max(f2.asof_week) 
          from forecast f2 
          where f2.product_id = f.product_id 
          and f2.for_week = f.for_week)) 
order by p.product_id, i.asof_week, f.for_week, f.asof_week 

К примеру, в течение 4-7 недель, я ищу результирующий:

product week qoh  projection 
1  4  -  13 
1  5  10  31 
1  6  20  42 
1  7  -  16 
2  6  200  2000 
2  7  -  2100 

НО на самом деле я получаю только 3 строки:

product | inven asof | qoh | fcast for | fcast qty | fcast asof 
---------+------------+-----+-----------+-----------+------------ 
     1 |   5 | 10 |   5 |  31 |   4 
     1 |   6 | 20 |   6 |  42 |   6 
     2 |   6 | 200 |   6 |  2000 |   5 
(3 rows) 
Time: 2.531 ms 

Я довольно новичок в SQL и могу использовать некоторые полезные указатели.

Некоторые примечания к данным: у меня есть несколько других таблиц данных, которые я проиллюстрировал на примере, чтобы сосредоточиться на этой проблеме, по крайней мере, один из них похож по своей природе на таблицу прогнозируемых величин (т. Е. С несколькими версиями строки для каждого продукта x неделя). Есть около 100 строк прогноза для каждого продукта X неделя, поэтому где-то мне также придется беспокоиться об эффективности ... но сначала исправьте результаты.

Я нахожусь на postgresql 9.2.

Спасибо.

+0

КСТАТИ: презентация этого вопроса очень полный (возможно, для недостающих PK/FKS за исключением) Молодцы.! – wildplasser

ответ

2

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

Во всяком случае, the following должен работать для вас (хотя я не могу гарантировать, что он будет работать для любых данных, особенно при наличии дублей):

select 
    products.product_id, 
    weeks.wkno, 
    inventory.qoh, 
    max(projection) 
from forecast 
join products on products.product_id = forecast.product_id 
join weeks on weeks.wkno = forecast.for_week 
left join inventory on 
    inventory.product_id = products.product_id 
    and inventory.asof_week = weeks.wkno 
group by 
    products.product_id, 
    weeks.wkno, 
    inventory.qoh 

К сожалению, я не могу дать вам, что много совет. Надеюсь, это поможет.

Редактировать: Измените запрос на удаление креста. Исходная версия here. Вам может потребоваться перекрестное соединение, если вы хотите оставить прогнозы объединения, если некоторые из них отсутствуют. Для вашего конкретного примера это ненужно.

Редактировать 2: Вышеприведенный запрос является семантически неправильным. following верен, но не является иллюстрацией моей точки зрения.

select 
    p.product_id, 
    p.wkno, 
    p.qoh, 
    f.projection 
from 
    (select 
     products.product_id, 
     weeks.wkno, 
     inventory.qoh, 
     max(forecast.asof_week) max_p 
    from forecast 
    join products on products.product_id = forecast.product_id 
    join weeks on weeks.wkno = forecast.for_week 
    left join inventory on 
     inventory.product_id = products.product_id 
     and inventory.asof_week = weeks.wkno 
    group by 
     products.product_id, 
      weeks.wkno, 
     inventory.qoh) as p 
    join forecast f on 
    f.product_id = p.product_id 
    and f.for_week = p.wkno 
    and f.asof_week = p.max_p 
+0

Жюльен, спасибо, он ближе. Мне не нужно max (проецирование), но {projection: asof_week самое последнее}. Я вижу, как вы перестроили соединения, которые очень проницательны. +1 – Dinesh

+0

А, простите об этом. Я не обращал внимания. Я отредактирую. –

0

Поблагодарите Жюльен за отзыв. Это дает результат, хотя я не уверен, что это лучший подход или как он будет действовать, если у меня будет более 100 миллионов строк, так как я все еще работаю с наборами игрушек. Вероятно, первая плохая вещь - pw ниже, не указывается.

with pw as (select * from products, weeks)   
    select pw.product_id "product", 
      pw.wkno,   
      i.asof_week "inven asof",  
      coalesce(i.qoh::text,'missing') "qoh", 
      f.for_week "fcast for",  
      coalesce(f.projection::text,'no fcast') "fcast qty",  
      f.asof_week "fcast asof"  
    from pw  
     left join inventory i on(pw.product_id = i.product_id and pw.wkno = i.asof_week) 
     left join forecast f on(pw.product_id = f.product_id 
           and f.for_week = pw.wkno   
           and f.asof_week = (select max(f2.asof_week)    
               from forecast f2       
               where f2.product_id = pw.product_id      
                and f2.asof_week < pw.wkno        
                and f2.for_week = pw.wkno))        
    where   
     not (i.asof_week is null and f.asof_week is null)  
    order by pw.product_id, 
       pw.wkno,  
       f.for_week,       
       f.asof_week   

, который дает

product | wkno | inven asof | qoh | fcast for | fcast qty | fcast asof 
---------+------+------------+---------+-----------+-----------+------------ 
     1 | 4 |   | missing |   4 | 12  |   3 
     1 | 5 |   5 | 10  |   5 | 31  |   4 
     1 | 6 |   6 | 20  |   6 | 42  |   5 
     1 | 7 |   | missing |   7 | 16  |   6 
     2 | 6 |   6 | 200  |   6 | 2000  |   5 
     2 | 7 |   | missing |   7 | 2100  |   5 
(6 rows) 
Time: 2.999 ms 
1

Там, как представляется, некоторые PK/FK ограничения отсутствующие данные:

CREATE TABLE products (
    product_id INTEGER PRIMARY KEY 
    ); 
CREATE TABLE weeks (
    wkno INTEGER PRIMARY KEY 
    ); 
CREATE TABLE inventory (
    product_id INTEGER REFERENCES products(product_id) 
    , asof_week INTEGER REFERENCES weeks(wkno) 
    , qoh float8 
    , PRIMARY KEY (product_id,asof_week) 
    ); 
CREATE TABLE forecast (
    product_id INTEGER REFERENCES products(product_id) 
    , for_week INTEGER REFERENCES weeks(wkno) 
    , asof_week INTEGER REFERENCES weeks(wkno) 
    , projection FLOAT8 
    , PRIMARY KEY (product_id,for_week,asof_week) 
    ); 
INSERT INTO weeks VALUES (4),(5),(6),(7) 
    , (1),(2),(3), (8) -- need these, too 
    ; 
-- et cetera. 

Если таблица weeks предназначена как таблица «календарь», он мог (и должен был) заменить на псевдошарм generate_series(4,7). (и ограничения FK упали)

Запрос страдает из-за конструкции LEFT JOIN + MAX (aggregate). Следующие должны делать то же самое и выглядит проще (NOT EXISTS на помощь ...):

SELECT p.product_id "product" 
     , i.asof_week "inven asof" 
     , i.qoh "qoh" 
     , f.for_week "fcast for" 
     , f.projection "fcast qty" 
     , f.asof_week "fcast asof" 
FROM products p 
CROSS JOIN weeks w 
LEFT JOIN inventory i ON i.product_id = p.product_id AND i.asof_week = w.wkno 
LEFT JOIN forecast f ON f.product_id = p.product_id AND f.for_week = w.wkno 
WHERE NOT EXISTS (
    SELECT * FROM forecast f2 
    WHERE f2.product_id = f.product_id 
     AND f2.for_week = f.for_week 
    AND f2.asof_week < f.asof_week 
    ) 
AND COALESCE(i.asof_week,f.for_week) IS NOT NULL 
ORDER BY p.product_id, i.asof_week, f.for_week, f.asof_week 
    ; 
+0

@Julien Langlois: Это вопрос вкуса. Я предпочитаю обозначать 1 = 1, потому что он более явный (CROSS скрывает все позади ключевого слова, которое выглядит просто как еще одно шумовое слово). BTW: Я не редактировал ваши пробелы, не так ли? – wildplasser

+0

О, ладно, справедливо. Что касается пробелов, я не уверен, что вы имеете в виду: если это то, что я должен был сделать комментарий, а не редактировать, я приношу свои извинения. –

+0

Это не важно, но я склонен рассматривать работу других людей в основном как «произведение искусства» или, по крайней мере, * как способ выражения себя. Даже если я этого не понимаю или если это будет противоречить моим предпочтениям. (и пробел был предназначен как шутка) – wildplasser

 Смежные вопросы

  • Нет связанных вопросов^_^