AskOverflow.Dev

AskOverflow.Dev Logo AskOverflow.Dev Logo

AskOverflow.Dev Navigation

  • Início
  • system&network
  • Ubuntu
  • Unix
  • DBA
  • Computer
  • Coding
  • LangChain

Mobile menu

Close
  • Início
  • system&network
    • Recentes
    • Highest score
    • tags
  • Ubuntu
    • Recentes
    • Highest score
    • tags
  • Unix
    • Recentes
    • tags
  • DBA
    • Recentes
    • tags
  • Computer
    • Recentes
    • tags
  • Coding
    • Recentes
    • tags
Início / dba / Perguntas / 74773
Accepted
Fake Name
Fake Name
Asked: 2014-08-24 22:44:27 +0800 CST2014-08-24 22:44:27 +0800 CST 2014-08-24 22:44:27 +0800 CST

GROUP BY uma coluna, enquanto ordena por outra no PostgreSQL

  • 772

Como posso GROUP BYuma coluna, enquanto ordenando apenas por outra.

Estou tentando fazer o seguinte:

SELECT dbId,retreivalTime 
    FROM FileItems 
    WHERE sourceSite='something' 
    GROUP BY seriesName 
    ORDER BY retreivalTime DESC 
    LIMIT 100 
    OFFSET 0;

Quero selecionar os últimos /n/ itens de FileItems, em ordem decrescente, com as linhas filtradas por DISTINCTvalores de seriesName. A consulta acima apresenta erros ERROR: column "fileitems.dbid" must appear in the GROUP BY clause or be used in an aggregate function. Eu preciso do dbidvalor para então pegar a saída dessa consulta e JOINna tabela de origem para pegar o restante das colunas que eu estava.

Observe que esta é basicamente a gestalt da pergunta abaixo, com muitos detalhes irrelevantes removidos para maior clareza.


Pergunta original

Eu tenho um sistema que estou migrando do sqlite3 para o PostgreSQL, porque superei em grande parte o sqlite:

    SELECT
            d.dbId,
            d.dlState,
            d.sourceSite,
        [snip a bunch of rows]
            d.note

    FROM FileItems AS d
        JOIN
            ( SELECT dbId
                FROM FileItems
                WHERE sourceSite='{something}'
                GROUP BY seriesName
                ORDER BY MAX(retreivalTime) DESC
                LIMIT 100
                OFFSET 0
            ) AS di
            ON  di.dbId = d.dbId
    ORDER BY d.retreivalTime DESC;

Basicamente, quero selecionar os últimos n DISTINCTitens no banco de dados, onde a restrição distinta está em uma coluna e a ordem de classificação está em uma coluna diferente.

Infelizmente, a consulta acima, embora funcione bem no sqlite, apresenta erros no PostgreSQL com o erro psycopg2.ProgrammingError: column "fileitems.dbid" must appear in the GROUP BY clause or be used in an aggregate function.

Infelizmente, enquanto adicionar dbIdà cláusula GROUP BY corrige o problema (por exemplo GROUP BY seriesName,dbId, ), isso significa que a filtragem distinta nos resultados da consulta não funciona mais, pois dbidé a chave primária do banco de dados e, como tal, todos os valores são distintos.

Da leitura da documentação do Postgres , existe SELECT DISTINCT ON ({nnn}), mas isso requer que os resultados retornados sejam classificados por {nnn}.

Portanto, para fazer o que eu quero via SELECT DISTINCT ON, eu teria que consultar todos DISTINCT {nnn}e seus MAX(retreivalTime), classificar novamente em retreivalTimevez de {nnn}, então pegar o maior 100 e consultar usando-os na tabela para obter o restante das linhas, que eu gostaria de evitar, como o banco de dados tem ~ 175 mil linhas e ~ 14 mil valores distintos na seriesNamecoluna, eu só quero os 100 mais recentes e essa consulta é um pouco crítica para o desempenho (preciso de tempos de consulta < 1/2 segundo).

Minha suposição ingênua aqui é basicamente que o banco de dados precisa apenas iterar sobre cada linha em ordem decrescente de retreivalTime, e simplesmente parar depois de ver os LIMITitens, portanto, uma consulta de tabela completa não é ideal, mas não pretendo realmente entender como o banco de dados sistema otimiza internamente, e eu posso estar abordando isso completamente errado.

FWIW, ocasionalmente uso OFFSETvalores diferentes, mas longos tempos de consulta para casos em que deslocamento > ~ 500 é completamente aceitável. Basicamente, OFFSETé um mecanismo de paginação de baixa qualidade que me permite fugir sem precisar dedicar cursores de rolagem a cada conexão, e provavelmente o revisitarei em algum momento.


Ref- Pergunta que fiz há um mês que levou a esta consulta .


Bom, mais notas:

    SELECT
            d.dbId,
            d.dlState,
            d.sourceSite,
        [snip a bunch of rows]
            d.note

    FROM FileItems AS d
        JOIN
            ( SELECT seriesName, MAX(retreivalTime) AS max_retreivalTime
                FROM FileItems
                WHERE sourceSite='{something}'
                GROUP BY seriesName
                ORDER BY max_retreivalTime DESC
                LIMIT %s
                OFFSET %s
            ) AS di
            ON  di.seriesName = d.seriesName AND di.max_retreivalTime = d.retreivalTime
    ORDER BY d.retreivalTime DESC;

Funciona corretamente para a consulta conforme descrito, mas se eu remover a GROUP BYcláusula, ela falha (é opcional no meu aplicativo).

psycopg2.ProgrammingError: column "FileItems.seriesname" must appear in the GROUP BY clause or be used in an aggregate function

Acho que fundamentalmente não estou entendendo como as subconsultas funcionam no PostgreSQL. Onde eu estou errando? Eu tinha a impressão de que uma subconsulta é basicamente apenas uma função embutida, onde os resultados são apenas alimentados na consulta principal.

postgresql performance
  • 3 3 respostas
  • 39668 Views

3 respostas

  • Voted
  1. Best Answer
    Erwin Brandstetter
    2014-08-25T18:57:57+08:002014-08-25T18:57:57+08:00

    Linhas consistentes

    A questão importante que ainda não parece estar no seu radar:
    De cada conjunto de linhas para o mesmo seriesName, você quer as colunas de uma linha ou apenas quaisquer valores de várias linhas (que podem ou não vir da mesma linha )?

    Sua resposta faz o último, você combina o máximo dbidcom o máximo retreivaltime, que pode vir de uma linha diferente.

    Para obter linhas consistentesDISTINCT ON , use e envolva-as em uma subconsulta para ordenar o resultado de maneira diferente:

    SELECT * FROM (
       SELECT DISTINCT ON (seriesName)
              dbid, seriesName, retreivaltime
       FROM   FileItems
       WHERE  sourceSite = 'mk' 
       ORDER  BY seriesName, retreivaltime DESC NULLS LAST  -- latest retreivaltime
       ) sub
    ORDER BY retreivaltime DESC NULLS LAST
    LIMIT  100;
    

    Detalhes para DISTINCT ON:

    • Selecione a primeira linha em cada grupo GROUP BY?

    A parte: provavelmente deveria ser retrievalTime, ou melhor ainda: retrieval_time. Identificadores de maiúsculas e minúsculas sem aspas são uma fonte comum de confusão no Postgres.

    Melhor desempenho com rCTE

    Como estamos lidando com uma grande tabela aqui, precisaríamos de uma consulta que possa usar um índice, o que não é o caso da consulta acima (exceto WHERE sourceSite = 'mk')

    Em uma inspeção mais detalhada, seu problema parece ser um caso especial de uma varredura de índice solto . O Postgres não suporta varreduras de índice solto nativamente, mas pode ser emulado com um CTE recursivo . Há um exemplo de código para o caso simples no Postgres Wiki.

    Ver:

    • SELECT DISTINCT é mais lento que o esperado na minha tabela no PostgreSQL
    • Otimize a consulta GROUP BY para recuperar o registro mais recente por usuário

    Mas seu caso é mais complexo. Acho que encontrei uma variante para fazê-lo funcionar para você. Com base neste índice (sem WHERE sourceSite = 'mk')

    CREATE INDEX mi_special_full_idx ON MangaItems
    (retreivaltime DESC NULLS LAST, seriesName DESC NULLS LAST, dbid)
    

    Ou (com WHERE sourceSite = 'mk')

    CREATE INDEX mi_special_granulated_idx ON MangaItems
    (sourceSite, retreivaltime DESC NULLS LAST, seriesName DESC NULLS LAST, dbid)
    

    O primeiro índice pode ser usado para ambas as consultas, mas não é totalmente eficiente com a WHEREcondição adicional. O segundo índice é de uso muito limitado para a primeira consulta. Como você tem as duas variantes da consulta, considere criar os dois índices.

    Eu adicionei dbidno final para permitir varreduras somente de índice .

    Essa consulta com um CTE recursivo faz uso do índice. Testei com o Postgres 9.3 e funcionou para mim: sem varredura sequencial, todas as varreduras somente de índice :

    WITH RECURSIVE cte AS (
       (
       SELECT dbid, seriesName, retreivaltime, 1 AS rn, ARRAY[seriesName] AS arr
       FROM   MangaItems
       WHERE  sourceSite = 'mk'
       ORDER  BY retreivaltime DESC NULLS LAST, seriesName DESC NULLS LAST
       LIMIT  1
       )
       UNION ALL
       SELECT i.dbid, i.seriesName, i.retreivaltime, c.rn + 1, c.arr || i.seriesName
       FROM   cte c
       ,      LATERAL (
          SELECT dbid, seriesName, retreivaltime
          FROM   MangaItems
          WHERE (retreivaltime, seriesName) < (c.retreivaltime, c.seriesName)
          AND    sourceSite = 'mk'  -- repeat condition!
          AND    seriesName <> ALL(c.arr)
          ORDER  BY retreivaltime DESC NULLS LAST, seriesName DESC NULLS LAST
          LIMIT  1
          ) i
       WHERE  c.rn < 101
       )
    SELECT dbid
    FROM   cte
    ORDER  BY rn;
    

    Você precisa incluir seriesName em ORDER BY, pois retreivaltimenão é exclusivo. "Quase" único ainda não é único.

    Explicação

    • A consulta não recursiva começa com a última linha.

    • A consulta recursiva adiciona a próxima linha mais recente com uma seriesNameque ainda não está na lista, etc., até que tenhamos 100 linhas.

    • As partes essenciais são a JOINcondição (b.retreivaltime, b.seriesName) < (c.retreivaltime, c.seriesName)e a ORDER BYcláusula ORDER BY retreivaltime DESC NULLS LAST, seriesName DESC NULLS LAST. Ambos correspondem à ordem de classificação do índice, o que permite que a mágica aconteça.

    • Coletando seriesNameem uma matriz para descartar duplicatas. O custo para b.seriesName <> ALL(c.foo_arr)cresce progressivamente com o número de linhas, mas para apenas 100 linhas ainda é barato.

    • Apenas retornando dbidconforme esclarecido nos comentários.

    Alternativa com índices parciais:

    Já lidamos com problemas semelhantes antes. Aqui está uma solução completa altamente otimizada baseada em índices parciais e uma função de loop:

    • O índice espacial pode ajudar uma consulta "intervalo - ordem por - limite"

    Provavelmente o caminho mais rápido (exceto para uma visão materializada) se feito corretamente. Mas mais complexo.

    Visualização Materializada

    Como você não tem muitas operações de gravação e elas não são críticas ao desempenho, conforme indicado nos comentários (deve estar na pergunta), salve as n primeiras linhas pré-computadas em uma visualização materializada e atualize-a após alterações relevantes no tabela subjacente. Em vez disso, baseie suas consultas críticas de desempenho na visualização materializada.

    • Poderia ser apenas um mv "fino" dos últimos 1000 dbidou mais. Na consulta, junte-se à tabela original. Por exemplo, se o conteúdo às vezes é atualizado, mas as n primeiras linhas podem permanecer inalteradas.

    • Ou um mv "gordo" com linhas inteiras para retornar. Mais rápido, ainda. Precisa ser atualizado com mais frequência, obviamente.

    Detalhes no manual aqui e aqui .

    • 12
  2. Fake Name
    2014-08-24T23:31:23+08:002014-08-24T23:31:23+08:00

    Ok, eu li os documentos mais e agora entendo o problema pelo menos um pouco melhor.

    Basicamente, o que está acontecendo é que existem vários valores possíveis dbidcomo resultado da GROUP BY seriesNameagregação. Com SQLite e MySQL, aparentemente o mecanismo de banco de dados apenas escolhe um aleatoriamente (o que é absolutamente bom no meu aplicativo).

    No entanto, o PostgreSQL é muito mais conservador, então, em vez de escolher um valor aleatório, ele gera um erro.

    Uma maneira simples de fazer essa consulta funcionar é aplicar uma função de agregação ao valor relevante:

    SELECT MAX(dbid) AS mdbid, seriesName, MAX(retreivaltime) AS mrt
        FROM MangaItems 
        WHERE sourceSite='mk' 
        GROUP BY seriesName
        ORDER BY mrt DESC 
        LIMIT 100 
        OFFSET 0;
    

    Isso torna a saída da consulta totalmente qualificada e a consulta agora funciona.

    • 8
  3. Fake Name
    2014-09-02T19:23:46+08:002014-09-02T19:23:46+08:00

    Bem, na verdade acabei usando alguma lógica procedural fora do banco de dados para realizar o que queria fazer.

    Basicamente, 99% do tempo, eu quero os últimos 100.200 resultados. O planejador de consultas parece não otimizar para isso e, se o valor de OFFSETfor grande, meu filtro procedural será muito mais lento.

    De qualquer forma, usei um cursor nomeado para iterar manualmente as linhas no banco de dados, recuperando as linhas em grupos de algumas centenas. Em seguida, filtrei-os para distinção no código do meu aplicativo e fecho o cursor imediatamente depois de acumular o número de resultados distintos desejados.

    O makocódigo (basicamente python). Muitas instruções de depuração restantes.

    <%def name="fetchMangaItems(flags='', limit=100, offset=0, distinct=False, tableKey=None, seriesName=None)">
        <%
            if distinct and seriesName:
                raise ValueError("Cannot filter for distinct on a single series!")
    
            if flags:
                raise ValueError("TODO: Implement flag filtering!")
    
            whereStr, queryAdditionalArgs = buildWhereQuery(tableKey, None, seriesName=seriesName)
            params = tuple(queryAdditionalArgs)
    
    
            anonCur = sqlCon.cursor()
            anonCur.execute("BEGIN;")
    
            cur = sqlCon.cursor(name='test-cursor-1')
            cur.arraysize = 250
            query = '''
    
                SELECT
                        dbId,
                        dlState,
                        sourceSite,
                        sourceUrl,
                        retreivalTime,
                        sourceId,
                        seriesName,
                        fileName,
                        originName,
                        downloadPath,
                        flags,
                        tags,
                        note
    
                FROM MangaItems
                {query}
                ORDER BY retreivalTime DESC;'''.format(query=whereStr)
    
            start = time.time()
            print("time", start)
            print("Query = ", query)
            print("params = ", params)
            print("tableKey = ", tableKey)
    
            ret = cur.execute(query, params)
            print("Cursor ret = ", ret)
            # for item in cur:
            #   print("Row", item)
    
            seenItems = []
            rowsBuf = cur.fetchmany()
    
            rowsRead = 0
    
            while len(seenItems) < offset:
                if not rowsBuf:
                    rowsBuf = cur.fetchmany()
                row = rowsBuf.pop(0)
                rowsRead += 1
                if row[6] not in seenItems or not distinct:
                    seenItems.append(row[6])
    
            retRows = []
    
            while len(seenItems) < offset+limit:
                if not rowsBuf:
                    rowsBuf = cur.fetchmany()
                row = rowsBuf.pop(0)
                rowsRead += 1
                if row[6] not in seenItems or not distinct:
                    retRows.append(row)
                    seenItems.append(row[6])
    
            cur.close()
            anonCur.execute("COMMIT;")
    
            print("duration", time.time()-start)
            print("Rows used", rowsRead)
            print("Query complete!")
    
            return retRows
        %>
    
    </%def>
    

    Atualmente, isso recupera os 100.200 itens de série distintos mais recentes em 115 ~ 80 milissegundos (o tempo mais baixo é ao usar uma conexão local, em vez de um soquete TCP), enquanto processa aproximadamente 1.500 linhas.

    Venha comentários:

    • As linhas são lidas em blocos de 250.
    • buildWhereQuery is my own dynamic query builder. Yes, this is a horrible idea. Yes, I know about SQLalchemy et al. I wrote my own because A. this is a personal project that I don't expect to ever use outside of my home LAN, and B. It's a great way to learn SQL.
    • I may consider switching between the two query mechanisms as dependent on the value of offset. It looks like when offset > 1000 and I'm filtering for distinct items, this approach starts to exceed the time required for procedures like the ones in @ErwinBrandstetter's answer.
    • @ErwinBrandstetter's answer is still a much better general solution. This is only better in one very specific case.
    • I had to use two cursors, for some odd reason. You can't create a named cursor unless you're in a transaction, but you can't start a transaction without a cursor (note - this is with autocommit mode off). I have to instantiate a anonymous cursor, issue some SQL (just a BEGIN, here), create my named cursor, use it, close it, and finally commit with the anonymous cursor.
    • This could probably be done entirely in PL/pgSQL, and the result would probably be even faster, but I know python much better.
    • 1

relate perguntas

  • Sequências Biológicas do UniProt no PostgreSQL

  • Como determinar se um Índice é necessário ou necessário

  • Onde posso encontrar o log lento do mysql?

  • Como posso otimizar um mysqldump de um banco de dados grande?

  • Qual é a diferença entre a replicação do PostgreSQL 9.0 e o Slony-I?

Sidebar

Stats

  • Perguntas 205573
  • respostas 270741
  • best respostas 135370
  • utilizador 68524
  • Highest score
  • respostas
  • Marko Smith

    conectar ao servidor PostgreSQL: FATAL: nenhuma entrada pg_hba.conf para o host

    • 12 respostas
  • Marko Smith

    Como fazer a saída do sqlplus aparecer em uma linha?

    • 3 respostas
  • Marko Smith

    Selecione qual tem data máxima ou data mais recente

    • 3 respostas
  • Marko Smith

    Como faço para listar todos os esquemas no PostgreSQL?

    • 4 respostas
  • Marko Smith

    Listar todas as colunas de uma tabela especificada

    • 5 respostas
  • Marko Smith

    Como usar o sqlplus para se conectar a um banco de dados Oracle localizado em outro host sem modificar meu próprio tnsnames.ora

    • 4 respostas
  • Marko Smith

    Como você mysqldump tabela (s) específica (s)?

    • 4 respostas
  • Marko Smith

    Listar os privilégios do banco de dados usando o psql

    • 10 respostas
  • Marko Smith

    Como inserir valores em uma tabela de uma consulta de seleção no PostgreSQL?

    • 4 respostas
  • Marko Smith

    Como faço para listar todos os bancos de dados e tabelas usando o psql?

    • 7 respostas
  • Martin Hope
    Jin conectar ao servidor PostgreSQL: FATAL: nenhuma entrada pg_hba.conf para o host 2014-12-02 02:54:58 +0800 CST
  • Martin Hope
    Stéphane Como faço para listar todos os esquemas no PostgreSQL? 2013-04-16 11:19:16 +0800 CST
  • Martin Hope
    Mike Walsh Por que o log de transações continua crescendo ou fica sem espaço? 2012-12-05 18:11:22 +0800 CST
  • Martin Hope
    Stephane Rolland Listar todas as colunas de uma tabela especificada 2012-08-14 04:44:44 +0800 CST
  • Martin Hope
    haxney O MySQL pode realizar consultas razoavelmente em bilhões de linhas? 2012-07-03 11:36:13 +0800 CST
  • Martin Hope
    qazwsx Como posso monitorar o andamento de uma importação de um arquivo .sql grande? 2012-05-03 08:54:41 +0800 CST
  • Martin Hope
    markdorison Como você mysqldump tabela (s) específica (s)? 2011-12-17 12:39:37 +0800 CST
  • Martin Hope
    Jonas Como posso cronometrar consultas SQL usando psql? 2011-06-04 02:22:54 +0800 CST
  • Martin Hope
    Jonas Como inserir valores em uma tabela de uma consulta de seleção no PostgreSQL? 2011-05-28 00:33:05 +0800 CST
  • Martin Hope
    Jonas Como faço para listar todos os bancos de dados e tabelas usando o psql? 2011-02-18 00:45:49 +0800 CST

Hot tag

sql-server mysql postgresql sql-server-2014 sql-server-2016 oracle sql-server-2008 database-design query-performance sql-server-2017

Explore

  • Início
  • Perguntas
    • Recentes
    • Highest score
  • tag
  • help

Footer

AskOverflow.Dev

About Us

  • About Us
  • Contact Us

Legal Stuff

  • Privacy Policy

Language

  • Pt
  • Server
  • Unix

© 2023 AskOverflow.DEV All Rights Reserve