rails way to write a complex query - ruby-on-rails

I have this query:
SELECT f.id,
Concat(f.name, ' ', REPLACE(f.parent_names, ',', ' ?')) AS FullName,
u.name AS Unit,
u.id AS UnitId,
u.position AS UnitPosition,
city.name AS City,
hus.mobile1 AS HusMobile,
wife.mobile1 AS WifeMobile,
hus.first_name AS Spouse1,
wife.first_name AS Spouse2,
f.phone AS HomePhone,
f.email AS Email,
Date_format(f.contact_initiation_date, '%d/%m/%Y') AS InitDate,
f.contact_initiation_date AS sql_date,
Date_format(f.status_change_date, '%d/%m/%Y') AS StatususChangeDate,
stts.name AS 'Status',
(SELECT Group_concat(' ', t.name, '<', t.id, '>' ORDER BY t.position DESC
)
FROM taggings tgs
JOIN tags t
ON tgs.tag_id = t.id
WHERE tgs.taggable_type = 'family'
AND tgs.taggable_id = (SELECT DISTINCT taggable_id
FROM taggings
WHERE tag_id IN( 76, 72, 74 )
AND taggable_id = f.id)) AS HandlingStatus,
Date_format(f.reconnection_date, '%d/%m/%Y') AS ReconnectionDate,
f.reconnection_date AS reconnection_sql_date,
Date_format(cmt.created_at, '%d/%m/%Y') AS CommentDate,
cmt.comment AS LastComment,
usr.name AS Comentator,
Format(e.income_total, 0) AS Income,
Format(e.expense_total, 0) AS Expense,
Format(e.debts_total, 0) AS Debts
FROM families f
JOIN categories stts
ON f.family_status_cat_id = stts.id
JOIN units u
ON f.unit_id = u.id
JOIN categories city
ON f.main_city_cat_id = city.id
LEFT JOIN comments cmt
ON f.last_comment_id = cmt.id
LEFT JOIN contacts hus
ON hus.id = f.husband_id
LEFT JOIN contacts wife
ON wife.id = f.wife_id
LEFT JOIN users usr
ON usr.id = cmt.user_id
LEFT JOIN escort_requests e
ON e.family_id = f.id
WHERE ( 1 = 0
OR ( u.is_busy = 0
AND f.family_status_cat_id = 1421 )
OR ( u.is_busy = 1
AND f.family_status_cat_id = 1421 )
OR ( f.family_status_cat_id = 1423 )
OR ( f.family_status_cat_id = 1424 ) )
HAVING ( 1 = 1
AND handlingstatus IS NOT NULL
AND fullname LIKE '%%' )
ORDER BY fullname
LIMIT 0, 100
How do I write it in Rails? Right now I build an array with a string, but is there a rails way to do it better? If there is no better way, just mention it.
The values in the line: WHERE tag_id IN( 76, 72, 74 ) are being created dynamically.

I've been trying to integrate some complex SQL with Rails, and found no better way than to run the SQL directly. Anything that strays away from a very simple query can get very difficult to code (and understand) in activerecord.

Related

Distinct join between 2 tables

this is my code :
SELECT DISTINCT Emps.name, Degrees.Name AS degree, Degrees.Date AS degree_date
FROM Emps INNER JOIN
Degrees ON Emps.id = Degrees.empId
but distict dosn't work , i want this result
i want distict name with degree which has max id or max date
thanks for all i found the solution
SELECT e.name, d.Name AS degree
FROM Emps AS e
full JOIN (
SELECT t.*, ROW_NUMBER() OVER (PARTITION BY t.empId ORDER BY t.id DESC) AS rn FROM Degrees AS t
) AS d ON e.id = d.empId
WHERE d.rn = 1

What table or view can I get a Work Items Tags in TFS 2017?

I have a recent request to include Work ItemTag information in a daily SSRS report that I generate. I was led to the SQL query below. The problem is that I cannot find a table or a view with the "WorkItemsAre" in the name. So giving that I have the WorkItemSK, System_Id, ProjectSK and CollectionGUID among many other things, how and where can I get the tags querying directly against the TFS database (I know querying against the TFS databases is not recommended but It's in my requirements)?
SELECT DISTINCT WorkItemsAre.ID, WorkItemsAre.Title, tbl_TagDefinition.Name
--,tbl_PropertyValue.ArtifactId, *
FROM tbl_TagDefinition
LEFT JOIN tbl_PropertyDefinition ON tbl_PropertyDefinition.Name =
'Microsoft.TeamFoundation.Tagging.TagDefinition.' + CONVERT(NVARCHAR(400),
tbl_TagDefinition.TagId)
LEFT JOIN tbl_PropertyValue ON tbl_PropertyValue.PropertyId =
tbl_PropertyDefinition.PropertyId
--LEFT JOIN WorkItemLongTexts ON WorkItemLongTexts.ID =
tbl_PropertyValue.ArtifactId
left join WorkItemsAre on WorkItemsAre.ID = tbl_PropertyValue.ArtifactId
WHERE
(
SELECT SUM(CASE WHEN IntValue = 0 THEN 1 ELSE -1 END) NB
FROM tbl_PropertyValue PROP_CNT
WHERE PROP_CNT.PropertyId = tbl_PropertyDefinition.PropertyId
AND WorkItemsAre.ID = PROP_CNT.ArtifactId
) > 0
TFS warehouse does not contain tags. For TFS 2018 (maybe also for tfs 2017) you can find information in table tbl_WorkItemCoreLatest in operational database (in my case: Tfs_DefaultCollection)
SELECT DISTINCT tbl_WorkItemCoreLatest.Id,tbl_TagDefinition.Name
--,tbl_PropertyValue.ArtifactId, *
FROM tbl_TagDefinition
LEFT JOIN tbl_PropertyDefinition ON tbl_PropertyDefinition.Name = 'Microsoft.TeamFoundation.Tagging.TagDefinition.' + CONVERT(NVARCHAR(400), tbl_TagDefinition.TagId)
LEFT JOIN tbl_PropertyValue ON tbl_PropertyValue.PropertyId = tbl_PropertyDefinition.PropertyId
--LEFT JOIN WorkItemLongTexts ON WorkItemLongTexts.ID = tbl_PropertyValue.ArtifactId
left join tbl_WorkItemCoreLatest on tbl_WorkItemCoreLatest.Id = tbl_PropertyValue.ArtifactId
WHERE
(
SELECT SUM(CASE WHEN IntValue = 0 THEN 1 ELSE -1 END) NB
FROM tbl_PropertyValue PROP_CNT
WHERE PROP_CNT.PropertyId = tbl_PropertyDefinition.PropertyId
AND tbl_WorkItemCoreLatest.Id = PROP_CNT.ArtifactId
) > 0

Refactor a query with two subselects in WHERE

I've got the following scope on the Project model to find projects to which at least 80 crowdfunding pledges have been done or projects which have reached their goal already:
class Project < ActiveRecord::Base
scope :popular, -> { collecting.where("80 <= (?) OR goal <= (?)",
Pledge.select('COUNT(*)').where("project_id = projects.id").where(paid: true),
Pledge.select('SUM(amount)').where("project_id = projects.id").where(paid: true))
}
(...)
end
This works just fine and produces the following SQL for Postres:
Project.popular
# SELECT "projects".* FROM "projects" WHERE "projects"."state" = 'collecting' AND (80 <= (SELECT COUNT(*) FROM "pledges" WHERE (project_id = projects.id) AND "pledges"."paid" = 't') OR goal <= (SELECT SUM(amount) FROM "pledges" WHERE (project_id = projects.id) AND "pledges"."paid" = 't'))
It's important to get the count as well:
Project.popular.count
# SELECT COUNT(*) FROM "projects" WHERE "projects"."state" = 'collecting' AND (80 <= (SELECT COUNT(*) FROM "pledges" WHERE (project_id = projects.id) AND "pledges"."paid" = 't') OR goal <= (SELECT SUM(amount) FROM "pledges" WHERE (project_id = projects.id) AND "pledges"."paid" = 't'))
Okay, the subselects are nice, but I have this feeling that there's a better and more efficient way to do this. I've tried joins and sum, but the mandatory group to use aggregate functions breaks Project.popular.count.
Any ideas how to refactor this? Maybe just a way to do where("project_id = projects.id") in Hash notation?
I don't know if this is any good for you because I can't express the below query in the ActiveRecord lingo but if you can it will be a big improvement over the poor query currently being generated
select "projects".*, project_count, project_amount
from
"projects"
inner join (
select
id,
count(*) as project_count,
sum(amount) as project_amount
from pledges
group by id
where paid
) pledges using (id)
where
"projects"."state" = 'collecting'
and
(project_count >= 80 or project_amount >= goal)
Thanks, #Clodonaldo! With a few litte touches, your query works like a charm:
select "projects".*, pledges_count, pledges_sum
from
"projects"
inner join (
select
project_id as id,
count(*) as pledges_count,
sum(amount) as pledges_sum
from pledges
where paid
group by project_id
) pledges using (id)
where
"projects"."state" = 'collecting'
and
(pledges_count >= 80 or pledges_sum >= goal)
Not sure thou whether AR or AREL can build this.
UPDATE:
The shortest I can come up with is (I don't need the count and sum values):
Project.collecting.joins(
"INNER JOIN (",
Pledge.paid.group(:project_id).select("
project_id AS id,
COUNT(*) AS count,
SUM(amount) AS sum
").to_sql,
") pledges USING (id)"
).where("count >= 80 OR sum >= goal")
The above produces:
SELECT "projects".*
FROM
"projects"
INNER JOIN (
SELECT
project_id AS id,
COUNT(*) AS count,
SUM(amount) AS sum
FROM "pledges"
WHERE "pledges"."paid" = 't'
GROUP BY project_id
) pledges USING (id)
WHERE
"projects"."state" = 'collecting'
AND
(count >= 80 OR sum >= goal)

Compute sum SQLPlus

I'm struggling to figure out the issue with my SQL table using compute sum.
All that is displayed where the sum of the column should be is a blank box!
Code Below:
TTITLE CENTER ==================== SKIP 1-
CENTER 'U T O O L' skip 1-
CENTER ==================== SKIP 1 -
LEFT 'Tool Report 1.03' SKIP 1 -
LEFT ============ SKIP 2-
RIGHT 'Page:' -
FORMAT 999 SQL.PNO SKIP 2
set pagesize 50
column MEMBERNAME HEADING 'Member Name' format a20
compute sum of TOTAL on Rental_ID
Break on RENTAL_ID
select Member.Member_ID, SUBSTR(Member.FName,0,10) || SUBSTR(' ',0,10) ||
SUBSTR(Member.SName,0,15) as MEMBERNAME,
Rental.Rental_ID,
Tool.Name,
Rental_Line.Qty,
Rental_Line.Price,
TO_Char(Rental_Line.Qty*Rental_Line.Price,'L9,999.99') TOTAL
from Rental_Line
INNER JOIN Rental
on Rental.Rental_ID = Rental_Line.Rental_ID
INNER JOIN Member
on Rental.Member_ID = Member.Member_ID
INNER JOIN Tool_Instance
on Rental_Line.Tool_Instance_ID = Tool_Instance.Tool_Instance_ID
INNER JOIN Tool
on Tool_Instance.Tool_ID = Tool.Tool_ID
where Rental.Rental_ID = '&Rental_ID';
may be this help you, as I understood you need SUM(Rental_Line.Qty) OVER (PARTITION BY Rental.Rental_ID)
select Member.Member_ID,
SUBSTR(Member.FName, 0, 10) || SUBSTR(' ', 0, 10) ||
SUBSTR(Member.SName, 0, 15) as MEMBERNAME,
Rental.Rental_ID,
Tool.Name,
Rental_Line.Qty,
Rental_Line.Price,
TO_Char(Rental_Line.Qty * Rental_Line.Price, 'L9,999.99') TOTAL,
SUM(Rental_Line.Qty) OVER (PARTITION BY Rental.Rental_ID) TOTAL_QTY,
SUM(Rental_Line.Qty * Rental_Line.Price) OVER (PARTITION BY Rental.Rental_ID) TOTAL_SUM
from Rental_Line
INNER JOIN Rental on Rental.Rental_ID = Rental_Line.Rental_ID
INNER JOIN Member on Rental.Member_ID = Member.Member_ID
INNER JOIN Tool_Instance on Rental_Line.Tool_Instance_ID =
Tool_Instance.Tool_Instance_ID
INNER JOIN Tool on Tool_Instance.Tool_ID = Tool.Tool_ID
where Rental.Rental_ID = '&Rental_ID';

find in a "anded" array

I have 2 tables : entradas (id, blah...) and georelaciones (:entrada_id, lugar_id, blah...). georelaciones is just a link table for a polymorphic relation.
I want to retrieve all the entradas which are in relation with each lugar_id (pass in an array).
I have build this ugly scope ("d" will be an array of lugar_id):
scope :con_pais, lambda {|d|
# select e.id from entradas as e inner join georelaciones as g1 on (g1.lugar_id = 55 and g1.entrada_id = e.id) inner join georelaciones as g2 on (g2.lugar_id = 66 and g2.entrada_id = e.id)
cadena = ""
i = 0
d.each{ |lugar_id| i += 1 ; cadena << " inner join georelaciones as g" + i.to_s + " on (g"+ i.to_s + ".lugar_id = " + lugar_id.to_s + " and g" + i.to_s + ".entrada_id = e.id)"}
entradas = Entrada.find_by_sql("select distinct e.id from entradas as e " + cadena)
a = []
entradas.each{ |e| a << e.id }
Entrada.where("id in (?)", a)
}
I know that's not good because my table entradas has some million records and I do not take advantage of lazy loading due to the use of find_by_sql.
How can I query the database and return an ActiveRecord::Relation directly without using find_by_sql?
Entrada.find_by_sql("select distinct e.id from entradas as e " + cadena)
=>
Entrada.select("distinct e.id as e #{cadena}")
Assuming that I got your relations correct, you should be able to do something like this:
class Georelacion < ActiveRecord::Base
belongs_to :entrada
scope :con_pais, lambda { |d|
includes(:entrada).where("georelaciones.lugar_id IN (?)", d)
}
...
end
And then in the controller:
#lugar_ids = [1,2,3,4]
#georelaciones = Georelacion.con_pais(#lugar_ids).group_by(&:lugar_id)
That should give you a hash where each key is a lugar_id and the value for each key is an array of georelaciones with the associated entrada already loaded from the database.

Resources