SQL: Есть ли простой способ упорядочить результаты ПЕРВЫЙ, ТОГДА группировать по другому столбцу? - PullRequest
5 голосов
/ 31 июля 2010

Я вижу, что в SQL GROUP BY должен предшествовать выражению ORDER BY.Означает ли это, что упорядочение выполняется после группировки, отбрасывающей идентичные строки / столбцы?

Поскольку мне кажется, что сначала нужно упорядочить строки по столбцу временной метки A, ТО, отбрасывая строки с одинаковым значением в столбце A. Не уверен, каквыполнить это ...

Я использую MySQL 5.1.41

create table
(
    A int,
    B timestamp
)

Данные могут быть:

+-----+-----------------------+
|  A  |  B                    |
+-----+-----------------------+
|  1  |  today                |
|  1  |  yesterday            |
|  2  |  yesterday            |
|  2  |  tomorrow             |
+-----+-----------------------+

Результаты Iстремлюсь будет:

+-----+-----------------------+
|  A  |  B                    |
+-----+-----------------------+
|  1  |  today                |
|  2  |  tomorrow             |
+-----+-----------------------+

По сути, я хочу строки с последней отметкой времени в столбце B (думаю, ORDER BY) и только одну строку для каждого значения в столбце A (думаю, DISTINCT или GROUP BY).

Мои фактические детали проекта, если вам нужны эти:

В реальной жизни у меня есть две таблицы - users и payment_receipts.

create table users
(
    phone_nr int(10) unsigned not null,
    primary key (phone_nr)
)

create table payment_receipts
(
    phone_nr int(10) unsigned not null,
    payed_ts timestamp default current_timestamp not null,
    payed_until_ts timestamp not null,
    primary key (phone_nr, payed_ts, payed_until_ts)
)

Таблицы могут включать в себя другие столбцы, я опустил все, что IMO здесь не имеет значения.Как часть схемы мобильных платежей, я должен отправлять SMS-сообщения пользователям через мобильную сотовую сеть через определенные промежутки времени, в зависимости, конечно же, от того, должен ли платеж быть или нет.Платеж актуализируется при отправке SMS, которое облагается налогом.Я веду учет всех платежей, совершенных с помощью таблицы payment_receipts для бухгалтерского учета, которая имитирует реальный магазин, где и покупатель, и продавец получают копию квитанции о покупке для справки.В этой таблице хранятся мои (продавцы) копии каждой квитанции.Квитанция клиента - это само полученное SMS.Каждый раз, когда отправляется SMS (и, таким образом, выполняется платеж), в таблицу вставляется запись квитанции с указанием, кто заплатил, когда и «до когда».Чтобы объяснить последнее, представьте себе услугу подписки, которая действует неограниченно долго, пока пользователь явно не откажется, после чего запись пользователя будет удалена.Оплата производится за месяц заранее, поэтому, как правило, разница между payed_ts и payed_until_ts составляет 30 дней.

Естественно, у меня есть пакетное задание, которое выполняется каждый день и требуетвыбрать список пользователей, которым требуется ежемесячная оплата в рамках автоматического продления подписки.Чтобы связать это с фиктивным примером ранее, столбец номера телефона phone_nr равен a, а payed_until_ts равен b, но в реальном коде есть две таблицы, которые приводят меня к следующему поведению и его последствиям: когдазапись пользователя удаляется, квитанция остается, для бухгалтерии.Таким образом, мне нужно не только сгруппировать платежи по дате и отменить все, кроме самой последней даты поступления платежа, мне также нужно следить за тем, чтобы не выбирать квитанции, для которых больше нет соответствующей пользовательской записи.

Я решаю проблему выбора записей, подлежащих оплате, путем нахождения квитанций с самым последним значением payed_until_ts (так как в большинстве случаев для каждого номера телефона будет несколько квитанций) для каждого phone_nr ииз этих строк мне также нужно оставить только те номера телефона, где payed_until_ts раньше, чем время выполнения пакетного задания.Я перебираю список этих номеров и отправляю платежи, сохраняя новую квитанцию ​​для каждого отправленного SMS, где payed_ts равно now() и payed_until_ts равно now() + interval 30 days.

Ответы [ 5 ]

9 голосов
/ 31 июля 2010
Select a,b from (select a,b from table order by b) as c group by a;
5 голосов
/ 31 июля 2010

Да, группировка выполняется в первую очередь, и она влияет на один select, тогда как порядок влияет на все результаты всех операторов select в union, таких как:

select a, 'max', max(b) from tbl group by a
union all select a, 'min', min(b) from tbl group by a
order by 1, 2

(с использованиемномера полей в order by, так как я не удосужился назвать свои столбцы).Каждый group by влияет только на его select, order by влияет на объединенный набор результатов.

Кажется, что вы можете достичь с помощью:

select A, max(B) from tbl group by A

Этоиспользует функцию агрегации max, чтобы в основном выполнить предварительное упорядочение (она не фактически не сортирует ее в любой достойной СУБД, а просто выберет максимум из подходящего индекса, если он доступен).1019 *

2 голосов
/ 31 июля 2010
SELECT DISTINCT a,b
FROM tbl t
WHERE b = (SELECT MAX(b) FROM tbl WHERE tbl.a = t.a);
2 голосов
/ 31 июля 2010

Согласно вашим новым правилам (протестировано с PostgreSQL)


Запрос, который вы хотите:

SELECT    pr.phone_nr, pr.payed_ts, pr.payed_until_ts 
FROM      payment_receipts pr
JOIN      users
          ON (pr.phone_nr = users.phone_nr)
   JOIN      (select phone_nr, max(payed_until_ts) as payed_until_ts 
              from payment_receipts 
              group by phone_nr
             ) sub
             ON (    pr.phone_nr       = sub.phone_nr 
                 AND pr.payed_until_ts = sub.payed_until_ts)
ORDER BY  pr.phone_nr, pr.payed_ts, pr.payed_until_ts;


Оригинальный ответ (с обновлениями):

CREATE TABLE foo (a NUMERIC, b TEXT, DATE);

INSERT INTO foo VALUES 
   (1,'a','2010-07-30'),
   (1,'b','2010-07-30'),
   (1,'c','2010-07-31'),
   (1,'d','2010-07-31'),
   (1,'a','2010-07-29'),
   (1,'c','2010-07-29'),
   (2,'a','2010-07-29'),
   (2,'a','2010-08-01');

-- table contents
SELECT * FROM foo ORDER BY c,a,b;
 a | b |     c      
---+---+------------
 1 | a | 2010-07-29
 1 | c | 2010-07-29
 2 | a | 2010-07-29
 1 | a | 2010-07-30
 1 | b | 2010-07-30
 1 | c | 2010-07-31
 1 | d | 2010-07-31
 2 | a | 2010-08-01

-- The following solutions both retrieve records based on the latest date
--    they both return the same result set, solution 1 is faster, solution 2
--    is easier to read

-- Solution 1: 
SELECT    foo.a, foo.b, foo.c 
FROM      foo
JOIN      (select a, max(c) as c from foo group by a) bar
  ON      (foo.a=bar.a and foo.c=bar.c)
ORDER BY  foo.a, foo.b, foo.c;

-- Solution 2: 
SELECT    a, b, MAX(c) AS c 
FROM      foo main
GROUP BY  a, b
HAVING    MAX(c) = (select max(c) from foo sub where main.a=sub.a group by a)
ORDER BY  a, b;

 a | b |     c      
---+---+------------
 1 | c | 2010-07-31
 1 | d | 2010-07-31
 2 | a | 2010-08-01
(3 rows)  


Комментарий:
1 возвращается дважды, потому что их несколько b значений. Это приемлемо (и рекомендуется). Ваши данные никогда не должны иметь этой проблемы, потому что c основан на значении b.

0 голосов
/ 28 января 2016
create table user_payments
(
    phone_nr int NOT NULL,
    payed_until_ts datetime NOT NULL
)

insert into user_payments
(phone_nr, payed_until_ts)
values
(1, '2016-01-28'), -- today
(1, '2016-01-27'), -- yesterday  
(2, '2016-01-27'), -- yesterday 
(2, '2016-01-29')  -- tomorrow

select phone_nr, MAX(payed_until_ts) as latest_payment
from user_payments
group by phone_nr

-- OUTPUT:
-- phone_nr latest_payment
-- 1        2016-01-28 00:00:00.000
-- 2        2016-01-29 00:00:00.000

В приведенном выше примере я использовал столбец datetime, но аналогичный запрос должен работать для столбца timestamp.

Функция MAX в основном выполняет столбец ORDER BY payed_until_ts и выбирает последнее значениекаждый номер телефонаКроме того, вы получите только одно значение для каждого phone_nr из-за предложения «GROUP BY».

...