Sequel: How to use 'order by' in a view with tinytds - ruby

I need to create a view with an order by-clause with sequel, tinytds and MSSQL
When I do so, I get the error
TinyTds::Error: The ORDER BY clause is invalid in views, inline functions, derived tables, subqueries, and common table expressions, unless TOP or FOR XML is also specified. (Sequel::DatabaseError)
My examplecode:
require 'sequel'
DB = Sequel.tinytds(
:host => 'server',
:database=> 'DB',
)
#Remove data from previous test
DB.drop_table(:testtab1) if DB.table_exists?(:testtab1)
DB.drop_view(:v_testtab1) rescue Sequel::DatabaseError
DB.drop_view(:v_testtab2) rescue Sequel::DatabaseError
DB.create_table(:testtab1){
primary_key :id
field :a, :type => :nvarchar, :size => 10
field :b, :type => :nvarchar, :size => 10
}
#Here the error comes up
#"SELECT * FROM `testtab1` ORDER BY `b`"
DB.create_view(:v_testtab1, DB[:testtab1].order_by(:b))
See solution on SQL-side is easy. Instead of the
SELECT * FROM `testtab1` ORDER BY `b`
I need a
SELECT top 100 percent * FROM `testtab1` ORDER BY `b`
I found a solution with an additional obsolete column (without the column dummy I get an invalid comma):
sel = DB[:testtab1].select(Sequel.lit('top 100 percent "" as dummy'), *DB[:testtab1].columns)
#SELECT top 100 percent "" as dummy, [ID], [A], [B] FROM [TESTTAB1]
DB.create_view(:v_testtab2, sel.order_by(:b))
A similar solution can be made with limit:
#Take a big number to get all entries.
#DB[:testtab1].count would take the number in moment of view creation, not usage.
sel = DB[:testtab1].limit(99999999999)
#SELECT TOP (99999999999) * FROM [TESTTAB1]
DB.create_view(:v_testtab3, sel.order_by(:b))
But I'm looking for a nicer solution. Is there another better possibility?
If it is important:
Ruby 2.1
Sequel 4.19
tiny_tds-0.6.2-x64-mingw32
MSSQL 10.50.2500.0, 64 bit

Related

I want to do an autocomplete with Ruby and Sequel

I am using Sequel with prostgres and Sinatra. I want to do an autocomplete search. I’ve verified my jQuery which sends a GET works fine.
The Ruby code is:
get '/search' do
search = params[:search]
DB[:candidates].select(:last).where('last LIKE ?', '_a_').each do |row|
l = row[:last]
end
end
The problem is the Sequel query:
I have tried every possible configuration of the query that I can think of with no luck.
So, for example, in the above query I get all the people who have "a" in their last name but when I change the query to:
DB[:candidates].select(:last).where('last LIKE ?', 'search')
or
DB[:candidates].select(:last).where('last LIKE ?', search) # (without '')
I get nothing.
I have done warn params.inspect which indicates the param search is being passed, so I am stuck.
Any ideas how the query should be written?
Finally, the second part of the question the results (when it works with '_a_') are rendered as {:last=>"Yao"} I would like just Yao, how can I do that?
I have tried numerous different types of query including raw SQL but no luck. Or is the approach just plain wrong?
Just installed Sequel and made working example:
require "rubygems"
require "sequel"
# connect to an in-memory database
DB = Sequel.sqlite
# create an items table
DB.create_table :items do
primary_key :id
String :name
Float :price
end
# create a dataset from the items table
items = DB[:items]
# populate the table
items.insert(:name => 'abc', :price => rand * 100)
items.insert(:name => 'def', :price => rand * 100)
items.insert(:name => 'ghi', :price => rand * 100)
items.insert(:name => 'gui', :price => rand * 100)
# print out the number of records
puts "Item count: #{items.count}"
# print out the average price
puts "The average price is: #{items.avg(:price)}"
recs = items.select(:name).where(Sequel.like(:name, 'g%'))
recs.each do |rec|
puts rec.values
end
I think you will get the point.
UPDATED
So in your case you should try this:
DB[:candidates]
.select(:last)
.where(Sequel.like(:last, "#{search}%"))
.map{|rec| rec.values}.flatten
It should return array of found strings.
Copy/pasting from the Sequel documentation:
You can search SQL strings in a case sensitive manner using the Sequel.like method:
items.where(Sequel.like(:name, 'Acme%')).sql
#=> "SELECT * FROM items WHERE (name LIKE 'Acme%')"
You can search SQL strings in a case insensitive manner using the Sequel.ilike method:
items.where(Sequel.ilike(:name, 'Acme%')).sql
#=> "SELECT * FROM items WHERE (name ILIKE 'Acme%')"
You can specify a Regexp as a like argument, but this will probably only work on PostgreSQL and MySQL:
items.where(Sequel.like(:name, /Acme.*/)).sql
#=> "SELECT * FROM items WHERE (name ~ 'Acme.*')"
Like can also take more than one argument:
items.where(Sequel.like(:name, 'Acme%', /Beta.*/)).sql
#=> "SELECT * FROM items WHERE ((name LIKE 'Acme%') OR (name ~ 'Beta.*'))"
Open up a Sequel console (not your Sinatra app) and play with the query until you get results back. Since you say you want only the last column your query should be something like:
# Search anywhere inside the last name
DB[:candidates].where( Sequel.ilike(:last, "%#{search}%") ).select_map(:last)
# Find last names starting with the search string
DB[:candidates].where( Sequel.ilike(:last, "#{search}%") ).select_map(:last)
Uglier alternatives:
DB[:candidates]
.select(:last)
.where( Sequel.ilike(:last, "%#{search}%") )
.all
.map{ |hash| hash[:last] }
DB[:candidates]
.select(:last)
.where( Sequel.ilike(:last, "%#{search}%") )
.map( :last )
If you want to rank the search results by the best matches, you might be interested in my free LiqrrdMetal library. Instead of searching on the DB, you would pull a full list of all last names into Ruby and use LiqrrdMetal to search through them. This would allow a search string of "pho" to match both "Phong" as well as "Phrogz", with the former scoring higher in the rankings.

Can I retrieve objects with Sequel from a complex query that limits results to fields from a single table?

I have a model whose rows I always want to sort based on the values in another associated model and I was thinking that the way to implement this would be to use set_dataset in the model. This is causing query results to be returned as hashes rather than objects, though, so none of the methods from the class can be used when iterating over the dataset.
I basically have two classes
class SortFields < Sequel::Model(:sort_fields)
set_primary_key :objectid
end
class Items < Sequel::Model(:items)
set_primary_key :objectid
one_to_one :sort_fields, :class => SortFields, :key => :objectid
end
Some backstory: the data is imported from a legacy system into mysql. The values in sort_fields are calculated from multiple other associated tables (some one-to-many, some many-to-many) according to some complicated rules. The likely solution will be to just add the values in sort_fields to items (I want to keep the imported data separate from the calculated data, but I don't have to). First, though, I just want to understand how far you can go with a dataset and still get objects rather than hashes.
If I set the dataset to sort on a field in items like so
class Items < Sequel::Model(:items)
set_primary_key :objectid
one_to_one :sort_fields, :class => SortFields, :key => :objectid
set_dataset(order(:sortnumber))
end
then the expected clause is added to the generated SQL, e.g.:
>> Items.limit(1).sql
=> "SELECT * FROM `items` ORDER BY `sortnumber` LIMIT 1"
and queries still return objects:
>> Items.limit(1).first.class
=> Items
If I order it by the associated fields though...
class Items < Sequel::Model(:items)
set_primary_key :objectid
one_to_one :sort_fields, :class => SortFields, :key => :objectid
set_dataset(
eager_graph(:sort_fields).
order(:sort1, :sort2, :sort3)
)
end
...I get hashes
?> Items.limit(1).first.class
=> Hash
My first thought was that this happens because all fields from sort_fields are included in the results and maybe if selected only the fields from items I would get Items objects again:
class Items < Sequel::Model(:items)
set_primary_key :objectid
one_to_one :sort_fields, :class => SortFields, :key => :objectid
set_dataset(
eager_graph(:sort_fields).
select(:items.*).
order(:sort1, :sort2, :sort3)
)
end
The generated SQL is what I would expect:
>> Items.limit(1).sql
=> "SELECT `items`.* FROM `items` LEFT OUTER JOIN `sort_fields` ON (`sort_fields`.`objectid` = `items`.`objectid`) ORDER BY `sort1`, `sort2`, `sort3` LIMIT 1"
It returns the same rows as the set_dataset(order(:sortnumber)) version but it still doesn't work:
>> Items.limit(1).first.class
=> Hash
Before I add the sort fields to the items table so that they can all live happily in the same model, is there a way to tell Sequel to return on object when it wants to return a hash?
If you use #eager_graph, you must use #all instead of #each to retrieve the results in order for the graph to be processed (since you cannot eagerly load without having all instances up front), or use the eager_each plugin (which makes #each call #all internally).

Sequel default example fails when switched to postgres adapter

I'm trying to run the Sequel example from http://sequel.rubyforge.org/. Everything works fine on sqlite, but fails when I switch to postgres.
This is the connection code:
DB = Sequel.connect(:adapter=>'postgres', :host=>'localhost', :database=>'testing', :user=>'postgres', :default_schema=>'sequel')
This is the error I get:
postgres.rb:145:in `async_exec': PG::Error: ERROR: relation "items" does not exist (Sequel::DatabaseError)
LINE 1: INSERT INTO "items" ("price", "name") VALUES (12.45377636338...
I'm suspecting that the issue is Sequel trying to execute INSERT INTO "items" instead "sequel.items", even though :default_schema is correctly set.
Anyone have any idea what i'm doing wrong?
Thanks in advance.
Edit - this is the code used:
require "rubygems"
require "sequel"
# connect to an in-memory database
#DB = Sequel.sqlite
DB = Sequel.connect(:adapter=>'postgres', :host=>'localhost', :database=>'testing', :user=>'postgres', :default_schema=>'sequel')
# create an items table
DB.create_table :items do
primary_key :id
String :name
Float :price
end
# create a dataset from the items table
items = DB[:items]
# populate the table
items.insert(:name => 'abc', :price => rand * 100)
items.insert(:name => 'def', :price => rand * 100)
items.insert(:name => 'ghi', :price => rand * 100)
# print out the number of records
puts "Item count: #{items.count}"
Looks like you're missing the password in the connect method (that's the only difference from the documentation example). Its common for the password to just be the username, so try that if you're not sure what the password is.
It's also suggested to use a different postgresql user with each project, which also makes naming the user intuitive (the project name.) That avoids potentially clashing names.
Anyway, see if this works:
DB = Sequel.postgres 'testing', host: 'localhost', default_schema: 'sequel',
user: 'postgres', password: 'postgres'

ruby Datamapper is not inserting all records

I have this ruby function which yields 1095 records MusicTab::FOps.gen_list('/fun/Music')
and I want to store them using datamapper. When I do this
MusicTab::FOps.gen_list('/fun/Music') do |arr_f|
#files=Files.create(
:file_path => arr_f[0],
:title => arr_f[1],
:album => arr_f[2],
:artist => arr_f[3] )
end
only 154 records are inserted, I don't understand what is so special about these records.
If I do this I get nil for p #files.id all the other records other than those 154 records which gets stored.
MusicTab::FOps.gen_list('/fun/Music') do |arr_f|
#files=Files.create(
:file_path => arr_f[0],
:title => arr_f[1],
:album => arr_f[2],
:artist => arr_f[3] )
p #files.id
p #files.title
p #files.album
end
If I just print the values I can see all the values like
counter=0
MusicTab::FOps.gen_list('/fun/Music') do |arr_f|
p arr_f
counter=counter+1
end
counter
please help..
Regards
try looking at #files.errors. My guess is that you are failing a validation and it isn't getting saved.
You can try enabling some Debug logging and see if that helps to isolate the problem.
DataMapper::Logger.new($stdout, :debug)
It would be helpful to see your Model Definition for the Files object, and a sample of the data that doesn't get saved, and one that does. The last time I ran into this issue was when I needed to set the size on my text fields as I was overflowing the default.

ORM for SQL Scripting

What is the best way to run simple SQL scripts in a database (preferably DBM implementation agnostically)?
So, for illustration purposes, using your best/suggested way, I'd like to see a script that creates a few tables with names from an array ['cars_table', 'ice_cream_t'], deletes all elements with id=5 in a table, and does a join between two tables and prints the result formatted in some nice way.
I've heard of Python and PL/SQL to do this
Ruby/Datamapper seems very attractive
Java + JDBC, maybe
Others?
Some of these are mostly used in a full application or within a framework. I'd like to see them used simply in scripts.
Ruby/Sequel is currently my weapon of choice.
Short example from the site:
require "rubygems"
require "sequel"
# connect to an in-memory database
DB = Sequel.sqlite
# create an items table
DB.create_table :items do
primary_key :id
String :name
Float :price
end
# create a dataset from the items table
items = DB[:items]
# populate the table
items.insert(:name => 'abc', :price => rand * 100)
items.insert(:name => 'def', :price => rand * 100)
items.insert(:name => 'ghi', :price => rand * 100)
# print out the number of records
puts "Item count: #{items.count}"
# print out the average price
puts "The average price is: #{items.avg(:price)}"
By using SQL DDL (Data Definition Language), which can be done db agnostically, if you're careful.
There are examples at the Wikipedia article:
http://en.wikipedia.org/wiki/Data_Definition_Language

Resources