Selecione a primeira linha em cada grupo GROUP BY?


1326

Como o título sugere, eu gostaria de selecionar a primeira linha de cada conjunto de linhas agrupadas com a GROUP BY.

Especificamente, se eu tenho uma purchasestabela assim:

SELECT * FROM purchases;

Minha saída:

id | cliente | total
--- + ---------- + ------
 1 | Joe 5
 2 Sally 3
 3 Joe 2
 4 Sally 1

Gostaria de consultar ida maior compra ( total) feita por cada um customer. Algo assim:

SELECT FIRST(id), customer, FIRST(total)
FROM  purchases
GROUP BY customer
ORDER BY total DESC;

Saída esperada:

PRIMEIRO (id) | cliente | PRIMEIRO (total)
---------- + ---------- + -------------
        1 | Joe 5
        2 Sally 3

como você está procurando apenas cada um dos maiores, por que não consultar MAX(total)?
phil294

4
@ a consulta de phil294 por max (total) não associará esse total ao valor 'id' da linha em que ocorreu.
gwideman 7/02

Respostas:


1117

No Oracle 9.2+ (não 8i +, como originalmente declarado), SQL Server 2005+, PostgreSQL 8.4+, DB2, Firebird 3.0+, Teradata, Sybase, Vertica:

WITH summary AS (
    SELECT p.id, 
           p.customer, 
           p.total, 
           ROW_NUMBER() OVER(PARTITION BY p.customer 
                                 ORDER BY p.total DESC) AS rk
      FROM PURCHASES p)
SELECT s.*
  FROM summary s
 WHERE s.rk = 1

Suportado por qualquer banco de dados:

Mas você precisa adicionar lógica para romper laços:

  SELECT MIN(x.id),  -- change to MAX if you want the highest
         x.customer, 
         x.total
    FROM PURCHASES x
    JOIN (SELECT p.customer,
                 MAX(total) AS max_total
            FROM PURCHASES p
        GROUP BY p.customer) y ON y.customer = x.customer
                              AND y.max_total = x.total
GROUP BY x.customer, x.total

2
O Informix 12.x também suporta funções de janela (embora o CTE precise ser convertido em uma tabela derivada). E Firebird 3.0 também suporta funções de janela
a_horse_with_no_name

37
ROW_NUMBER() OVER(PARTITION BY [...])juntamente com outras otimizações, me ajudou a reduzir uma consulta de 30 segundos para alguns milissegundos. Obrigado! (PostgreSQL 9.2)
Sam

8
Se houver várias compras com a mais alta totalpara um cliente, a primeira consulta retornará um vencedor arbitrário (dependendo dos detalhes da implementação; a idalteração pode ser executada a cada execução!). Normalmente (nem sempre) você deseja uma linha por cliente, definida por critérios adicionais como "aquele com o menor id". Para corrigir, acrescente idà ORDER BYlista de row_number(). Então você obtém o mesmo resultado da segunda consulta, o que é muito ineficiente para este caso. Além disso, você precisaria de outra subconsulta para cada coluna adicional.
Erwin Brandstetter

2
O BigQuery do Google também suporta o comando ROW_NUMBER () da primeira consulta. Trabalhou como um encanto para nós
Praxiteles

2
Observe que a primeira versão com a função window funciona a partir da versão 3.25.0 do SQLite: sqlite.org/windowfunctions.html#history
brianz 17/18

1150

No PostgreSQL, isso geralmente é mais simples e rápido (mais otimização de desempenho abaixo):

SELECT DISTINCT ON (customer)
       id, customer, total
FROM   purchases
ORDER  BY customer, total DESC, id;

Ou mais curto (se não tão claro) com números ordinais de colunas de saída:

SELECT DISTINCT ON (2)
       id, customer, total
FROM   purchases
ORDER  BY 2, 3 DESC, 1;

Se totalpuder ser NULL (não será prejudicial de qualquer maneira, mas você desejará corresponder aos índices existentes ):

...
ORDER  BY customer, total DESC NULLS LAST, id;

Pontos principais

  • DISTINCT ONé uma extensão PostgreSQL do padrão (onde apenas DISTINCTa SELECTlista inteira é definida).

  • Liste qualquer número de expressões na DISTINCT ONcláusula, o valor da linha combinada define duplicatas. O manual:

    Obviamente, duas linhas são consideradas distintas se diferirem em pelo menos um valor de coluna. Valores nulos são considerados iguais nesta comparação.

    Negrito ênfase minha.

  • DISTINCT ONpode ser combinado com ORDER BY. As expressões iniciais em ORDER BYdevem estar no conjunto de expressões em DISTINCT ON, mas você pode reorganizar a ordem entre elas livremente. Exemplo. Você pode adicionar expressões adicionaisORDER BY para selecionar uma linha específica de cada grupo de pares. Ou, como o manual coloca :

    As DISTINCT ONexpressões devem corresponder às ORDER BY expressões mais à esquerda . A ORDER BYcláusula normalmente contém expressões adicionais que determinam a precedência desejada de linhas dentro de cada DISTINCT ONgrupo.

    Eu adicionei idcomo último item para romper os laços:
    "Escolha a linha com a menor idde cada grupo que compartilhe a mais alta total".

    Para ordenar os resultados de uma maneira que não concorde com a ordem de classificação que determina a primeira por grupo, você pode aninhar a consulta acima em uma consulta externa com outra ORDER BY. Exemplo.

  • Se totalpuder ser NULL, você provavelmente desejará a linha com o maior valor não nulo. Adicione NULLS LASTcomo demonstrado. Vejo:

  • A SELECTlista não é restrita por expressões DISTINCT ONou ORDER BYde qualquer forma. (Não é necessário no caso simples acima):

    • Você não precisa incluir nenhuma das expressões em DISTINCT ONou ORDER BY.

    • Você pode incluir qualquer outra expressão na SELECTlista. Isso é fundamental para substituir consultas muito mais complexas por subconsultas e funções agregadas / janelas.

  • Eu testei com o Postgres versões 8.3 - 12. Mas o recurso existe desde pelo menos a versão 7.1, então basicamente sempre.

Índice

O índice perfeito para a consulta acima seria um índice de várias colunas, abrangendo todas as três colunas na sequência correspondente e com a ordem de classificação correspondente:

CREATE INDEX purchases_3c_idx ON purchases (customer, total DESC, id);

Pode ser muito especializado. Mas use-o se o desempenho de leitura para uma consulta específica for crucial. Se você tiver DESC NULLS LASTna consulta, use o mesmo no índice para que a ordem de classificação corresponda e o índice seja aplicável.

Efetividade / otimização de desempenho

Pese o custo e o benefício antes de criar índices personalizados para cada consulta. O potencial do índice acima depende em grande parte da distribuição dos dados .

O índice é usado porque fornece dados pré-classificados. No Postgres 9.2 ou posterior, a consulta também pode se beneficiar de uma varredura de índice apenas se o índice for menor que a tabela subjacente. O índice deve ser verificado na íntegra, no entanto.

Referência

Eu tinha uma referência simples aqui, que está desatualizada agora. Substituí-lo por uma referência detalhada nesta resposta separada .


28
Essa é uma ótima resposta para a maioria dos tamanhos de banco de dados, mas quero ressaltar que à medida que você se aproxima de ~ milhões de linhas DISTINCT ONse torna extremamente lento. A implementação sempre classifica a tabela inteira e procura duplicatas, ignorando todos os índices (mesmo se você tiver criado o índice de várias colunas necessário). Consulte explainextended.com/2009/05/03/postgresql-optimizing-distinct para obter uma possível solução.
Meekohi 24/03

14
Usar ordinais para "diminuir o código" é uma péssima idéia. Que tal deixar os nomes das colunas para torná-los legíveis?
KOTJMF 30/09

13
@KOTJMF: Sugiro que você escolha sua preferência pessoal. Eu demonstro as duas opções para educar. A abreviação da sintaxe pode ser útil para expressões longas na SELECTlista.
Erwin Brandstetter

1
@jangorecki: O benchmark original é de 2011, não tenho mais a configuração. Mas já era hora de executar testes nas páginas 9.4 e 9.5, de qualquer maneira. Veja detalhes na resposta adicionada. . Você pode adicionar um comentário com o resultado da sua instalação abaixo?
Erwin Brandstetter 11/01

2
@ PirateApp: Não do topo da minha cabeça. DISTINCT ONé bom apenas para obter uma linha por grupo de pares.
Erwin Brandstetter

134

Referência

Testando os candidatos mais interessantes com Postgres 9.4 e 9.5 com uma mesa no meio do caminho realista de 200 mil linhas em purchasese 10k distintacustomer_id ( avg. 20 linhas por cliente ).

Para o Postgres 9.5, realizei um segundo teste com 86446 clientes distintos. Veja abaixo ( média de 2,3 linhas por cliente ).

Configuração

Tabela principal

CREATE TABLE purchases (
  id          serial
, customer_id int  -- REFERENCES customer
, total       int  -- could be amount of money in Cent
, some_column text -- to make the row bigger, more realistic
);

Eu uso um serial(restrição de PK adicionada abaixo) e um número inteiro, customer_idpois essa é uma configuração mais típica. Também adicionado some_columnpara compensar tipicamente mais colunas.

Dados fictícios, PK, índice - uma tabela típica também possui algumas tuplas mortas:

INSERT INTO purchases (customer_id, total, some_column)    -- insert 200k rows
SELECT (random() * 10000)::int             AS customer_id  -- 10k customers
     , (random() * random() * 100000)::int AS total     
     , 'note: ' || repeat('x', (random()^2 * random() * random() * 500)::int)
FROM   generate_series(1,200000) g;

ALTER TABLE purchases ADD CONSTRAINT purchases_id_pkey PRIMARY KEY (id);

DELETE FROM purchases WHERE random() > 0.9; -- some dead rows

INSERT INTO purchases (customer_id, total, some_column)
SELECT (random() * 10000)::int             AS customer_id  -- 10k customers
     , (random() * random() * 100000)::int AS total     
     , 'note: ' || repeat('x', (random()^2 * random() * random() * 500)::int)
FROM   generate_series(1,20000) g;  -- add 20k to make it ~ 200k

CREATE INDEX purchases_3c_idx ON purchases (customer_id, total DESC, id);

VACUUM ANALYZE purchases;

customer tabela - para consulta superior

CREATE TABLE customer AS
SELECT customer_id, 'customer_' || customer_id AS customer
FROM   purchases
GROUP  BY 1
ORDER  BY 1;

ALTER TABLE customer ADD CONSTRAINT customer_customer_id_pkey PRIMARY KEY (customer_id);

VACUUM ANALYZE customer;

No meu segundo teste para a versão 9.5, usei a mesma configuração, mas com random() * 100000para gerar customer_idpara obter apenas algumas linhas por customer_id.

Tamanhos de objeto para tabela purchases

Gerado com esta consulta .

               what                | bytes/ct | bytes_pretty | bytes_per_row
-----------------------------------+----------+--------------+---------------
 core_relation_size                | 20496384 | 20 MB        |           102
 visibility_map                    |        0 | 0 bytes      |             0
 free_space_map                    |    24576 | 24 kB        |             0
 table_size_incl_toast             | 20529152 | 20 MB        |           102
 indexes_size                      | 10977280 | 10 MB        |            54
 total_size_incl_toast_and_indexes | 31506432 | 30 MB        |           157
 live_rows_in_text_representation  | 13729802 | 13 MB        |            68
 ------------------------------    |          |              |
 row_count                         |   200045 |              |
 live_tuples                       |   200045 |              |
 dead_tuples                       |    19955 |              |

Consultas

1. row_number()em CTE, ( ver outra resposta )

WITH cte AS (
   SELECT id, customer_id, total
        , row_number() OVER(PARTITION BY customer_id ORDER BY total DESC) AS rn
   FROM   purchases
   )
SELECT id, customer_id, total
FROM   cte
WHERE  rn = 1;

2. row_number()na subconsulta (minha otimização)

SELECT id, customer_id, total
FROM   (
   SELECT id, customer_id, total
        , row_number() OVER(PARTITION BY customer_id ORDER BY total DESC) AS rn
   FROM   purchases
   ) sub
WHERE  rn = 1;

3. DISTINCT ON( veja outra resposta )

SELECT DISTINCT ON (customer_id)
       id, customer_id, total
FROM   purchases
ORDER  BY customer_id, total DESC, id;

4. rCTE com LATERALsubconsulta ( veja aqui )

WITH RECURSIVE cte AS (
   (  -- parentheses required
   SELECT id, customer_id, total
   FROM   purchases
   ORDER  BY customer_id, total DESC
   LIMIT  1
   )
   UNION ALL
   SELECT u.*
   FROM   cte c
   ,      LATERAL (
      SELECT id, customer_id, total
      FROM   purchases
      WHERE  customer_id > c.customer_id  -- lateral reference
      ORDER  BY customer_id, total DESC
      LIMIT  1
      ) u
   )
SELECT id, customer_id, total
FROM   cte
ORDER  BY customer_id;

5. customermesa com LATERAL( veja aqui )

SELECT l.*
FROM   customer c
,      LATERAL (
   SELECT id, customer_id, total
   FROM   purchases
   WHERE  customer_id = c.customer_id  -- lateral reference
   ORDER  BY total DESC
   LIMIT  1
   ) l;

6. array_agg()com ORDER BY( veja outra resposta )

SELECT (array_agg(id ORDER BY total DESC))[1] AS id
     , customer_id
     , max(total) AS total
FROM   purchases
GROUP  BY customer_id;

Resultados

Tempo de execução para as consultas acima com EXPLAIN ANALYZE(e todas as opções desativadas ), o melhor de 5 execuções .

Todas as consultas utilizaram uma Varredura por Índice Apenaspurchases2_3c_idx (entre outras etapas). Alguns deles apenas para o tamanho menor do índice, outros de forma mais eficaz.

A. Postgres 9.4 com 200 mil linhas e ~ 20 por customer_id

1. 273.274 ms  
2. 194.572 ms  
3. 111.067 ms  
4.  92.922 ms  
5.  37.679 ms  -- winner
6. 189.495 ms

B. O mesmo com o Postgres 9.5

1. 288.006 ms
2. 223.032 ms  
3. 107.074 ms  
4.  78.032 ms  
5.  33.944 ms  -- winner
6. 211.540 ms  

C. O mesmo que B., mas com ~ 2,3 linhas por customer_id

1. 381.573 ms
2. 311.976 ms
3. 124.074 ms  -- winner
4. 710.631 ms
5. 311.976 ms
6. 421.679 ms

Referências relacionadas

Aqui está um novo teste "ogr" com 10 milhões de linhas e 60 mil "clientes" únicos no Postgres 11.5 (atual em setembro de 2019). Os resultados ainda estão alinhados com o que vimos até agora:

Referência original (desatualizada) de 2011

Eu executei três testes com o PostgreSQL 9.1 em uma tabela da vida real de 65579 linhas e índices btree de coluna única em cada uma das três colunas envolvidas e aproveitei o melhor tempo de execução de 5 execuções.
Comparando a primeira consulta do @OMGPonies ( A) com a solução acimaDISTINCT ON ( B):

  1. Selecione a tabela inteira, resultando em 5958 linhas neste caso.

    A: 567.218 ms
    B: 386.673 ms
  2. Use a condição WHERE customer BETWEEN x AND yresultante em 1000 linhas.

    A: 249.136 ms
    B:  55.111 ms
  3. Selecione um único cliente com WHERE customer = x.

    A:   0.143 ms
    B:   0.072 ms

Mesmo teste repetido com o índice descrito na outra resposta

CREATE INDEX purchases_3c_idx ON purchases (customer, total DESC, id);

1A: 277.953 ms  
1B: 193.547 ms

2A: 249.796 ms -- special index not used  
2B:  28.679 ms

3A:   0.120 ms  
3B:   0.048 ms

5
Obrigado por uma excelente referência. Eu queria saber se a consulta de dados de eventos em que você tem um carimbo de data e hora em vez do total se beneficiaria do novo índice BRIN. Isso pode potencialmente acelerar a consulta temporal.
Jangorecki

3
@jangorecki: Qualquer tabela enorme com dados classificados fisicamente pode se beneficiar de um índice BRIN.
Erwin Brandstetter

@ErwinBrandstetter Nos exemplos 2. row_number()e 5. customer table with LATERAL, o que garante que o ID seja o menor?
Artem Novikov

@ArtemNovikov: Nada. O objetivo é recuperar, por customer_id linha com a mais alta total. É uma coincidência enganosa nos dados de teste da pergunta que as idlinhas selecionadas também sejam as menores por customer_id.
Erwin Brandstetter

1
@ArtemNovikov: Para permitir verificações apenas de índice.
precisa

55

Isso é comum problema, que já possui soluções bem testadas e altamente otimizadas . Pessoalmente, prefiro a solução de junção esquerda de Bill Karwin (a postagem original com muitas outras soluções ).

Observe que muitas soluções para esse problema comum podem ser encontradas surpreendentemente em uma das fontes mais oficiais, manual do MySQL ! Consulte Exemplos de consultas comuns :: As linhas que mantêm o máximo em grupo de uma determinada coluna .


22
Como o manual do MySQL é "oficial" para as questões do Postgres / SQLite (sem mencionar o SQL)? Além disso, para ficar claro, a DISTINCT ONversão é muito mais curta, mais simples e geralmente apresenta um desempenho melhor no Postgres do que as alternativas com uma auto LEFT JOINou semi-anti-junção NOT EXISTS. Também é "bem testado".
Erwin Brandstetter

3
Adicionalmente ao que Erwin escreveu, eu diria que o uso de uma função de janela (que é a funcionalidade SQL comum hoje em dia) é quase sempre mais rápido do que usando uma junção com uma tabela derivada
a_horse_with_no_name

6
Ótimas referências. Eu não sabia que isso era chamado de o maior problema de n por grupo. Obrigado.
David Mann

A questão não diz respeito ao maior n por grupo, mas ao primeiro n.
Reinierpost

1
Em um caso de dois campos de pedidos, eu tentei "a solução de junção esquerda de Bill Karwin" apresenta um desempenho ruim. Veja meu comentário abaixo stackoverflow.com/a/8749095/684229
Johnny Wong

30

No Postgres você pode usar array_aggassim:

SELECT  customer,
        (array_agg(id ORDER BY total DESC))[1],
        max(total)
FROM purchases
GROUP BY customer

Isso fornecerá a você a idmaior compra de cada cliente.

Algumas coisas a serem observadas:

  • array_aggé uma função agregada, por isso trabalha com GROUP BY.
  • array_aggpermite especificar uma ordem com escopo definido apenas para si mesma, para que não restrinja a estrutura de toda a consulta. Também existe uma sintaxe de como você classifica NULLs, se precisar fazer algo diferente do padrão.
  • Depois que construímos a matriz, pegamos o primeiro elemento. (As matrizes do Postgres são indexadas 1, não indexadas 0).
  • Você poderia usar de array_aggmaneira semelhante para sua terceira coluna de saída, mas max(total)é mais simples.
  • Ao contrário DISTINCT ON, o uso array_aggpermite manter o seu GROUP BY, caso você queira isso por outros motivos.

14

A solução não é muito eficiente, como apontado por Erwin, devido à presença de SubQs

select * from purchases p1 where total in
(select max(total) from purchases where p1.customer=customer) order by total desc;

Obrigado, sim, concordo com você, a junção entre subq e consulta externa realmente leva mais tempo. "In" não será um problema aqui, pois o subq resultará em apenas uma linha. BTW, que erro de sintaxe você está apontando?
user2407394

ohh .. usado para "Teradata" .. editado now..however desempate não é necessária aqui, pois precisa encontrar maior total para cada cliente ..
user2407394

Você está ciente de que obtém várias linhas para um único cliente em caso de empate? Se isso é desejável depende dos requisitos exatos. Normalmente não é. Para a questão em questão, o título é bastante claro.
Erwin Brandstetter

Isso não está claro na pergunta: se o mesmo cliente tiver comprado = Máximo para 2 IDs diferentes, acho que devemos exibir os dois.
user2407394

10

Eu uso desta maneira (apenas postgresql): https://wiki.postgresql.org/wiki/First/last_%28aggregate%29

-- Create a function that always returns the first non-NULL item
CREATE OR REPLACE FUNCTION public.first_agg ( anyelement, anyelement )
RETURNS anyelement LANGUAGE sql IMMUTABLE STRICT AS $$
        SELECT $1;
$$;

-- And then wrap an aggregate around it
CREATE AGGREGATE public.first (
        sfunc    = public.first_agg,
        basetype = anyelement,
        stype    = anyelement
);

-- Create a function that always returns the last non-NULL item
CREATE OR REPLACE FUNCTION public.last_agg ( anyelement, anyelement )
RETURNS anyelement LANGUAGE sql IMMUTABLE STRICT AS $$
        SELECT $2;
$$;

-- And then wrap an aggregate around it
CREATE AGGREGATE public.last (
        sfunc    = public.last_agg,
        basetype = anyelement,
        stype    = anyelement
);

Então seu exemplo deve funcionar quase como está:

SELECT FIRST(id), customer, FIRST(total)
FROM  purchases
GROUP BY customer
ORDER BY FIRST(total) DESC;

CAVEAT: ignora as linhas NULL


Editar 1 - Use a extensão postgres

Agora eu uso desta maneira: http://pgxn.org/dist/first_last_agg/

Para instalar no ubuntu 14.04:

apt-get install postgresql-server-dev-9.3 git build-essential -y
git clone git://github.com/wulczer/first_last_agg.git
cd first_last_app
make && sudo make install
psql -c 'create extension first_last_agg'

É uma extensão do postgres que oferece a primeira e a última funções; aparentemente mais rápido do que o caminho acima.


Edição 2 - Ordenação e filtragem

Se você usar funções agregadas (como estas), poderá solicitar os resultados, sem a necessidade de solicitar os dados já:

http://www.postgresql.org/docs/current/static/sql-expressions.html#SYNTAX-AGGREGATES

Portanto, o exemplo equivalente, com pedidos, seria algo como:

SELECT first(id order by id), customer, first(total order by id)
  FROM purchases
 GROUP BY customer
 ORDER BY first(total);

Obviamente, você pode solicitar e filtrar o que considerar adequado no agregado; é uma sintaxe muito poderosa.


Usando essa abordagem de função personalizada também. Suficientemente universal e simples. Por que complicar as coisas, essa solução é significativamente menos eficiente do que outras?
Sergey Shcherbakov

9

A pergunta:

SELECT purchases.*
FROM purchases
LEFT JOIN purchases as p 
ON 
  p.customer = purchases.customer 
  AND 
  purchases.total < p.total
WHERE p.total IS NULL

COMO ISSO FUNCIONA! (Eu estive lá)

Queremos garantir que tenhamos apenas o total mais alto para cada compra.


Algumas coisas teóricas (pule esta parte se você quiser apenas entender a consulta)

Seja Total uma função T (cliente, ID), onde ele retorna um valor com o nome e o ID. Para provar que o total fornecido (T (cliente, ID)) é o mais alto, temos que provar que queremos provar

  • Tx T (cliente, ID)> T (cliente, x) (esse total é superior a todo o total desse cliente)

OU

  • Tx T (cliente, ID) <T (cliente, x) (não existe um total maior para esse cliente)

A primeira abordagem precisará de nós para obter todos os registros para esse nome que eu realmente não gosto.

O segundo precisará de uma maneira inteligente de dizer que não pode haver registro maior que este.


Voltar para SQL

Se sairmos, uniremos a tabela ao nome e o total será menor que a tabela unida:

      LEFT JOIN purchases as p 
      ON 
      p.customer = purchases.customer 
      AND 
      purchases.total < p.total

garantimos que todos os registros que possuem outro registro com o total mais alto para o mesmo usuário sejam associados:

purchases.id, purchases.customer, purchases.total, p.id, p.customer, p.total
1           , Tom           , 200             , 2   , Tom   , 300
2           , Tom           , 300
3           , Bob           , 400             , 4   , Bob   , 500
4           , Bob           , 500
5           , Alice         , 600             , 6   , Alice   , 700
6           , Alice         , 700

Isso nos ajudará a filtrar o total mais alto para cada compra, sem a necessidade de agrupamento:

WHERE p.total IS NULL

purchases.id, purchases.name, purchases.total, p.id, p.name, p.total
2           , Tom           , 300
4           , Bob           , 500
6           , Alice         , 700

E essa é a resposta que precisamos.


8

Solução muito rápida

SELECT a.* 
FROM
    purchases a 
    JOIN ( 
        SELECT customer, min( id ) as id 
        FROM purchases 
        GROUP BY customer 
    ) b USING ( id );

e realmente muito rápido se a tabela for indexada por id:

create index purchases_id on purchases (id);

A cláusula USING é muito padrão. Só que alguns sistemas menores de banco de dados não o possuem.
Holger Jakobs

2
Isso não encontrar compra dos clientes com maior total de
Johnny Wong

7

No SQL Server, você pode fazer isso:

SELECT *
FROM (
SELECT ROW_NUMBER()
OVER(PARTITION BY customer
ORDER BY total DESC) AS StRank, *
FROM Purchases) n
WHERE StRank = 1

Explicação: Aqui, agrupar por é feito com base no cliente e, em seguida, solicitá-lo pelo total. Cada um desses grupos recebe o número de série como StRank e estamos contratando o primeiro cliente cujo StRank é 1


Obrigado! Isso funcionou perfeitamente e foi muito fácil de entender e implementar.
Ruohola 1/10/19


4

No PostgreSQL, outra possibilidade é usar a first_valuefunção window em combinação com SELECT DISTINCT:

select distinct customer_id,
                first_value(row(id, total)) over(partition by customer_id order by total desc, id)
from            purchases;

Eu criei um composto (id, total), para que ambos os valores sejam retornados pelo mesmo agregado. Você pode sempre aplicar first_value()duas vezes.


3

A solução aceita por OMG Ponies "Supported by any database" tem boa velocidade em meu teste.

Aqui, forneço uma solução da mesma abordagem, mas mais completa e limpa de qualquer banco de dados. Os laços são considerados (pressupõe o desejo de obter apenas uma linha para cada cliente, até vários registros para o total máximo por cliente) e outros campos de compra (por exemplo, purchase_payment_id) serão selecionados para as linhas correspondentes reais na tabela de compras.

Suportado por qualquer banco de dados:

select * from purchase
join (
    select min(id) as id from purchase
    join (
        select customer, max(total) as total from purchase
        group by customer
    ) t1 using (customer, total)
    group by customer
) t2 using (id)
order by customer

Essa consulta é razoavelmente rápida, especialmente quando há um índice composto como (cliente, total) na tabela de compras.

Observação:

  1. t1, t2 são alias de subconsulta que podem ser removidos dependendo do banco de dados.

  2. Advertência : a using (...)cláusula atualmente não é suportada no MS-SQL e no Oracle db a partir desta edição em janeiro de 2017. Você precisa expandi-la para, por exemplo, on t2.id = purchase.idetc. A sintaxe USING funciona no SQLite, MySQL e PostgreSQL.


2

Snowflake / Teradata suporta QUALIFYcláusula que funciona como HAVINGpara funções em janela:

SELECT id, customer, total
FROM PURCHASES
QUALIFY ROW_NUMBER() OVER(PARTITION BY p.customer ORDER BY p.total DESC) = 1

1
  • Se você deseja selecionar qualquer linha (por alguma condição específica) do conjunto de linhas agregadas.

  • Se você deseja usar outra sum/avgfunção de agregação ( ) além de max/min. Assim, você não pode usar pista comDISTINCT ON

Você pode usar a próxima subconsulta:

SELECT  
    (  
       SELECT **id** FROM t2   
       WHERE id = ANY ( ARRAY_AGG( tf.id ) ) AND amount = MAX( tf.amount )   
    ) id,  
    name,   
    MAX(amount) ma,  
    SUM( ratio )  
FROM t2  tf  
GROUP BY name

Você pode substituir amount = MAX( tf.amount )por qualquer condição que desejar por uma restrição: Esta subconsulta não deve retornar mais de uma linha

Mas se você quiser fazer essas coisas, provavelmente está procurando funções da janela


1

Para o SQl Server, a maneira mais eficiente é:

with
ids as ( --condition for split table into groups
    select i from (values (9),(12),(17),(18),(19),(20),(22),(21),(23),(10)) as v(i) 
) 
,src as ( 
    select * from yourTable where  <condition> --use this as filter for other conditions
)
,joined as (
    select tops.* from ids 
    cross apply --it`s like for each rows
    (
        select top(1) * 
        from src
        where CommodityId = ids.i 
    ) as tops
)
select * from joined

e não se esqueça de criar um índice clusterizado para colunas usadas

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.