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 / 160237
Accepted
joanolo
joanolo
Asked: 2017-01-07 15:32:53 +0800 CST2017-01-07 15:32:53 +0800 CST 2017-01-07 15:32:53 +0800 CST

Faz sentido ter CASE .. END em um ORDER BY?

  • 772

Consultas como SELECT * FROM t ORDER BY case when _parameter='a' then column_a end, case when _parameter='b' then column_b endsão possíveis, mas: Esta é uma boa prática?

É comum usar parâmetros na parte WHERE das consultas e ter algumas colunas computadas na parte SELECT, mas não tão comum para parametrizar a cláusula ORDER BY.

Digamos que temos um aplicativo que lista carros usados ​​(à la CraigsList). A lista de carros pode ser classificada por preço ou cor. temos uma função que, dada uma certa quantidade de parâmetros (digamos, faixa de preço, cor e critérios de classificação, por exemplo) retorna um conjunto de registros com os resultados.

Para torná-lo concreto, vamos supor que carsestão todos na tabela a seguir:

CREATE TABLE cars
(
  car_id serial NOT NULL PRIMARY KEY,  /* arbitrary anonymous key */
  make text NOT NULL,       /* unnormalized, for the sake of simplicity */
  model text NOT NULL,      /* unnormalized, for the sake of simplicity */
  year integer,             /* may be null, meaning unknown */
  euro_price numeric(12,2), /* may be null, meaning seller did not disclose */
  colour text               /* may be null, meaning unknown */
) ;

A tabela teria índices para a maioria das colunas...

CREATE INDEX cars_colour_idx
  ON cars (colour);
CREATE INDEX cars_price_idx
  ON cars (price);
/* etc. */

E tenha algumas enumerações de commodities:

CREATE TYPE car_sorting_criteria AS ENUM
   ('price',
    'colour');

... e alguns dados de exemplo

INSERT INTO cars.cars (make, model, year, euro_price, colour)

VALUES 
    ('Ford',   'Mondeo',   1990,  2000.00, 'green'),
    ('Audi',   'A3',       2005,  2500.00, 'golden magenta'),
    ('Seat',   'Ibiza',    2012, 12500.00, 'dark blue'),
    ('Fiat',   'Punto',    2014,     NULL, 'yellow'),
    ('Fiat',   '500',      2010,  7500.00, 'blueish'),
    ('Toyota', 'Avensis',  NULL,  9500.00, 'brown'), 
    ('Lexus',  'CT200h',   2012, 12500.00, 'dark whitish'), 
    ('Lexus',  'NX300h',   2013, 22500.00, NULL) ;

O tipo de consultas que vamos fazer são do estilo:

SELECT
    make, model, year, euro_price, colour
FROM
    cars.cars
WHERE
    euro_price between 7500 and 9500 
ORDER BY
    colour ;

Gostaríamos de ter consultas desse estilo em uma função:

CREATE or REPLACE FUNCTION get_car_list
   (IN _colour    text, 
    IN _min_price numeric, 
    IN _max_price numeric, 
    IN _sorting_criterium car_sorting_criteria) 
RETURNS record AS
$BODY$
      SELECT
          make, model, year, euro_price, colour
      FROM
          cars
      WHERE
           euro_price between _min_price and _max_price
           AND colour = _colour
      ORDER BY
          CASE WHEN _sorting_criterium = 'colour' THEN
            colour
          END,
          CASE WHEN _sorting_criterium = 'price' THEN
            euro_price
          END 
$BODY$
LANGUAGE SQL ;

Em vez dessa abordagem, o SQL nessa função poderia ser gerado dinamicamente (em PL/pgSQL) como uma string e depois EXECUTADO.

Podemos sentir algumas limitações, vantagens e desvantagens com qualquer uma das abordagens:

  1. Dentro de uma função, descobrir qual é o plano de consulta para uma determinada instrução é difícil (se possível). No entanto, tendemos a usar funções principalmente quando vamos usar algo com bastante frequência.
  2. Erros em SQL estático serão capturados (principalmente) quando a função for compilada ou quando for chamada pela primeira vez.
  3. Erros em SQL dinâmico só serão detectados (principalmente) depois que a função for compilada e todos os caminhos de execução forem verificados (ou seja: o número de testes a serem executados na função pode ser muito alto).
  4. Uma consulta paramétrica como a exposta provavelmente será menos eficiente do que uma gerada dinamicamente; no entanto, o executor terá um trabalho mais difícil de analisar / criar a árvore de consulta / decidir todas as vezes (o que pode afetar a eficiência na direção oposta).

Pergunta:

Como "obter o melhor dos dois mundos" (se possível)? [Eficiência + Verificações do compilador + Depuração fácil + Otimização fácil]

NOTA: isso deve ser executado no PostgreSQL 9.6.

postgresql functions
  • 4 4 respostas
  • 12721 Views

4 respostas

  • Voted
  1. Evan Carroll
    2017-01-11T15:57:21+08:002017-01-11T15:57:21+08:00

    Três pontos que eu levantaria,

    1. Esta é uma consulta muito básica, mesmo em sua versão aplicada. Crie um VIEWpara isso. Faça com que seus usuários personalizem as WHEREcondições usando o arquivo VIEW. As funções são caixas pretas para o planejador de consulta. É horrível usá-los dentro de outras funções, só que SQLé embutido. E as funções dinâmicas não obtêm planos em cache.
    2. Se você quiser continuar usando plpgsql, use RETURNS QUERY(ou RETURNS QUERY EXECUTE) not RETURNS SETOF. Não há razão para usar RETURNS SETOFcom uma classificação. Tem que ser armazenado em buffer de qualquer maneira, afaik. Você encontrará problemas com qualquer um deles se o conjunto de resultados for maior que work_mem.
    3. Não tenho certeza do que seu aplicativo está escrito. Presumo que web. Eu estive na indústria automotiva por uma década, porém, fiz muitas coisas como o Craigslist e ferramentas de postagem para o Craigslist. "Não classifique coisas para usuários no banco de dados" geralmente é uma boa regra de ouro. Não há razão para isso. Solte essas coisas em JSON e deixe-os lidar com isso no navegador. A menos que você esteja mostrando mais de 1000 linhas, nem vale a pena pensar nisso. Basta considerar o tempo de ida e volta de uma torre de telefonia celular. Você nunca terá esse tempo de volta se perguntando sobre esse problema.

    Avançando, eu até consideraria agrupar um serviço como o PostgREST, que lida com pedidos completamente arbitrários,

    If you care where nulls are sorted, add nullsfirst or nullslast:
    
    GET /people?order=age.nullsfirst
    GET /people?order=age.desc.nullslast
    
    • 3
  2. Best Answer
    Erwin Brandstetter
    2017-01-17T17:21:47+08:002017-01-17T17:21:47+08:00

    Resposta geral

    Primeiro, quero abordar a ambigüidade na premissa:

    É comum usar parâmetros na parte WHERE das consultas e ter algumas colunas computadas na parte SELECT, mas não tão comum para parametrizar a cláusula ORDER BY.

    As colunas calculadas na SELECTparte quase nunca são relevantes para o plano de consulta ou desempenho. Mas "na WHEREparte" é ambíguo.

    É comum parametrizar valores na WHEREcláusula, o que funciona para instruções preparadas. (E o PL/pgSQL trabalha com instruções preparadas internamente.) Um plano de consulta genérico geralmente faz sentido independentemente dos valores fornecidos . Ou seja, a menos que as tabelas tenham uma distribuição de dados muito desigual, mas como o Postgres 9.2 PL/pgSQL replaneja as consultas algumas vezes para testar se o plano genérico parece bom o suficiente:

    • Desempenho do procedimento armazenado do PostgreSQL

    Mas não é tão comum parametrizar predicados inteiros (incluindo identificadores ) na WHEREcláusula, o que é impossível com instruções preparadas para começar. Você precisa de SQL dinâmico comEXECUTE , ou monta as strings de consulta no cliente.

    As expressões dinâmicas ORDER BYestão em algum lugar entre as duas. Você pode fazer isso com uma CASEexpressão, mas isso é muito difícil de otimizar em geral. O Postgres pode usar índices com um simples ORDER BY, mas não com CASEexpressões que escondam a eventual ordem de classificação. O planejador é inteligente, mas não uma IA. Dependendo do restante da consulta ( ORDER BYpode ser relevante para o plano ou não - é relevante no seu exemplo), você pode acabar com um plano de consulta abaixo do ideal o tempo todo .
    Além disso, você adiciona o menor custo da CASE(s) expressão(ões). E no seu exemplo particular também para váriasORDER BY colunas inúteis .

    Normalmente, SQL dinâmico com EXECUTEé mais rápido ou muito mais rápido para isso.

    A capacidade de manutenção não deve ser um problema se você mantiver um formato de código claro e legível no corpo da função.

    Corrigir função de demonstração

    A função na pergunta está quebrada . O tipo de retorno é definido para retornar um registro anônimo:

    RETURNS record AS
    

    Mas a consulta na verdade retorna um conjunto de registros, teria que ser:

    RETURNS SETOF record AS
    

    Mas isso ainda é inútil. Você teria que fornecer uma lista de definição de coluna com cada chamada. Sua consulta retorna colunas de tipo conhecido. Declare o tipo de retorno de acordo! Estou supondo aqui, use tipos de dados reais de colunas/expressões retornadas:

    RETURNS TABLE (make text, model text, year int, euro_price int, colour text) AS
    

    Eu uso os mesmos nomes de coluna por conveniência. As colunas na RETURNS TABLEcláusula são efetivamente OUTparâmetros, visíveis em todas as instruções SQL no corpo (mas não dentro EXECUTEde ). Portanto, qualifique colunas em consultas no corpo da função para evitar possíveis conflitos de nomenclatura. A função de demonstração funcionaria assim:

    CREATE or REPLACE FUNCTION get_car_list (
        _colour            text, 
        _min_price         numeric, 
        _max_price         numeric, 
        _sorting_criterium car_sorting_criteria) 
      RETURNS TABLE (make text, model text, year int, euro_price numeric, colour text) AS
    $func$
          SELECT c.make, c.model, c.year, c.euro_price, c.colour
          FROM   cars c
          WHERE  c.euro_price BETWEEN _min_price AND _max_price
          AND    c.colour = _colour
          ORDER  BY CASE WHEN _sorting_criterium = 'colour' THEN c.colour     END
                  , CASE WHEN _sorting_criterium = 'price'  THEN c.euro_price END;
    $func$  LANGUAGE sql;
    

    Não confunda a RETURNSpalavra-chave na declaração da função com o RETURNcomando plpgsql como Evan fez em sua resposta . Detalhes:

    • Diferença entre return next e return record

    Dificuldade geral da consulta de exemplo

    Predicado em algumas colunas (ainda pior: predicados de intervalo ), outras colunas em ORDER BY, isso já é difícil de otimizar. Mas você mencionou em um comentário :

    Os conjuntos de resultados reais podem estar na ordem de várias linhas 1.000 (e, portanto, classificados e paginados em partes menores do lado do servidor)

    Portanto, você adicionará LIMITe OFFSETa essas consultas, retornando as n "melhores" correspondências primeiro. Ou alguma técnica de paginação mais inteligente:

    • Melhore o desempenho da ordem com colunas de várias tabelas

    Você precisa de um índice correspondente para tornar isso rápido. Não vejo como isso poderia funcionar com CASEexpressões em ORDER BY.

    Considerar:

    • Otimize uma consulta com LIMIT pequeno, predicado em uma coluna e ordenado por outra
    • O índice espacial pode ajudar uma consulta "range - order by - limit"
    • 3
  3. Lucas
    2017-01-08T01:08:07+08:002017-01-08T01:08:07+08:00

    Não faz sentido ter um caso na ordem de uma função no caso descrito

    Os índices não serão usados ​​na cláusula order by dessa forma. O mecanismo de banco de dados terá que calcular a expressão de caso para cada linha e depois classificar. E essa técnica não é extensível para junções e filtros dinâmicos.

    Eu iria com uma geração dinâmica de SQL...

    Gerando SQL dinâmico em uma função PostgreSQL

    Você pode fazer algo assim:

      create or replace function fn_test(car_sort_option) returns text as $$
      declare
          xsql text;
          xresult text;
      begin
    
        xsql = 'select array_to_json(array_agg(c)) from cars';
    
        if $1 = 'colour' then 
          xsql = xsql || ' order by colour';
        elsif $1 = 'model' then  
          xsql = xsql || ' order by model';
        else 
          raise exception 'invalid parameter: sort option=% is invalid', $1; 
        end if;
    
        execute xsql into xresult;
    
        return xresult;
      end
    
      $$ language 'plpgsql';
    

    Não costumo fazer SQL dinâmico para algo tão específico da camada de apresentação dentro do PL/pgSQL. Normalmente prefiro deixar essa construção SQL em PHP ou Java. Mas para muitas outras coisas eu faço muito SQL dinâmico dentro do PL/pgSQL. Principalmente para particionamento, manutenção de banco de dados, implementação de fluxo de trabalho e controles de consistência de dados.

    Eu descobri que esta política deixa o código mais limpo, tem melhor uso de índices e é utilizável em SQL dinâmico mais complicado. O uso do índice é crítico no meu mundo porque trabalho com análises em bancos de dados com vários bilhões de registros e preciso de respostas rápidas.

    Notas adicionais sobre por que é mais comum gerar SQL dinâmico fora do banco de dados (a seguir comentários)

    Isso acontece porque a maioria do SQL dinâmico só é necessário quando um nome de tabela ou um nome de coluna é dinâmico. Para todo o resto, as consultas parametrizadas funcionarão bem. O SQL dinâmico é necessário para a maioria dos relatórios. Não apenas porque você precisará escolher a coluna de ordem de classificação, mas na maioria das vezes você precisará incluir filtros e colunas dinamicamente. Muitos desenvolvedores continuam relatando SQL nos programas de relatório na camada de apresentação ou nos lotes que os geram, caso em que o SQL dinâmico também será gerado fora do banco de dados.

    • 1
  4. bentaly
    2017-01-12T11:18:01+08:002017-01-12T11:18:01+08:00

    Eu usaria SQL dinâmico neste caso.
    A consulta dinâmica é menos complexa e pode render um melhor plano de execução.
    Pela minha experiência, qualquer ganho com o armazenamento em cache do plano ou tempo de análise mais curto é insignificante perto disso.

    Eu costumava seguir a regra de "Usar SQL dinâmico somente se o SQL estático não funcionar". Hoje, como regra geral, costumo escolhê-lo de acordo com a cláusula da consulta SQL em que preciso de flexibilidade:

    • ONDE- SQL estático
    • FROM, ORDER BY, GROUP BY - SQL dinâmico
    • SELECIONE - Alternativo

    É verdade que é um pouco mais difícil de depurar, mas o uso de algumas práticas recomendadas deve ajudar a reduzir a lacuna.
    Por exemplo, em plpgsql você pode usar uma notação $ + REPLACE + RAISE NOTICE:

    -- Write the entire query using $ for replacements.
    -- Don't use || operator.
    -- This makes the dynamic query clearer to read and easier to maintain and debug.
    v_sql:= 'SELECT <some columns> FROM tbl ORDER BY $sorting$';
    
    v_sql:= REPLACE(v_sql,
      '$sorting$',
      CASE condition
        WHEN value1 THEN 'colX'
        WHEN value2 THEN 'colY'
      END);
    
    -- Debug the query when running
    RAISE NOTICE '%', v_sql;
    RETURN QUERY EXECUTE v_sql;  
    

    Você deve ser capaz de interceptar quaisquer erros de sintaxe nas primeiras execuções.
    Erros lógicos serão tão fáceis/difíceis de encontrar quanto com SQL estático.

    • 1

relate perguntas

  • Posso ativar o PITR depois que o banco de dados foi usado

  • Práticas recomendadas para executar a replicação atrasada do deslocamento de tempo

  • Os procedimentos armazenados impedem a injeção de SQL?

  • Sequências Biológicas do UniProt no PostgreSQL

  • 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