Estou usando o Rails 5. Tenho o seguinte modelo ...
class Order < ApplicationRecord
...
has_many :line_items, :dependent => :destroy
O modelo LineItem possui um atributo, "discount_applied". Gostaria de devolver todos os pedidos em que existem zero instâncias de um item de linha, com o campo "discount_applied" não sendo nulo. Como escrevo esse método localizador?
Respostas:
Não é eficiente, mas achei que poderia resolver o seu problema:
Atualização :
Em vez de encontrar pedidos com todos os seus itens de linha sem desconto, podemos excluir todos os pedidos com itens de linha com desconto aplicado no resultado da saída. Isso pode ser feito com subconsulta dentro da cláusula where:
Você pode combiná-los em um único método localizador:
Espero que isto ajude
fonte
Antes de tudo, isso realmente depende se você deseja ou não usar uma abordagem Arel pura ou se o SQL é bom. O primeiro é IMO somente recomendável se você pretende criar uma biblioteca, mas desnecessário se estiver criando um aplicativo em que, na realidade, é altamente improvável que você esteja alterando seu DBMS ao longo do caminho (e, se o fizer, alterando algumas consultas manuais provavelmente serão o menor dos seus problemas).
Supondo que o uso do SQL seja bom, a solução mais simples que deve funcionar em praticamente todos os bancos de dados é:
Isso também deve funcionar praticamente em qualquer lugar (e tem um pouco mais de Arel e menos SQL manual):
Dependendo do DBMS específico (mais especificamente: seu respectivo otimizador de consulta), um ou outro pode ter melhor desempenho.
Espero que ajude.
fonte
Um código possível
Eu aconselho a se familiarizar com a documentação do AR para Métodos de Consulta.
Atualizar
Isso parece estar mais interessado do que eu pensava inicialmente. E mais complicado, então não poderei fornecer um código funcional. Mas eu procuraria uma solução usando
LineItem.group(order_id).having(discount_applied: nil)
, que deve fornecer uma coleção de itens de linha e depois usá-la como subconsulta para encontrar pedidos relacionados.fonte
Se você deseja que todos os registros onde discount_applied seja nulo, então:
Order.includes(:line_items).where.not(line_items: {discount_applied: nil})
(o uso inclui para evitar o problema n + 1) ou
fonte
Aqui está a solução para o seu problema
A primeira consulta retornará IDs de
Orders
pelo menos umline_item
tendodiscount_applied
. A segunda consulta retornará tudoorders
onde houver zero instâncias de umline_item
tendo odiscount_applied
.fonte
Eu usaria o
NOT EXISTS
recurso do SQL, que está disponível pelo menos no MySQL e no PostgreSQLdeve ficar assim
fonte
Se entendi corretamente, você deseja obter todos os pedidos para os quais nenhum item de linha (se houver) tem um desconto aplicado.
Uma maneira de obter esses pedidos
ActiveRecord
seria o seguinte:Aqui está uma breve explicação de como isso funciona:
left_outer_joins
, desde que você não acesse os itens de linha de cada pedido. Você também pode usarleft_joins
, que é um alias.Order
instância, adicione.eager_load(:line_items)
à cadeia que impedirá a realização de uma consulta adicional para cada pedido (N + 1), ou seja,order.line_items.each
em uma exibição.distinct
é essencial para garantir que os pedidos sejam incluídos apenas uma vez no resultado.Atualizar
Minha solução anterior foi verificar apenas
discount_applied IS NULL
pelo menos um item de linha, não todos. A consulta a seguir deve retornar os pedidos necessários.Isto é o que está acontecendo:
orders LEFT OUTER JOIN line_items
) para incluir pedidos sem itens associados.Order
objeto, independentemente de quantos itens ele possui (GROUP BY recipes.id
).HAVING (COUNT(line_items.discount_applied) = 0)
).Espero que ajude.
fonte
Você não pode fazer isso de forma eficiente com os trilhos clássicos left_joins, mas a junção esquerda do sql foi criada para lidar com esses casos
Uma junção interna simples retornará todos os pedidos, juntamente com todos os itens de linha,
mas se não houver itens de linha para esse pedido, a ordem será ignorada (como um falso onde)
Com a junção esquerda, se nenhum item de linha for encontrado, o sql o unirá a um entrada vazia para mantê-lo
Então, partimos para os line_items que não queremos e localizamos todos os pedidos com um line_items vazio
E evite todo o código com
where(id: pluck(:id))
ouhaving("COUNT(*) = 0")
, no dia isso matará seu banco de dadosfonte