I am using MySQL2 in Ruby to query a database. What is a direct way to check whether the result of a query is empty?
The code looks like:
require 'mysql2'
client = Mysql2::Client.new(:host => "localhost", :username => "root")
results = client.query("SELECT * FROM users WHERE group='githubbers'")
The Mysql2 documentation is indeed very poor. But by inspecting the type of results you will notice that it is a Mysql2::Result which contains 3 methods. The one that you are interested in is count (or alias size) which will return the number of rows of the result.
From here you can easily check if it is 0:
(results.count == 0)
Alternatively you could open the Mysql2::Result class and add the method empty? yourself:
class Mysql2::Result
def empty?
(count == 0)
end
end
And then you can just do:
results.empty?
0 == results.size
will return true if results is empty. AFAIK there is no direct method (such as Array#empty?) , but you could monkey patch it.
Related
I'm writing a Ruby script that accesses a MySQL database using the Mysql2 gem.
After I get a set of results from a query, I want to examine a subset of the rows of the result set (e.g. rows 5 to 8) rather than the whole set.
I know I can do this with a while loop, something like this:
db = Mysql2::Client.new(:host => "myserver", :username => "user", :password => "pass", :database => "books")
rs = db.query "select * from bookslist"
i = 5
while i <= 8
puts rs.entries[i]
i += 1
end
db.close
But I'm aware this is probably not the best way to write this in Ruby. How can I make this more "idiomatic" Ruby code?
(I know my other option is to modify the query to return only the data I want. I still want to know how to do this in Ruby)
Ruby provides range operator in for loop, Probably you can use following code
for i in 5..8
puts rs.entries[i]
end
I have a system that has a User, Message, and MessageToken models. A User can create Messages. But when any User reads the Messages of others a MessageToken is created that associates the reader (User) to the Message. MessageTokens are receipts that keep track of the states for the user and that particular message. All of my associations in the Models are set up properly, and everything works fine, except for structuring a very specific query that I cannot get to work properly.
User.rb
has_many :messages
Message.rb
belongs_to :user
has_many :message_tokens
MessageToken.rb
belongs_to :user
belongs_to :message
I am trying to structure a query to return Messages that: Do not belong to the user; AND { The user has a token with the read value set to false OR The user does not have a token at all }
The later part of the statement is what is causing problems. I am able to successfully get results for Messages that are not the user, Messages that the user has a token for with read => false. But I cannot get the expected result when I try to make a query for Messages that have no MessageToken for the user. This query does not error out, it just does not return the expected result. How would you structure such a query?
Below are the results of my successful queries and the expected results.
130 --> # Messages
Message.count
78 --> # Messages that are not mine
Message.where.not(:user_id => #user.id)
19 --> # Messages that are not mine and that I do not have a token for
59 --> # Messages that are not mine, and I have a token for
Message.where.not(:user_id => #user.id).includes(:message_tokens).where(message_tokens: {:user_id => #user.id}).count
Message.where.not(:user_id => #user.id).includes(:message_tokens).where(["message_tokens.user_id = ?", #user.id]).count
33 --> # Messages that are not mine, and I have a token for, and the token is not read
Message.where.not(:user_id => #user.id).includes(:message_tokens).where(message_tokens: {:user_id => #user.id, :read => false}).count
Message.where.not(:user_id => #user.id).includes(:message_tokens).where(["message_tokens.user_id = ? AND message_tokens.read = false", #user.id]).references(:message_tokens).count
The Final Expected Result
52 --> # Messages that are not mine and: I have a token for that is not read OR I do not have a token for
My best attempt at a query to achieve my goal
64 --> # Wrong number returned, expected 52
Message.where.not(:user_id => #user.id).includes(:message_tokens).where(["(message_tokens.user_id = ? AND message_tokens.read = false) OR message_tokens.user_id <> ?", #user.id, #user.id]).references(:message_tokens).count
The problem lies in the query trying to find Messages that are not the users and that the user does not have a token for
63 --> #This does not yield the expected result, it should == 19 (the number of Messages that are not mine and that I do not have a token for)
Message.where.not(:user_id => #user.id).includes(:message_tokens).where.not(message_tokens: {:user_id => #user.id}).count
Message.where.not(:user_id => #user.id).includes(:message_tokens).where(["message_tokens.user_id <> ?", #user.id]).references(:message_tokens).count
How can I solve this?
If you don't mind using 2 queries, a possible solution would be:
messages_not_written_by_user = Message.where.not(:user_id => #user.id)
messages_already_read_by_user = Message.where.not(:user_id => #user.id).includes(:message_tokens).where(message_tokens: {:user_id => #user.id, :read => true})
messages_not_read_by_user_yet = messages_not_written_by_user - messages_already_read_by_user
I would personally find this syntax more readable:
messages_not_written_by_user = Message.where.not(:user => #user).count
messages_already_read_by_user = Message.where.not(:user => #user).includes(:message_tokens).where(message_tokens: {:user => #user, :read => true}).count
One remark to this query:
63 --> #This does not yield the expected result, it should == 19 (the number of Messages that are not mine and that I do not have a token for)
Message.where.not(:user_id => #user.id).includes(:message_tokens).where.not(message_tokens: {:user_id => #user.id}).count
This query searches for all the messages which have a token with an arbitrary other user. (If msg1 has a token with #user, and it also has a token with #another_user, this query will find it.)
Full disclosure - I'm not sure how I'd do this as you have it set up right now. However: are you against installing a gem to help? If you're not, I'd suggest you look into the Squeel gem (https://github.com/activerecord-hackery/squeel).
Squeel makes these kinds of associations a lot easier and allows use to use the plain old | operator. It's built on Arel and shouldn't effect anything you've written in ActiveRecord (at least in my experience). Hope that helps!
Ok, so thanks to the help of R11 Runner I was able to come up with a solution, which required using pure SQL. I could not use the Squeel gem or ActiveRecord as there was no equivalent to SQL's NOT EXISTS operator, which was the crucial component missing.
The reason this works is because unlike the other solutions the NOT EXISTS operator will return all records from the Messages table where there are no records in the MessageTokens table for the given user_id, whereas using where.not would look for the first match instead not ensuring the non existence that was needed.
Message.find_by_sql ["SELECT * FROM messages where messages.user_id <> ?
AND (
(EXISTS (SELECT * FROM message_tokens WHERE message_id = messages.id AND user_id = ? AND read = FALSE))
OR
(NOT EXISTS (SELECT * FROM message_tokens WHERE message_id = messages.id AND user_id = ?))
)",#user.id, #user.id, #user.id]
I am trying to run the following Linq query from MySQL client
query = query.Where(c => c.CustomerRoles
.Select(cr => cr.Id)
.Intersect(customerRoleIds)
.Any()
);
This code looks okay, but gives the error:
System.NotSupportedException: Specified method is not supported.at MySql.Data.Entity.SqlGenerator.Visit(DbIntersectExpression expression)
This looks to me like an issue with .Intersect. Can anybody tell me the cause of this error and how to fix it?
i think #GertArnold's post is a correct and best of the answers, but i'm wonder why have you gotten NotSupportedException yet ? so the problem should not be from intersect probably.
where is customerRoleIds come from ? is it IQueryable<T> ?
break the query, and complete it step by step.
if you don't get exception at this lines:
var a = query.Select(c => new {
c,
CustomerRoleIDList = c.CustomerRoles.Select(cr => cr.Id).AsEnumerable()
})
.ToList();
var b = customerRoleIds.ToList();
you must get the result by this:
var b = query.Where(c => c.CustomerRoles.any(u => customerRoleIds.Contains(u.Id)))
.ToList();
if you get exception by above query, you can try this final solution to fetch data first, but note by this, all data will be fetched in memory first:
var a = query.Select(c => new {
c,
CustomerRoleIDList = c.CustomerRoles.Select(cr => cr.Id).AsEnumerable()
})
.ToList();
var b = a.Where(c => c.CustomerRoleIDList.any(u => customerRoleIds.Contains(u)))
.Select(u => u.c)
.ToList();
Using Intersect or Except is probably always troublesome with LINQ to a SQL backend. With Sql Server they may produce horrible SQL queries.
Usually there is support for Contains because that easily translates to a SQL IN statement. Your query can be rewritten as
query = query.Where(c => c.CustomerRoles
.Any(cr => customerRoleIds.Contains(cr.Id)));
I don't think that customerRoleIds will contain many items (typically there won't be hundreds of roles), otherwise you should take care not to hit the maximum number of items allowed in an IN statement.
query.Where(c => c.CustomerRoles
.Any(v=>customerRoleIds.Any(e=>e == v.Id))
.Select(cr => cr.Id))
.ToList();
Try adding toList() before intersect, that should enumerate results locally instead running on MySql, you will get performance hit thought.
query = query.Where(c => c.CustomerRoles.Select(cr => cr.Id)).ToList().Intersect(customerRoleIds);
I'm trying to find out how rails converts a hash such as (This is an example please do not take this literally I threw something together to get the concept by I know this query is the same as User.find(1)):
{
:select => "users.*",
:conditions => "users.id = 1",
:order => "username"
}
Into:
SELECT users.* FROM users where users.id = 1 ORDER BY username
The closest thing I can find is ActiveRecord::Base#find_every
def find_every(options)
begin
case from = options[:from]
when Symbol
instantiate_collection(get(from, options[:params]))
when String
path = "#{from}#{query_string(options[:params])}"
instantiate_collection(format.decode(connection.get(path, headers).body) || [])
else
prefix_options, query_options = split_options(options[:params])
path = collection_path(prefix_options, query_options)
instantiate_collection( (format.decode(connection.get(path, headers).body) || []), prefix_options )
end
rescue ActiveResource::ResourceNotFound
# Swallowing ResourceNotFound exceptions and return nil - as per
# ActiveRecord.
nil
end
end
I'm unsure as to how to modify this to just return what the raw mysql statement would be.
So after a few hours of digging I came up with an answer although its not great.
class ActiveRecord::Base
def self._get_finder_options options
_get_construct_finder_sql(options)
end
private
def self._get_construct_finder_sql(options)
return (construct_finder_sql(options).inspect)
end
end
adding this as an extension gives you a publicly accessible method _get_finder_options which returns the raw sql statement.
In my case this is for a complex query to be wrapped as so
SELECT COUNT(*) as count FROM (INSERT_QUERY) as count_table
So that I could still use this with the will_paginate gem. This has only been tested in my current project so if you are trying to replicate please keep that in mind.
I'm using Ruby's mysql2 gem found here:
https://github.com/brianmario/mysql2
I have the following code:
client = Mysql2::Client.new(
:host => dbhost,
:port => dbport, :database => dbname,
:username => dbuser,
:password => dbpass)
sql = "SELECT column1, column2, column3 FROM table WHERE id=#{id}"
res = client.query(sql, :as => :array)
p res # prints #<Mysql2::Result:0x007fa8e514b7d0>
Is it possible the above .query call to return array of hashes, each hesh in the res array to be in the format column => value. I can do this manually but from the docs I was left with the impression that I can get the results directly loaded in memory in the mentioned format. I need this, because after that I have to encode the result in json anyway, so there is no advantage for me to fetch the rows one by one. Also the amount of data is always very small.
Change
res = client.query(sql, :as => :array)
to:
res = client.query(sql, :as => :hash)
As #Tadman says, :as => :hash is the default, so actually you don't have to specify anything.
You can always fetch the results as JSON directly:
res = client.query(sql, :as => :json)
The default format, as far as I know, is an array of hashes. If you want symbol keys you need to ask for those. A lot of this is documented in the gem itself.
You should also be extremely cautious about inserting things into your query with string substitution. Whenever possible, use placeholders. These aren't supported by the mysql2 driver directly, so you should use an adapter layer like ActiveRecord or Sequel.
The source code for mysql2 implemented MySql2::Result to simply include Enumerable, so the obvious way to access the data is by using any method implemented in Enumerabledoc here.
For example, #each, #each_with_index, #collect and #to_a are all useful ways to access the Result's elements.
puts res.collect{ |row| "Then the next result was #{row}" }.join("\t")