Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there a [straightforward] way to order results *first*, *then* group by another column, with SQL?

I see that in an SQL query, the GROUP BY has to precede the ORDER BY expression. Does this imply that ordering is done after grouping would have discarded identical rows?

Because I seem to need to order rows by a timestamp first, then discard the rows with identical timestamp. And I don't know how to accomplish this.

I am using MySQL 5.1.41.

Here is the definition of the table expressed with create table:

create table
(
    A int,
    B timestamp
)

The data could be:

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

The results of the query on the above table, which I am after, would be:

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

Basically, I want the rows with the latest timestamp in column "B" (hence the mention of ORDER BY), and only one row for each value in column "A" (think DISTINCT or GROUP BY).

The actual problem behind the simplified example above:

In reality, I have two tables - users and 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)
)

The tables may include other columns but I omit these as irrelevant. Implementing a payment scheme, I have to send SMS to users across the cellular network, in periodic intervals depending on whether the payment is due or not. The payment is actualized when the SMS is sent as the recipient is taxed for it. I use the payment_receipts table to keep records of all payments done, i.e. for book-keeping. This is intended to model a real shop where both the buyer and the seller get a copy of the receipt of purchase, for reference. This table stores my (seller's) copy [of each receipt]. The customer's receipt is the received SMS itself. Each time an SMS is sent (and thus a payment is accomplished), the table is inserted a receipt record, stating who paid, when and "until when". To explain the latter, imagine a subscription service, but one which spans indefinitely until the user opt-out explicitly, at which point the corresponding user record is removed. A payment is made a month in advance, so as a rule, the difference between the payed_ts and payed_until_ts is 30 days worth of time.

I have a batch job that executes every day and needs to select a list of users that are due monthly payment as part of the automatic subscription renewal described above. To link this to the dummy example earlier, the phone number column phone_nr would be the column "A" and payed_until_ts would be column "B", but in reality there are two tables, which has to do with the following behaviour: when a user record is removed, the receipt must remain, for book-keeping. So not only do I need to group payments by date and discard all but the latest payment receipt date, I also need to watch out not to select receipts for which there no longer is a matching user record.

To solve the problem of selecting required records -- those that are due payment -- I need to find receipts with the latest payed_until_ts timestamp for each phone_nr (there may be several, obviously) and out of those records I further need to select only those phone numbers where payed_until_ts is earlier than the time the batch job executes. I then would send an SMS to each of these numbers, inserting a receipt record for each sent SMS, where payed_ts is now() and payed_until_ts is now() + interval 30 days.

But I can't seem to come up with the query required.

like image 315
amn Avatar asked Jul 31 '10 12:07

amn


People also ask

Where does the ORDER BY clause Go in SQL?

In this syntax, the ORDER BY clause appears after the FROM clause. In case the SELECT statement contains a WHERE clause, the ORDER BY clause must appear after the WHERE clause. To sort the result set, you specify the column in which you want to sort and the kind of the sort order: Ascending ( ASC)

How to sort values in multiple columns using SQL ORDER BY?

2) Using SQL ORDER BY clause to sort values in multiple columns example To sort by the employees by the first name in ascending order and the last name in descending order, you use the following statement: SELECT employee_id, first_name, last_name, hire_date, salary FROM employees ORDER BY first_name, last_name DESC;

How to use the group by statement in SQL Server?

Important points for the GROUP BY SQL Statement: The GROUP BY statement can only be used in a SQL SELECT statement. The GROUP BY statement must be after the WHERE clause. (If one exists.) The GROUP BY statement must be before the ORDER BY clause. (If one exists.) To filter the GROUP BY results, you must use the HAVING clause after the GROUP BY.

How to sort the result set by date in SQL?

Besides the character and numeric, SQL allows you to sort the result set by date. The following statement sorts the employees by values in the hire_date column in the ascending order. SELECT employee_id, first_name, last_name, hire_date, salary FROM employees ORDER BY hire_date; Code language: SQL (Structured Query Language) (sql)


2 Answers

Select a,b from (select a,b from table order by b) as c group by a;
like image 54
Mike Sherov Avatar answered Nov 02 '22 04:11

Mike Sherov


Yes, grouping is done first, and it affects a single select whereas ordering affects all the results from all select statements in a union, such as:

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

(using field numbers in order by since I couldn't be bothered to name my columns). Each group by affects only its select, the order by affects the combined result set.

It seems that what you're after can be achieved with:

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

This uses the max aggregation function to basically do your pre-group ordering (it doesn't actually sort it in any decent DBMS, rather it will simply choose the maximum from an suitable index if available).

like image 31
paxdiablo Avatar answered Nov 02 '22 05:11

paxdiablo