I'm trying to access some data from our on-site billing server to be populate into a rails app that i'm deploying. I've done some digging about and think Tiny TDS and a rake task is the best way to go but I seem to be getting a bit stuck. The code showed below is just an example and not finished! I keep getting a server name not found in configuration files error.
task :import_customers do
RAILS_HOME = File.expand_path(File.join(File.dirname(__FILE__),"../.."))
RAILS_CONFIG = "#{RAILS_HOME}/config"
require "#{RAILS_CONFIG}/environment"
require 'tiny_tds'
client = TinyTds::Client.new(:username => 'user', :password => 'pass', :host => 'SQLSRVR')
result = client.execute("select sitedetails.siteid, company.id as companyid, sitedetails.shortname, company.name,sitedetails.sitename as [Site Name] from company inner join sitedetails on company.id=sitedetails.id left outer join solutionscustinfo s on sitedetails.siteid=s.siteid left outer join paymentconditions a on s.paymentconditions_id=a.id left outer join company agent on company.agent_id=agent.id left outer join sitecontacts billingcontact on billingcontact.contactid=s.billingcontact_id left outer join package p on p.id=package left outer join tariffnames v on v.tariffcode=isnull(s.lcr_tariff,p.lcr_tariff) left outer join tariffnames d on d.tariffcode=isnull(s.data_tariff,p.data_tariff) left outer join tariffnames m on m.tariffcode=isnull(s.mob_tariff,p.mob_tariff) left outer join (select invoiceaddress from sitedetails group by invoiceaddress) ba on ba.invoiceaddress=sitedetails.siteid left outer join discount on discount.id=isnull(s.discount,p.discount) left outer join billrun on billrun.id=s.bill_run left outer join report_profile on report_profile.id=s.report_profile left outer join account_manager on account_manager.id=company.acctmgr_id where company.is_customer<>0 order by company.name,sitedetails.shortname")
result.each do |row|
puts row
name = row['name']
sitename = row['Site Name']
puts sitename
#company = Company.all
end
end
Sorry for not replying I went about this in a different way in the end.
namespace :abillity do
desc "import customers"
def return_address(siteid)
client = TinyTds::Client.new(:username => 'earth', :password => 'gs500e', :host => '192.168.1.38')
result = client.execute("select Address, Town, County, PostCode from sitedetails WHERE SiteID = '#{siteid}'")
result.each do |row|
if (row['Address'] == nil or row['Town'] == nil or row['County'] == nil or row['PostCode'] == nil)
return "Not Listed"
end
#address = row['Address'] + " " + row['Town'] + " " + row['County'] + " " + row['PostCode']
if (#address == nil)
return "Not Listed"
elsif (#address.strip == "")
return "Not Listed"
else
return #address
end
return ""
end
end
task :import_customers => :environment do
require 'tiny_tds'
require 'rubygems'
client = TinyTds::Client.new(:username => 'earth', :password => 'gs500e', :host => '192.168.1.38')
result = client.execute("select sitedetails.siteid, company.id as companyid, sitedetails.shortname, company.name,sitedetails.sitename as [Site Name],sitedetails.sage_id as [Account No] from company inner join sitedetails on company.id=sitedetails.id left outer join solutionscustinfo s on sitedetails.siteid=s.siteid left outer join paymentconditions a on s.paymentconditions_id=a.id left outer join company agent on company.agent_id=agent.id left outer join sitecontacts billingcontact on billingcontact.contactid=s.billingcontact_id left outer join package p on p.id=package left outer join tariffnames v on v.tariffcode=isnull(s.lcr_tariff,p.lcr_tariff) left outer join tariffnames d on d.tariffcode=isnull(s.data_tariff,p.data_tariff) left outer join tariffnames m on m.tariffcode=isnull(s.mob_tariff,p.mob_tariff) left outer join (select invoiceaddress from sitedetails group by invoiceaddress) ba on ba.invoiceaddress=sitedetails.siteid left outer join discount on discount.id=isnull(s.discount,p.discount) left outer join billrun on billrun.id=s.bill_run left outer join report_profile on report_profile.id=s.report_profile left outer join account_manager on account_manager.id=company.acctmgr_id where company.is_customer<>0 order by company.name,sitedetails.shortname")
result.each do |row|
# First of all check if a company allready exists that has the same name and site name
existing = Company.where(["name = ?",row['name']]).where(["site = ?",row['Site Name']]).limit(1)
if (row['Account No'] == nil or row['Account No'] == "")
row['Account No'] = "N/A"
end
if (existing.first == nil)
# Company does not exist add it
#company = Company.new
#company.name = row['name']
#company.site = row['Site Name']
#company.accountNumber = row['Account No']
#company.address = return_address(row['siteid'])
#company.companytype = "Customer"
if (#company.save! == false)
debugger
end
else
# Existing, make sure acct no and address is up to date
#company = existing[0]
#company.accountNumber = row['Account No']
#company.address = return_address(row['siteid'])
#company.save
end
end
end
end
Related
First I'd like to describe idea of what I am trying to do. I have "jobstat_jobs" table where I store the information about computing job perfomance. I am trying to compose 2 queries: 1) jobs grouped by project 2) jobs grouped by project and state. Then these queries are inner joined and I want to display share of jobs of each state among all jobs. I implemented it using ActiveRecord and raw sql, but I can't do it with arel. I get the "stack level too deep" on the "joined.to_sql" line.
members = Core::Member.arel_table
jobs = Perf::Job.arel_table
cool_relation = jobs.where(jobs[:state].not_in(%w[COMPLETETED RUNNING unknown]))
relation = cool_relation.join( Arel::Nodes::SqlLiteral.new <<-SQL
INNER JOIN core_members ON core_members.login = jobstat_jobs.login
SQL
).join(Arel::Nodes::SqlLiteral.new <<-SQL
RIGHT JOIN sessions_projects_in_sessions ON
sessions_projects_in_sessions.project_id = core_members.project_id
SQL
).group(members[:project_id]).project(members[:project_id].as('id'))
hours = '(extract(epoch from (end_time - start_time))/ 3600)'
selections = {
node_hours: "(sum((#{hours})*num_nodes))",
jobs: "count(jobstat_jobs.id)"
}
selections.each do |key, value|
relation = relation.project(
Arel::Nodes::SqlLiteral.new(value).as(key.to_s)
)
end
state_relation = relation.project(jobs[:state].as('state'))
.group(jobs[:state])
s = state_relation.as('s')
pp ActiveRecord::Base.connection.exec_query(state_relation.to_sql).to_a
joined = relation.join(s)
.on(jobs[:id].eq(s[:id]))
.project(s[:id], s[:state])
puts joined.to_sql
joined
I noticed the strange thing. When I replace "joined = relation" with "jobs.where(jobs[:state].not_in(%w[COMPLETETED RUNNING unknown]))" it works. But when I replace "joined = relation" with "joined = cool_relation" it doesn't work and I get "stack level too deep" (these 2 replacements are almost the same).
Arel v 9.0.0, Postgresql
My problem is that I expected arel to create a new object each time I chain method(like ActiveRecord::Relation).
Just add #clone method here:
joined = relation.clone.join(s)
.on(jobs[:id].eq(s[:id]))
.project(s[:id], s[:state])
I got SQL string, but it was wrong and there were exceptions on database level. Now my code is following:
members = Core::Member.arel_table
jobs = Perf::Job.arel_table
cool_relation = jobs.where(jobs[:state].not_in(%w[COMPLETETED RUNNING unknown]))
relation = cool_relation.join( Arel::Nodes::SqlLiteral.new <<-SQL
INNER JOIN core_members ON core_members.login = jobstat_jobs.login
SQL
.gsub("\n", ' ')).join(Arel::Nodes::SqlLiteral.new <<-SQL
RIGHT JOIN sessions_projects_in_sessions ON
sessions_projects_in_sessions.project_id = core_members.project_id
SQL
.gsub("\n", ' ')).group(members[:project_id]).project(members[:project_id].as('id'))
hours = '(extract(epoch from (end_time - start_time))/ 3600)'
selections = {
node_hours: "(sum((#{hours})*num_nodes))",
jobs: "count(jobstat_jobs.id)"
}
selections.each do |key, value|
relation = relation.project(
# Arel::Nodes::SqlLiteral.new(value).as(key.to_s)
Arel::Nodes::SqlLiteral.new("(CAST(#{value} AS decimal))").as(key.to_s)
)
end
state_relation = relation.clone.project(jobs[:state].as('state'))
.group(jobs[:state])
s = state_relation.as('s')
n = relation.as('n')
pp ActiveRecord::Base.connection.exec_query(state_relation.to_sql).to_a
pp ActiveRecord::Base.connection.exec_query(relation.to_sql).to_a
manager = Arel::SelectManager.new
joined = manager.project(s[:id], s[:state])
.from(s)
.join(n).on(s[:id].eq(n[:id]))
selections.keys.each do |key|
joined = joined.project(s[key].as("s_#{key}"), n[key].as("n_#{key}"))
.project(s[key] / n[key].as("share_#{key}"))
end
puts joined.to_sql
joined
Pay attention to the #clone method used here too. When I remove #clone, project method affects relation variable too and I get wrong SQL because of that.
The joined.to_sql line produces the following and works as expected:
SELECT s."id", s."state", s."node_hours" AS s_node_hours,
n."node_hours" AS n_node_hours, s."node_hours" / n."node_hours" AS
share_node_hours, s."jobs" AS s_jobs, n."jobs" AS n_jobs,
s."jobs" / n."jobs" AS share_jobs FROM (SELECT "core_members".
"project_id" AS id, (CAST((sum(((extract(epoch from (end_time - start_time))/ 3600))*num_nodes)) AS decimal)) AS node_hours,
(CAST(count(jobstat_jobs.id) AS decimal)) AS jobs,
"jobstat_jobs"."state" AS state FROM "jobstat_jobs" INNER JOIN
core_members ON core_members.login = jobstat_jobs.login
RIGHT JOIN sessions_projects_in_sessions ON sessions_projects_in_sessions.project_id = core_members.project_id
WHERE "jobstat_jobs"."state" NOT IN ('COMPLETETED', 'RUNNING', 'unknown')
GROUP BY "core_members"."project_id", "jobstat_jobs"."state") s INNER JOIN
(SELECT "core_members"."project_id" AS id, (CAST((sum(((extract(epoch from
(end_time - start_time))/ 3600))*num_nodes)) AS decimal)) AS node_hours,
(CAST(count(jobstat_jobs.id) AS decimal)) AS jobs FROM "jobstat_jobs"
INNER JOIN core_members ON core_members.login = jobstat_jobs.login RIGHT JOIN sessions_projects_in_sessions ON
sessions_projects_in_sessions.project_id = core_members.project_id WHERE
"jobstat_jobs"."state" NOT IN ('COMPLETETED', 'RUNNING', 'unknown') GROUP BY
"core_members"."project_id") n ON s."id" = n."id"
Now that I understand the desired output here is how I would go about this
class Report
JOB_STATS = Arel::Table.new('jobstat_jobs')
CORE_MEMBERS = Arel::Table.new('core_members')
SESSIONS = Arel::Table.new('sessions_projects_in_sessions')
def additions
# This could be ported too if I knew the tables for end_time, start_time, and num_nodes
{
node_hours: Arel.sql("((extract(epoch from (end_time - start_time))/ 3600))*num_nodes").sum,
jobs: JOB_STATS[:id].count
}
end
def n
#n ||= _base_query.as('n')
end
def s
#s ||= _base_query
.project(JOB_STATS[:state])
.group(JOB_STATS[:state]).as('s')
end
def alias_columns
additions.keys.flat_map do |key|
[s[key].as("s_#{key}"),
n[key].as("n_#{key}"),
(s[key] / n[key]).as("share_#{key}")]
end
end
def query
Arel::SelectManager.new.project(
s[:project_id].as('id'),
s[:state],
*alias_columns
)
.from(s)
.join(n).on(s[:project_id].eq(n[:project_id]))
end
def to_sql
query.to_sql
end
private
def cast_as_decimal(value,alias_name:)
Arel::Nodes::NamedFunction.new(
"CAST",
[Arel::Nodes::As.new(value, Arel.sql('DECIMAL'))]
).as(alias_name.to_s)
end
def _base_query
JOB_STATS
.project(
CORE_MEMBERS[:project_id],
*additions.map {|k,v| cast_as_decimal(v, alias_name: k)})
.join(CORE_MEMBERS).on(CORE_MEMBERS[:login].eq(JOB_STATS[:login]))
.outer_join(SESSIONS).on(SESSIONS[:project_id].eq(CORE_MEMBERS[:project_id]))
.where(JOB_STATS[:state].not_in(['COMPLETETED', 'RUNNING', 'unknown']))
.group(CORE_MEMBERS[:project_id])
end
end
Result of Report.new.to_sql
SELECT
s."project_id" AS id,
s."state",
s."node_hours" AS s_node_hours,
n."node_hours" AS n_node_hours,
s."node_hours" / n."node_hours" AS share_node_hours,
s."jobs" AS s_jobs,
n."jobs" AS n_jobs,
s."jobs" / n."jobs" AS share_jobs
FROM
(
SELECT
"core_members"."project_id",
CAST(SUM(((extract(epoch from (end_time - start_time))/ 3600))*num_nodes) AS DECIMAL) AS node_hours,
CAST(COUNT("jobstat_jobs"."id") AS DECIMAL) AS jobs,
"jobstat_jobs"."state"
FROM
"jobstat_jobs"
INNER JOIN "core_members" ON "core_members"."login" = "jobstat_jobs"."login"
LEFT OUTER JOIN "sessions_projects_in_sessions" ON "sessions_projects_in_sessions"."project_id" = "core_members"."project_id"
WHERE
"jobstat_jobs"."state" NOT IN (N'COMPLETETED', N'RUNNING', N'unknown')
GROUP BY
"core_members"."project_id",
"jobstat_jobs"."state"
) s
INNER JOIN (
SELECT
"core_members"."project_id",
CAST(SUM(((extract(epoch from (end_time - start_time))/ 3600))*num_nodes) AS DECIMAL) AS node_hours,
CAST(COUNT("jobstat_jobs"."id") AS DECIMAL) AS jobs
FROM
"jobstat_jobs"
INNER JOIN "core_members" ON "core_members"."login" = "jobstat_jobs"."login"
LEFT OUTER JOIN "sessions_projects_in_sessions" ON "sessions_projects_in_sessions"."project_id" = "core_members"."project_id"
WHERE
"jobstat_jobs"."state" NOT IN (N'COMPLETETED', N'RUNNING', N'unknown')
GROUP BY
"core_members"."project_id"
) n ON s."project_id" = n."project_id"
This will also allow you further filter the resulting query like so:
rpt = Report.new
q = rpt.query.where(rpt.n[:jobs].gt(12))
q.to_sql
#=> "...same as above...WHERE n.\"jobs\" > 12"
I'm trying to do the following, and if I were to uncomment the distinct it will break. Also if I comment out the order and leave the distinct in, it will work.
Contestant.joins('INNER JOIN votes AS V ON V.contestant_id = contestants.id AND V.season_id = '+ season_number.to_s)
.joins('LEFT OUTER JOIN votes AS XV ON (XV.contestant_id = '+self.id.to_s+') AND (XV.tribal_council_key = V.tribal_council_key) AND XV.contestant_voted_for_id = V.contestant_voted_for_id')
.joins('INNER JOIN season_rosters ON season_rosters.season_id = V.season_id')
.where('V.is_jury_vote = (?) AND V.contestant_id <> (?) AND XV.tribal_council_key IS NOT NULL', :false, self.id)
.order('season_rosters.finished')
#.distinct
The error I get is below...
TinyTds::Error: Incorrect syntax near '*'.: EXEC sp_executesql N'SELECT DISTINCT *, __order FROM ( SELECT [contestants].*, DENSE_RANK() OVER (ORDER BY season_rosters.finished ASC) AS __order, ROW_NUMBER() OVER (PARTITION BY [contestants].* ORDER BY season_rosters.finished ASC) AS __joined_row_num FROM [contestants] INNER JOIN votes AS V ON V.contestant_id = contestants.id AND V.season_id = 6 LEFT OUTER JOIN votes AS XV ON (XV.contestant_id = 112) AND (XV.tribal_council_key = V.tribal_council_key) AND XV.contestant_voted_for_id = V.contestant_voted_for_id INNER JOIN season_rosters ON season_rosters.season_id = V.season_id WHERE (V.is_jury_vote = (''false'') AND V.contestant_id <> (112) AND XV.tribal_council_key IS NOT NULL) ) AS __sq WHERE __joined_row_num = 1 ORDER BY __order'
The issue is with this part:
SELECT DISTINCT *, __order
Try adding the required columns to your GROUP BY.
Contestant.joins('INNER JOIN votes AS V ON V.contestant_id = contestants.id AND V.season_id = '+ season_number.to_s)
.joins('LEFT OUTER JOIN votes AS XV ON (XV.contestant_id = '+self.id.to_s+') AND (XV.tribal_council_key = V.tribal_council_key) AND XV.contestant_voted_for_id = V.contestant_voted_for_id')
.joins('INNER JOIN season_rosters ON season_rosters.season_id = V.season_id')
.where('V.is_jury_vote = (?) AND V.contestant_id <> (?) AND XV.tribal_council_key IS NOT NULL', :false, self.id)
.order('season_rosters.finished')
.group('col1,col2,__order')
Also in your SQL error, order by is on a different column while in your code, it is on season_rosters.finished.
The real tactical question I am facing is all categories are set as 'default' therefore if I make options[:category] = 'default' it only adds the points that have no category. Therefore if i add points to cateogry 'arin' it will not be counted to the 'default' total. So I tried to grab all tables if NOT NULL or by category but it keeps grabbing the same amount for 'arin'.
default: 20
arin: 20
Should be total of 40 if category not supplied or at 'default', if params category 'arin' then it should be 20.
Can someone help me understand the concept behind the correct SQL to get the results I am looking for?
New to rails and SQL.
def self.top_scored(options = {})
options[:table_name] ||= :users
options[:since_date] ||= 4.months.ago
options[:end_date] ||= 1.month.from_now
options[:category] ||= nil
options[:limit] ||= 10
alias_id_column = "#{options[:table_name].to_s.singularize}_id"
if options[:table_name] == :sashes
sash_id_column = "#{options[:table_name]}.id"
else
sash_id_column = "#{options[:table_name]}.sash_id"
end
# MeritableModel - Sash -< Scores -< ScorePoints
sql_query = <<SQL
SELECT
#{options[:table_name]}.id AS #{alias_id_column},
SUM(num_points) as sum_points
FROM #{options[:table_name]}
LEFT JOIN merit_scores ON merit_scores.sash_id = #{sash_id_column}
LEFT JOIN merit_score_points ON merit_score_points.score_id = merit_scores.id
WHERE merit_score_points.created_at > '#{options[:since_date]}' AND merit_score_points.created_at < '#{options[:end_date]}' AND (merit_scores.category IS NOT NULL OR merit_scores.category = '#{options[:category]}')
GROUP BY #{options[:table_name]}.id, merit_scores.sash_id
ORDER BY sum_points DESC
LIMIT #{options[:limit]}
SQL
results = ActiveRecord::Base.connection.execute(sql_query)
results.map do |h|
h.keep_if { |k, v| (k == alias_id_column) || (k == 'sum_points') }
end
results
end
end
Seems no one answered and only down voted. Here is to anyone that questions this in the future. I figured out you can split sql statements and use an if statement in rails around the SQL.
sql_query = "SELECT
#{options[:table_name]}.id AS #{alias_id_column},
SUM(num_points) as sum_points
FROM #{options[:table_name]}
LEFT JOIN merit_scores ON merit_scores.sash_id = #{sash_id_column}
LEFT JOIN merit_score_points ON merit_score_points.score_id = merit_scores.id
WHERE merit_score_points.created_at > '#{options[:since_date]}' AND merit_score_points.created_at < '#{options[:end_date]}' "
if(options[:category] != nil)
sql_query += "AND merit_scores.category = \"#{options[:category]}\" "
end
sql_query += "GROUP BY #{options[:table_name]}.id, merit_scores.sash_id
ORDER BY sum_points DESC
LIMIT #{options[:limit]} "
results = ActiveRecord::Base.connection.execute(sql_query)
SELECT if((a.status is null)or(a.status='')or(a.status='-'),'Not Submitted Yet',a.status) as OBJ1, 'Cummulative Progress' as obj2, a.project_id, gp.team_name,
COUNT(DISTINCT(a.task_id)) as NILAI
FROM task a INNER JOIN
site b ON a.site_id=b.site_id LEFT OUTER JOIN
task_privilige pv ON a.task_id = pv.task_id LEFT OUTER join
team gp on pv.team_id = gp.team_id LEFT OUTER join
task_tenant f ON a.task_id = f.task_id
WHERE 1 ".$xqr."
GROUP BY OBJ1, a.ID, a.PROJECT_ID, a.TASK_ID, a.PHASE, a.SITE_ID, a.ATTACHMENT_FILE, a.PLAN_DATE, a.BAPP, a.STATUS, a.REMARK, a.TASK_TYPE, a.LAST_UPDATE, a.LAST_UPDATER_ID, a.TASK_ID_REFERENCE, a.SOURCE_SITE_ID, a.DESTINATION, a.VENDOR, a.ADMIN_ID, a.ENGINEER_ID, a.MATERIAL_ID, a.REMARK_CHECKER, a.TAG_CHECK, a.TAG_MANUAL,
b.ID, b.SITE_NAME, b.PERIODE, b.SITE_ID, b.TYPE_SITE, b.BSC, b.ADDRESS, b.AREA, b.CITY, b.LAT, b.LANG, b.RADIUS, b.KETERANGAN, b.LAST_UPDATE, b.LAST_UPDATER_ID,
pv.ID, pv.TASK_ID, pv.TEAM_ID, pv.LAST_UPDATE, pv.LAST_UPDATER_ID,
gp.ID, gp.TEAM_ID, gp.TEAM_NAME, gp.TEAM_LEADER_ID, gp.LAST_UPDATE, gp.LAST_UPDATER_ID,
f.ID, f.TASK_ID, f.TENANT_NAME, f.TENANT_TYPE
You can use CASE:
CASE WHEN a.status IS NULL OR a.status = '' OR a.status = '-'
THEN 'Not Submitted Yet'
ELSE a.status
END AS OBJ1
Or you can use IIF:
IIF(a.status IS NULL OR a.status = '' OR a.status = '-',
'Not Submitted Yet', a.status) AS OBJ1
I have a Rails scope query that is returning duplicates. Can anyone see what's the problem with it?
Here's the code:
scope :visible_to_user, lambda { |user|
{
:joins => 'LEFT JOIN SCHEMA.groups ON groups.id = uploaded_files.group_id
LEFT JOIN uploaded_file_references ON uploaded_files.id = uploaded_file_references.uploaded_file_id
LEFT JOIN message_threads ON message_threads.id = uploaded_file_references.thread_id
LEFT JOIN thread_participants ON thread_participants.message_thread_id = message_threads.id',
:conditions => [
%{
uploaded_files.in_private_conversation = false
AND ( ( NOT COALESCE(groups.private, false) )
OR uploaded_files.group_id IN (?)
OR ( thread_participants.referenced_id = '?'
AND thread_participants.referenced_type = 'User')
)
}, user.group_ids, user.id
]
}
}
In rails >= 3:
You can add .uniq call to any scope to not return duplicates, like so:
MyTable.a_scope.where(something).uniq
In rails < 3:
you have to add it by hand with a select configuration like so:
MyTable.find(:all, :select => "distinct my_table.*")