SQL: группировка 2 таблиц как 1 с соединением, объединением, а затем?

У меня есть 5 таблиц:

идентификатор клиента - имя

p_orders id - id_customer - код - дата

p_items id - id_order - описание - цена

и h_orders и h_items, которые являются точной копией p_orders и p_items.

Когда таблицы p_ достигают большого количества строк, я перемещаю самые старые в таблицы h_.. они должны быть в истории.

Итак, моя проблема: как получить данные как из таблиц p_, так и из таблиц h_, считая их одной уникальной таблицей?

Например, я хочу получить количество заказов для каждого клиента и общую стоимость (всех заказов клиента), и я использую этот запрос:

SELECT
    customer.id,
    customer.name,
    count(DISTINCT p_orders.id) AS num_orders,
    sum(p_items.price) AS total_money
FROM
    customer
    INNER JOIN p_orders ON p_orders.id_customer = customer.id
    INNER JOIN p_items ON p_items.id_order = p_orders.id
GROUP BY
    customer.id,
    customer.name,
    p_orders.id_customer
ORDER BY
    customer.id

он работает только для одного «набора» таблиц (p_ или h_).. но я хочу их обоих.

Я пытался использовать UNION:

(
    SELECT
        customer.id,
        customer.name,
        count(DISTINCT p_orders.id) AS num_orders,
        sum(p_items.price) AS total_money
    FROM
        customer
        INNER JOIN p_orders ON p_orders.id_customer = customer.id
        INNER JOIN p_items ON p_items.id_order = p_orders.id
    GROUP BY
        customer.id,
        customer.name,
        p_orders.id_customer
)
UNION
(
    SELECT
        customer.id,
        customer.name,
        count(DISTINCT h_orders.id) AS num_orders,
        sum(h_items.price) AS total_money
    FROM
        customer
        INNER JOIN h_orders ON h_orders.id_customer = customer.id
        INNER JOIN h_items ON h_items.id_order = h_orders.id
    GROUP BY
        customer.id,
        customer.name,
        h_orders.id_customer
)
ORDER BY id ASC

Это работает, но если у клиента есть заказы как в таблицах p_, так и в таблицах h_, у меня будет 2 строки для этого клиента с двумя разными num_orders и total_money (соответственно из таблиц p_ и h_).

Я попытался добавить идентификатор GROUP BY вне союза:

(
    --SELECT 2
)
UNION
(
    --SELECT 1
)
GROUP BY id
ORDER BY id ASC

но запрос терпит неудачу с ОШИБКОЙ: синтаксическая ошибка в или рядом с "GROUP" в символе 948, кажется, что GROUP BY не может использоваться таким образом.

Любое предложение?

ИЗМЕНИТЬ:

Для uriDium да, все таблицы имеют столбец id в качестве первичного ключа, а упомянутые поля (он же p_orders.id_customer) также являются внешними ключами. Вот тестовый дамп структуры базы данных (я добавил некоторые индексы и внешние ключи после создания таблицы, но я не думаю, что это что-то значит):

CREATE TABLE customer (
    id serial NOT NULL,
    name character(50)
);
CREATE TABLE p_orders (
    id serial NOT NULL,
    id_customer integer NOT NULL,
    date date DEFAULT now(),
    code character(5)
);
CREATE TABLE p_items (
    id serial NOT NULL,
    id_order integer NOT NULL,
    descr character(250),
    price money
);
CREATE TABLE h_orders (
    id integer NOT NULL,
    id_customer integer NOT NULL,
    date date,
    code character(5)
);
CREATE TABLE h_items (
    id integer NOT NULL,
    id_order integer NOT NULL,
    descr character(250),
    price money
);
CREATE UNIQUE INDEX id_h_orders ON h_orders USING btree (id);
CREATE INDEX id_h_o_c ON h_orders USING btree (id_customer);
CREATE UNIQUE INDEX id_items_h ON h_items USING btree (id);
CREATE INDEX id_ordinr_dsve ON h_items USING btree (id_order);

ALTER TABLE ONLY customer
    ADD CONSTRAINT customer_pkey  (id);
ALTER TABLE ONLY p_orders
    ADD CONSTRAINT p_orders_pkey PRIMARY KEY (id);
ALTER TABLE ONLY p_items
    ADD CONSTRAINT p_items_pkey PRIMARY KEY (id);
ALTER TABLE ONLY stats
    ADD CONSTRAINT stats_pkey PRIMARY KEY (id);
ALTER TABLE ONLY p_orders
    ADD CONSTRAINT "$1" FOREIGN KEY (id_customer) REFERENCES customer(id) ON DELETE CASCADE;
ALTER TABLE ONLY p_items
    ADD CONSTRAINT "$1" FOREIGN KEY (id_order) REFERENCES p_orders(id) ON DELETE CASCADE;
ALTER TABLE ONLY h_orders
    ADD CONSTRAINT "$1" FOREIGN KEY (id_customer) REFERENCES customer(id) ON DELETE CASCADE;
ALTER TABLE ONLY h_items
    ADD CONSTRAINT "$1" FOREIGN KEY (id_order) REFERENCES h_orders(id) ON DELETE CASCADE;

person Strae    schedule 22.05.2009    source источник


Ответы (6)


Вероятно, вам следует создать представления для двух таблиц:

CREATE VIEW All_Orders
AS
     SELECT
          id,
          id_customer,
          code,
          date,
          'H' AS order_type
     FROM
          h_orders
     UNION ALL
     SELECT
          id,
          id_customer,
          code,
          date,
          'P' AS order_type
     FROM
          p_orders

CREATE VIEW All_Order_Items  -- A table name of "items" is pretty bad in my opinion
AS
     SELECT
          id,
          id_order,
          description,
          price,
          'H' AS order_item_type
     FROM
          h_items
     UNION ALL
     SELECT
          id,
          id_order,
          description,
          price,
          'P' AS order_item_type
     FROM
          p_items

Теперь вы можете просто присоединиться к этим взглядам. Я включил типы (P и H), чтобы вы знали, на что теперь ссылается столбец «id». Если идентификаторы в ваших двух таблицах ("h" и "p" могут иметь дубликаты, вам придется присоединиться к таблице Orders прямо в представлении All_Order_Items. В противном случае у вас возникнут большие проблемы с соединением между двумя представлениями. Надеюсь, ваш идентификатор столбцы спроектированы разумно, а не просто автоматически увеличиваются или столбцы идентификаторов.

person Tom H    schedule 22.05.2009
comment
Нет, когда я перемещаю данные из p_* в h_*, я сохраняю те же значения столбцов, даже идентификатор - и в обеих таблицах определяется как уникальный первичный ключ, единственная разница в том, что в таблице h_* нет последовательность для автоматического увеличения значения идентификатора при каждой вставке - person Strae; 22.05.2009
comment
Надеемся, что ваши столбцы идентификаторов спроектированы разумно, а не просто автоматически увеличиваются или столбцы идентификаторов. Идентификаторы — это интеллектуальный способ проектирования в большинстве бизнес-приложений. - person HLGEM; 22.05.2009
comment
Что именно вы имеете в виду, HLGEM? Вы думаете, что я неправильно (или неэффективно) использую идентификаторы? пожалуйста, объясните .. я здесь, чтобы учиться! - person Strae; 22.05.2009
comment
@DaNieL - Поскольку вы сохраняете значения идентификатора, вы должны быть в хорошей форме. - person Tom H; 22.05.2009
comment
Похоже, что схема id спроектирована правильно: автоинкремент в p*, затем сохраняется при переходе к h*. Прочитайте правильно, конечно, как бы я это сделал... ;) - person RolandTumble; 22.05.2009
comment
Я должен сохранить исходный идентификатор из p_ *, потому что есть другие таблицы, использующие данные заказов и товаров :) - person Strae; 22.05.2009

Вы можете попробовать это:

SELECT tbl.ID, 
       tbl.Name, 
       sum(tbl.num_orders) num_orders, 
       sum(tbl.total_money) total_money
FROM (    
      SELECT customer.id, 
             customer.name,        
             count(DISTINCT p_orders.id) AS num_orders,        
             sum(p_items.price) AS total_money    
      FROM customer        
            INNER JOIN p_orders 
                ON p_orders.id_customer = customer.id        
            INNER JOIN p_items 
                ON p_items.id_order = p_orders.id    
      GROUP BY customer.id, customer.name, p_orders.id_customer

      UNION

      SELECT customer.id, 
             customer.name,        
             count(DISTINCT h_orders.id) AS num_orders,
             sum(h_items.price) AS total_money    
      FROM  customer        
             INNER JOIN h_orders 
                 ON h_orders.id_customer = customer.id
             INNER JOIN h_items 
                 ON h_items.id_order = h_orders.id    
      GROUP BY customer.id, customer.name, h_orders.id_customer
    ) tbl
 GROUB BY tbl.id, tbl.name
 ORDER BY tbl.id ASC
person Jimmie R. Houts    schedule 22.05.2009

Создайте представление с объединением двух запросов, но без агрегатных функций. USe Union All, поскольку одна и та же запись не находится в обеих таблицах, и вам не нужно, чтобы сервер тратил время на поиски этого. Вероятно, у вас будут другие времена, когда вы захотите получить доступ к обеим таблицам в запросе.

Затем напишите свой запрос, используя представление.

код просмотра будет выглядеть примерно так (вам могут понадобиться и другие поля для других целей:

Create view customerOrders
AS
SELECT      customer.id as CustomerID,  customer.name, p_orders.id as OrderID,  p_items.price  as price
FROM        customer        
INNER JOIN  p_orders ON p_orders.id_customer = customer.id        
INNER JOIN  p_items ON p_items.id_order = p_orders.id
union all
SELECT      customer.id,  customer.name,  h_orders.id as id, H_items.price           
FROM        customer        
INNER JOIN  h_orders ON h_orders.id_customer = customer.id        
INNER JOIN  h_items ON h_items.id_order = h_orders.id

тогда вызов для вашего запроса будет примерно таким (ничего из этого не проверено, возможно, потребуется настройка)

SELECT    CustomerID,    customer.name,    count(DISTINCT OrderID) AS num_orders,    
sum(price) AS total_money
FROM    customerOrders
GROUP BY     CustomerID,    customer.name
ORDER BY    CustomerID
person HLGEM    schedule 22.05.2009

Насколько я знаю, SQL Server должен автоматически удалять дубликаты. Использование UNION ALL будет включать дубликаты. Я полагаю, что SQL Server будет использовать первичные ключи как средство определения того, что является дубликатом. Состоят ли первичные ключи в этих таблицах из одного и того же типа данных, и является ли идентификатор 1 в вашей таблице p также идентификатором 1 в вашей таблице h?

person uriDium    schedule 22.05.2009
comment
Я считаю, что при объединении при устранении дубликатов учитываются все поля, а не только ключи. - person Eric Petroelje; 22.05.2009
comment
Да - отредактировано с тестовой структурой базы данных - person Strae; 22.05.2009

Самый простой способ сделать то, что вы ищете, - это создать представления (скажем, «a_orders» и «a_items»). Представления будут просто определены как:

SELECT * FROM p_orders
UNION
SELECT * FROM h_orders

Если вы удаляете строки из a_orders при вставке их в h_orders (чтобы заданный порядок не был в обеих таблицах), было бы намного эффективнее использовать UNION ALL вместо UNION.

person Eric Petroelje    schedule 22.05.2009

Спасибо за все ответы, ребята..

Как «способ просмотра», так и «способ подзапроса» Джимми Р. Хаутса работают отлично, может быть, представления просто более удобны в использовании... и они оба должны занимать одинаковое время (или нет?)

Так что я отмечу как лучший первый ответ о взглядах.

В любом случае, если я могу, могу ли я спросить вас, хороши ли структура и индексы, которые я использовал, или их можно оптимизировать?

person Strae    schedule 22.05.2009