Executando SQL no Rails

23/08/07

Às vezes precisamos realizar consultas que não pertencem a nenhum modelo no Rails. Uma maneira de fazer isso é utilizando o método execute. Veja um exemplo:

# setting a variable
# with the current connection
conn = ActiveRecord::Base.connection

result = conn.execute "SELECT id, email FROM users LIMIT 3"
# => #<Mysql::Result>

Como resultado, você recebe um objeto Mysql::Result, que pode ser iterado da seguinte maneira:

result = conn.execute "SELECT id, email FROM users LIMIT 2"
result.each do |user|
  puts "id: %s" % user[0]
  puts "email: %s" % user[1]
  puts
end

Normalmente, precisamos fazer consultas baseadas em valores passados pelo usuário. É extremamente importante que você tome os cuidados necessários para não sofrer um ataque através de SQL Injection. Para que isto não aconteça, você deve utilizar o método quote:

query = "SELECT id, email FROM users WHERE email = %s" % conn.quote("' OR 1=1 #")
puts query
# => SELECT id, email FROM users WHERE email = '\' OR 1=1 #'

Além do método execute, você pode utilizar outros mais específicos como é o caso do método select_all. Este método retorna um array de hashes, com o nome das colunas como índice, tornando mais simples a iteração.

result = conn.select_all "SELECT id, email FROM users LIMIT 2"
result.each do |user|
  puts "id: %s" % user['id']
  puts "email: %s" % user['email']
  puts
end

Para os casos em que tudo o que você precisa é de apenas uma linha como resultado, você pode utilizar o método select_one:

result = conn.select_one("SELECT COUNT(*) AS total FROM users")
puts result['total']

No Rails, todos os modelos estendem a classe ActiveRecord::Base. Por isso, os métodos acima também estão disponíveis no modelo.

class User < ActiveRecord::Base
  def self.all
    connection.select_all "SELECT id, email FROM users"
  end
end
 
puts User.all
# => [{"id"=>"1", "email"=>"user@example.com"}, {"id"=>"2", "email"=>"another@example.com"}]

Lembre-se que não é nenhuma vergonha utilizar SQL diretamente no Rails. O que o ActiveRecord faz é nos poupar das tarefas rotineiras. Já para as mais complexas, você provavelmente vai precisar de uma das soluções acima. Para mais informações, acesse a documentação do ActiveRecord.

Gerenciador MySQL para Linux

26/03/07

Quem usa/usou Windows e tinha que mexer com banco de dados MySQL provavelmente usou o MySQL-Front, descontinuado por pressão da própria MySQL. Hoje, uma alternativa é o SQLyog.

A verdade é que sempre quis um programa assim para Linux. Aquele gerenciador — MySQL Administrator — criado pela MySQL é horrível. Tudo muito complicado de fazer. E não é que procurando por algum outro gerenciador, encontrei o Emma?

Atalhos semelhantes ao falecido MySQL-Front, interface simples de usar, ou seja, exatamente do jeito que eu queria! No Ubuntu, basta rodar o comando abaixo para instalá-lo:

$~ sudo aptitude install emma

Não sei te dizer em qual repositório este pacote está localizado, então, se você não encontrá-lo lembre-se de habilitar os repositórios universe e multiverse.

MS-SQL Server: ruim como tudo o que a Microsoft faz

04/01/07

É incrível mas tudo o que a Microsoft faz é um lixo. Não consigo lembrar de nada que me faça dizer "Nossa, eles detonaram com ____ (seu aplicativo aqui)".

Estou fazendo um job que, infelizmente, deve ser feito em SQL Server. Como não conheço muito sobre ele, estou fazendo as coisas direto em SQL, sem usar procedures, view ou outra feature mais complicada/avançada.

Em um certo momento, precisei fazer um LEFT JOIN com COUNT. O problema é que o SQL Server é tão burro que preciso definir cada campo que não faz parte da agregação na cláusula GROUP BY. Mas adivinha… Isso modifica o resultado!

Para ilustrar, veja um exemplo de "tópicos" e "respostas".

CREATE TABLE topics (
	id INT(11) AUTO_INCREMENT NOT NULL,
	title VARCHAR(255)
);
 
CREATE TABLE replies (
	id INT(11) AUTO_INCREMENT NOT NULL,
	topic_id INT(11)
	title VARCHAR(255)
);
 
 
INSERT INTO topics (title) VALUES ('Topic #1');
INSERT INTO topics (title) VALUES ('Topic #2');
INSERT INTO replies (title, topic_id) VALUES ('Reply topic #2', 2);

Se eu fizesse a seguinte seleção, deveria me retornar o tópico com a quantidade de respostas:

SELECT
    topics.title AS title,
    COUNT(replies.id) AS replies
FROM 
    topics
LEFT JOIN replies ON
    topics.id = replies.topic_id
GROUP BY
    replies.id;
title: Topic #1, replies: 0
title: Topic #2, replies: 1

Isso funciona no MySQL e provavelmente deve funcionar no PostgreSQL. Mas no MS-SQL Server recebo a seguinte mensagem:

Server: Msg 8120, Level 16, State 1, Line 1
Column 'topics.title' is invalid in the select list because it is not 
contained in either an aggregate function or the GROUP BY clause.

E tem gente que ainda paga caro por isso!