Справка по SQL-запросам с группированием и вложенными запросами - PullRequest
1 голос
/ 03 декабря 2010

Даны две таблицы:

  • Таблица A, содержащая customerid, lastchange, internallink

  • Таблица B, содержащий internallink, turnover

(Я просто нахожу здесь и заменяю его на общий пример, фактическая структура более сложна. Сейчас диалект SQLэто mySQL.)

Единственная уникальная вещь (для каждой таблицы) - это internallink.В A есть несколько записей с одним и тем же customerID, разными датами в lastchange и разными значениями internallink.Есть другие предметы, связанные с этим;Я не могу изменить таблицы.

Мне нужны идентификаторы записи из A, которые являются самыми последними для клиента (наибольшее значение lastchange из всех с тем же идентификатором customer) и , для которыхзаписи в B, которые соответствуют определенному условию значения, связаны между собой.

Я думаю, что часть

SELECT `internallink` FROM `B` WHERE (`turnover` > 10000) 

не является проблемой.

Я получил это далеко:

SELECT `customerID`, MAX(`lastchange`)
  FROM `A` 
 WHERE `lastchange` IN (SELECT `internallink` FROM `B` 
                         WHERE `turnover` > 10000)
 GROUP BY `customerID`;

Увы, это утверждение дает неправильные результаты, потому что выше вернет мне customerIDs, для которых самое последнее значение не соответствует критериям, но некоторые более старые сделали - он выбирает самое старое, которое имело, и возвращает это,Но в случае, если самая последняя запись ниже порогового значения, customerID вообще не должен появляться.

Где я ошибся и каков правильный подход к этому?

Пример таблицы данныхA

customerid   lastchange   internallink
         3   2010-02-11   11
         3   2010-09-04   12
         3   2010-10-22   13
         3   2010-11-23   14
         4   2010-05-05   15
         4   2010-12-01   16
         5   2010-11-28   17
         5   2010-11-29   18

Таблица B

internallink  turnover
          11     47000
          12     11000
          13      8000
          14     15000
          15     17000
          16     23000
          17     50000
          18     10000

Фактический порог в моих тестах - 12000. Вы можете видеть, что customerID не должен быть в наборе результатов, так как самая последняя запись находится нижепорог.

Результирующий набор должен быть (3,2010-11-23) (4,2010-12-01) - но в настоящее время он также содержит (5,2010-11-28), что неверно.


Становясь немного ближе (с вашей помощью, спасибо!), Оба эти утверждения работают вместе:

SELECT customerID, MAX (lastchange), internallink FROM A GROUP BY customerID;SELECT internallink FROM B WHERE (оборот> 12000);

Теперь все, что мне нужно, это пересечение обоих ... с правильной логикой!

Ответы [ 3 ]

1 голос
/ 03 декабря 2010

Следующий запрос должен делать то, что вы хотите. Это не самый эффективный способ написания такого рода запроса. Но он использует стандартный SQL и выполняется в любой базе данных.

Работает так: внутренний подзапрос находит все обычаи вместе с последними изменениями. Для каждой такой пары (customerid, lastchange) мы находим исходную строку в таблице A. Найдя строку в таблице A, мы используем internallink, чтобы найти совпадающую запись в B, но только если соответствующий оборот превышает 10000.

drop table a;
drop table b;

create table a(
   customerid   int  not null
  ,lastchange   date not null
  ,internallink int  not null
);

create table b(
   internallink int not null
  ,turnover     int not null
);

insert into a values(3, date '2010-02-11', 11);
insert into a values(3, date '2010-09-04', 12);
insert into a values(3, date '2010-10-22', 13);
insert into a values(3, date '2010-11-23', 14);
insert into a values(4, date '2010-05-05', 15);
insert into a values(4, date '2010-12-01', 16);
insert into a values(5, date '2010-11-28', 17);
insert into a values(5, date '2010-11-29', 18);

insert into b values(11, 47000);
insert into b values(12, 11000);
insert into b values(13,  8000);
insert into b values(14, 15000);
insert into b values(15, 17000);
insert into b values(16, 23000);
insert into b values(17, 50000);
insert into b values(18, 10000);

select a.customerid
      ,a.lastchange
      ,a.internallink
      ,b.turnover
  from a
  join b on (a.internallink = b.internallink)
 where b.turnover > 10000
   and (a.customerid, a.lastchange) in(select customerid,max(lastchange)
                                         from a
                                     group by customerid);
0 голосов
/ 04 декабря 2010

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

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

CREATE TRIGGER sync_a_insert AFTER INSERT ON a FOR EACH ROW 
    INSERT INTO cache (`customerID`, `internallink`) VALUES (NEW.`customerID`,NEW.`internallink`);
CREATE TRIGGER sync_a_update AFTER UPDATE ON a FOR EACH ROW 
    UPDATE cache SET `internallink` = NEW.`internallink` WHERE (`customerID` = NEW.`customerID`);
CREATE TRIGGER sync_a_delete BEFORE DELETE ON a FOR EACH ROW 
    DELETE FROM cache WHERE `customerID` = OLD.`customerID`;

Для INSERT и UPDATE эти триггеры срабатывают после факта, поэтому записи в таблице a завершаются до обновления кэша.Для DELETE, кэш должен быть обновлен до исчезновения исходной записи.

Как только это будет сделано, все остальное станет простым:

SELECT `customerID` FROM cache WHERE `internallink` IN 
    (SELECT `internallink` FROM b WHERE (`turnover` > 10000));

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

Ответы, которые вы далибыли, однако, очень полезны для меня.Я многому научился от них и от попыток следовать вашим советам (даже помещая некоторые из них для использования уже в других местах).Спасибо всем, кто ответил на мой вопрос!

0 голосов
/ 03 декабря 2010

Это работает на сервере sql - я не уверен, что mySql имеет аналогичные функции ранжирования.

select a.id, a.lastchange, b.turnover, a.rownumber from B b inner join 
(SELECT id, lastchange, internallink, ROW_NUMBER() OVER(PARTITION BY id ORDER BY lastchange DESC) AS 'rownumber'
FROM A) a on b.internallink = a.internallink
where a.rownumber = 1 and b.turnover > 5000

"ROW_NUMBER () OVER (PARTITION BY id ORDER BY lastchange DESC) AS 'rownumber'" означает ...

Я хочу сгруппировать все идентичные идентификаторы вместе и упорядочить их по lastchange по desc после того, как посчитаем каждую строку. Да, и назовите этот столбец.

id  lastchange    internallink  rownumber
1   2010-01-03    2           1
1   2010-01-02    1           2
1   2010-01-01    1           3
2   2010-01-04    2           1

При выборе любой записи с номером, равным 1, будет возвращена последняя измененная запись идентификатора.

...