Skip to content

Instantly share code, notes, and snippets.

@bergfelipe
Forked from lucasslemos/consultas.rb
Created October 13, 2022 13:50
Show Gist options
  • Select an option

  • Save bergfelipe/49cc1cce00fdfaee1cf8441d807ecd3b to your computer and use it in GitHub Desktop.

Select an option

Save bergfelipe/49cc1cce00fdfaee1cf8441d807ecd3b to your computer and use it in GitHub Desktop.
Active Record
###########################################
where pt 1
###########################################
Transfer.where(amount_cents: 1500)
O Active Records cria cláusulas IN na consulta SQL quando passamos um array de valores
Transfer.where(amount_cents: [1000, 1500, 2000]) #SELECT "products".* FROM transfers WHERE "transfer.amount_cents" IN (100, 150, 200)
você pode passar também um range como parâmetro:
Transfer.where(amount_cents: 1000..10000)
Você pode passar uma string como paramêtro, e escrever sua condição SQL na mão, por exemplo:
User.where('name LIKE ? AND email = ?', "%#{var_name}%", email)
Nesse caso as interrogações são substituídas pelas variáveis na mesma ordem que foram inseridas e declaradas(tome cuidado)
A melhor abordagem para o exemplo acima é fazer um bind das suas variáveis:
User.where('name LIKE :name AND email = :email', name: "%#{var}%", email: var_email) # uma vantagem é poder usar a mesma variável em locais diferentes da query
###########################################
where pt 2
###########################################
User.where(name: "John").or(User.where(last_name: 'Miller').first)
SELECT * FROM users WHERE users.name = 'John' OR users.last_name = 'Miller'
User.where(name: "John").where(gender: 'Male')
SELECT * FROM users users.name = 'John' AND users.last_name = 'Miller'
Você também pode criar uma busca de negação usando .where.not
Company.where.not('balance_cents > 10000')
O .where.not também aceita arrays e strings de consultas SQL
###########################################
Order
###########################################
.order(*clauses)
Company.order('created_at desc')
User.order(:created_at)
Transfer.order(:created_at).take(3) # Em ambos casos, por default, a listagem é feita de forma ascendente
User.order(age: :desc) # lista de forma decrescente
###########################################
select vs pluck
###########################################
.select(campos) e .pluck(campos)
Por padrão o Active Records faz <SELECT *> nas chamadas, e muitas vezes precisamos de campos específicos da tabela, o select resolve esse problema para nós:
O correto é usar o .select quando você precisa de um ActiveRecord::Relation
Product.select(:price) ######terminar
User.select(:first_name, :last_name).where("users"."age" >= 30)
User.where(status: 'offline').select(:email)
User.select(:email)
Parecido com o .select, o método .pluck difere apenas no seu retorno, onde ao invés de um ActiveRecord::Relation, você recebe um array de de strings
O pluck possui uma performance melhor do que o .select devido a isso:
Company.where(status: "Active").pluck(:name) #will return something like ["Company A", "Company B"]
Company.where(status: "Active").pluck(:id, :name) #will return something like [ [1, "Company A"], [2, "Company B"] ]
###########################################
each
###########################################
.each
No ruby existe o loop "for", que aprendemos em outras linguagens, porém para iterar um array é mais aconselhável usarmos o .each
juices_flavors = ['orange', 'grape', 'lemon']
juice_flavors.each { |flavor| puts flavor.upcase }
o .each não altera o valor dos elementos iterados
n = [10, 20, 30]
n.each do |number|
puts number * 2
end
puts n # o .each não altera o valor da variável, então be careful =)
###########################################
find_each
###########################################
.find_each
Iterar uma grande quantidade de records, pode ser prejudicial, trazendo lentidão para seu servidor e podendo causar até um crash dependendo da quantidade de records.
Para evitar isso o ActiveRecords possui um método find_each, que possui uma opção batch_size. Digamos que você possui 10.000 records na tabela Transfer, usando find_each sua busca ficaria assim:
Transfer.all.find_each(batch_size: 2500) do |transfer|
puts transfer.user.name
end
# A busca retornará 4 batches de 2500 records
Ao invés de carregar um grande número de records, o find_each carregará cada batch de uma vez, e após seu uso irá "jogar fora" da memória o batch usado liberando memória ram. Dessa forma você até aumenta o número de queries no seu banco de dados, porém você ganha performance e desempenho. Lembre-se de usá-lo apenas quando tiver um grande número de records a serem carregados.
###########################################
map
###########################################
.map
As maiores diferenças entre o map e o each, é que no .map é que no .map você recebe um Array com o resultado do bloco aplicado para cada elemento recebido
então o loop Transfer.all.map { |transfer| transfer.amount_cents * 2 } retorna um array pegando cada value de um payment multiplicado por 2.
você pode usar o map pra retornar campos específicos de um Model com esse shortcut
User.all.map(&:email)
porém por questões de beleza e performance, nesses casos sempre é melhor usar o método .pluck
User.pluck(:email)
Usando .pluck você tem uma performance cerca de 8 vezes maior do que o map nesse caso
###########################################
select loop
###########################################
.select
O método .select também aceita um bloco de iteração. Ele executa a lógica passada dentro do bloco para cada elemento do Array, caso a expressão retorne true ele adiciona o elemento ao Array de retorno
t = Transfer.select { |transfer| transfer.amount_cents > 5000 }
[#<Transfer id: "1", user_id: "15", amount_cents: 8087, created_at: "2019-03-04 12:49:02", updated_at: "2019-03-04 12:49:02">, #<Transfer id: "2", user_id: "19", amount_cents: 8457, created_at: "2019-03-04 12:49:03", updated_at: "2019-03-04 12:49:03">]
Como já vimos o .select sempre retorna um Array de ActiveRecord::Relation, caso você precise de retornar um campo específico, você pode maximizar a performance da sua query passando um pluck
t = Transfer.select { |transfer| transfer.amount_cents > 5000 }.pluck(:amount_cents)
['8534', '8457']
###########################################
n+1 parte 1
###########################################
Avoid N+1 queries
entendendo;
# Assuming User has the following association
class User
has_many :transfers
end
# Running this activerecord code
users = User.all
users.each do |user|
user.transfers
end
# Will generate n+1 queries, where n is the number of users, namely
# SELECT * FROM users;
# SELECT * FROM transfers WHERE transfers.user_id = 1;
# SELECT * FROM transfers WHERE transfers.user_id = 2;
# SELECT * FROM transfers WHERE transfers.user_id = 3;
# .....
# SELECT * FROM transfers WHERE posts.user_id = n;
###########################################
sum
###########################################
.sum
Quando você precisa somar uma coluna do seu banco de dados(muito útil para mostrar informações em dashboards) você pode usar o método .sum
Transfer.sum(:amount_cents)
Se você quiser uma condição, use dessa forma:
Payment.where("payments.amount_cents > 5000").sum(:amount_cents)
#Nesse exemplo passamos um where usando uma string para usar o simbolo >(maior) do SQL
você pode fazer cálculos usando o sum, digamos que você salva o value de um Payment em centavos, para obter sua forma no formato correto, você poderia fazer
user.transfers.sum(:amount_cents) / 100.0 #é uma boa prática armazenar valores monetários em centavos, uma boa gem para lidar com esse tipo de field é a gem money-rails
###########################################
find_or_create_by
###########################################
.find_or_create_by E .find_or_initialize_by
Esses métodos são muito úteis quando você não quer criar dados duplicados no seu banco de dados.
User.find_or_create_by(name: 'Bruce', last_name: 'Wayne') # O record só será criado se não existir um User com name Bruce com sobrenome Wayne
a diferença entre os 2 métodos acima é que o _create_by faz um Model.create se o find não der match, o _initialize_by faz um Model.new
user = User.find_or_initialize_by(name: 'Bruce', last_name: 'Wayne') # user.persisted? -> false
Caso você queira verificar a existência apenas de um campo para criar um record com vários outros, você pode usar o .create_with
user = User.create_with(name: 'Bruce', last_name: 'Wayne', power: 'money').find_or_create_by(hero: 'Batman')
user = User.create_with(name: 'Bruce', last_name: 'Wayne', power: 'money').find_or_initialize_by(hero: 'Batman')
Passando o create_with com todos os outros dados, o método só irá verificar a existencia de um hero 'Batman' no database, caso não encontre, ele irá ser criado ou inicializado com todos dados passados dentro do create_with e do .find_or_create_by/.find_or_initialize_by
###########################################
bulk
###########################################
Tratando Records em massa
Tratar Records em massa(bulk) é uma ótima maneira de ganhar performance, pois dessa forma, você faz apenas uma chamada no seu database ao invés de criar record por record dentro de um loop.
.delete_all
Você pode destruir records específicos com o método .delete_all, tenha cuidado ao usar esse tipo de método para não se arrepender depois, mas vamos à um exemplo
sleeping_users = User.where(status: 'sleeping')
sleeping_users.delete_all
# DELETE FROM users WHERE users.status = 'sleeping'; --- Apenas 1 query
-----
.update_all
a mesma ideia para updates:
best_payers = User.where(status: 'top_payer')
best_payers.update_all(grade: 5) <-- ou --> User.where(status: 'top_payer').update_all(grade: 5)
#
-----
Criando em massa
O ActiveRecord::Base#create aceita um hash de valores, o que torna muito mais prático a criação de records em massa quando possível:
new_members = [
{ first_name: "Nasus", email: "nasus@bla.net" },
{ first_name: "Xogath", email: "xo@bla.com" },
{ first_name: "Annie", email: "annie@tibbers.com" }
]
Member.create(new_members)
INSERT INTO users (name, email) VALUES ('Nasus', 'nasus@bla.net'), ('Xogath', 'xo@bla.com'), ('Annie', 'annie@tibbers.org')
###########################################
joins parte 1
###########################################
joins no Rails.
Restituicao.joins(:conta_bancaria).where(conta_bancaria: { cpf_cnpj: doc }
Caso você não conheça o conceito de Joins SQL, por favor leia o artigo do devmedia: https://www.devmedia.com.br/sql-join-entenda-como-funciona-o-retorno-dos-dados/31006
Se temos um Model Band que belongs_to Genre podemos fazer
Band.joins(:genre) # essa query retorna todas bandas que possuem um gênero
Genre.joins(:bands) # retorna todos gêneros que possuem bandas
Band.joins(:genre).uniq # retorna todas bandas que possuem um gênero sem duplicar bandas que já foram listadas
digamos que uma banda has_many musics, você também pode filtrar por multiplas associações
Band.joins(:genre, :musics) # retorna todas bandas que possuem um gênero e pelo menos uma música cadastrada
o .uniq continua sendo útil para evitar duplicações nesse caso
Band.joins(:genre, :musics).uniq
Ao ter uma associação considerada nested você também consegue fazer joins dessa forma
Genre.joins(bands: :musics) # retorna todos os gêneros que possuem bandas que tem músicas cadastradas
você pode fazer esse join para mais de um nested attributes também.
Genre.joins(bands: [:musics, :reviews]) # retorna todos os gêneros que possuem bandas que tem músicas e reviews cadastrados
###########################################
joins parte 2
###########################################
joins com .where
A principal ideia do join sempre é pegar um model baseado em uma condição de outro
exemplo:
Genre.joins(:bands).where('bands.description = null')
# retorna todos os gêneros que possuem bandas com description vazia
Band.joins(:musics).where(musics: {duration: 9..16}).uniq
# retorna todas as bandas com músicas de duração entre 9 e 16 minutos sem duplicações.
###########################################
métodos uteis
###########################################
métodos uteis
.exists?(condition) ou Model.where(condition).exists?
.any? também é útil para arrays
[].any? #false
[1, 2, 3].any? #true
.empty?
.many?
.one?
.to_a
.to_json
.respond_to? :method_name
.is_a? Class
###########################################
Base.connection
###########################################
exec_query
def self.user(recebedor)
sql = ActiveRecord::Base.sanitize_sql_array(["SELECT * FROM USER s WHERE s.PESSOA_ID = ?", recebedor])
ActiveRecord::Base.connection.exec_query(sql)
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment