How to fetch those agent ids efficiently in Rails - mysql

Check following section is model related sections :
ALL_STATUS = ['approved', 'pending', 'processing', 'declined', 'rejected']
#agent.rb
has_many :reports
has_many :assessments, through: :reports
#report.rb
has_many :assessments
belongs_to :agent
#assessment.rb
belongs_to :agent
Those are my sample records in DB.
#agent
id name
1 Alex
2 Justin
3 Clark
4 Mike
#reports
id agent_id status
1 1 approved
2 1 pending
3 1 processing
4 1 rejected
#assessment
id report_id agent_id status
1 1 1 approved
2 3 1 processing
3 2 1 pending
4 4 1 rejected
Desc:
once we create reports those are getting converted to assessment
record.
Here reports submitted by agent#Alex(user_id: 1) has been added for assessment.
I want to find out those UserIDs whose all assessments are not Yet added / Reject those user_ids whose reports are already added to
assessment List?
So here output will be only [2,3,4]. (those ids might have reports record but may not have any assessments)
I am using following code to get those agent ids.
eligible_agents = []
result = Agent.joins(:reports, :assessments)
result.each do |agent|
if(agent.reports.pluck(:status).count !=agent.assessments.pluck(:status).count)
eligible_agents.push agent.id
end
end
Here the problem is clearly visible, if number of reports/assessments grows then query present inside if condition is going to run multiple times.
What better solution we can have here ?

The following will return any Agent who does not have an Assessment:
Agent.where.not(id: Assessment.all.pluck(:agent_id))
Is this what you were looking for?
If you need just the Agent ids:
Agent.where.not(id: Assessment.all.pluck(:agent_id)).pluck(:id)

Instead of going though Agent you want to get all reports that don't have associated assessment. Then grab agent_ids from that list. I think joins does an inner join by default, so you need to specify left join condition and filter things that are missing assessments. Then you can just pluck(:agent_id) out of that.

Related

Access: finding the corresponding value of maximum value

I have a database in which I perform an audit on a set of required documents, for several locations of those documents.
So I have a table named Locations and a table named Documents, which are correlated through a 2 x 2 relationship.
Every document can have multiple versions. In my query, I want to see only the most recent version of each document, so the max(Id).
Now, every version can be 'audited' (checked) multiple times, for example 2 times each year. Each Audit/check is stored in a record, and I want to show only the most recent audit for each document, so Max(ID).
This is my Selection Query:
SELECT [~Locations].Location, [+DocuProperties].Category, [~Documents].[Document name], Max([DocuVersion].Id) AS MaxDocuID, Max([Audit].Id) AS MaxAuditID, [Audit].Conclusion
FROM ([~Documents] INNER JOIN ([~Locations] INNER JOIN ([+DocuLocation] INNER JOIN [+DocuProperties] ON [+DocuLocation].Id = [+DocuProperties].DocuLocation) ON [~Locations].Id = [+DocuLocation].Location) ON [~Locations].Id = [+DocuLocation].DocuName) INNER JOIN (DocuVersion INNER JOIN 2Audit ON [DocuVersion].Id = [Audit].DocuVersion) ON [+DocuProperties].Id = [DocuVersion].DocuLocation
GROUP BY [~Locations].Location, [+Docuproperties].Category, [~Documents].[Document name], [Audit].Conclusion
However: I do not wish to Group on Audit Conclusion, I wish to show the Audit conclusion that corresponds to the Max(Id) of that Audit.
So for every most recent Audit, I want to show the Conclusion. This conclusion I want to show for each Document, grouped byCategory and grouped byLocation.
I know I need to build a nested subquery of some form, but I just can't get any code to work.
I hope anybody can help.
The basic idea is like this:
Table 1
DocuProperties
Id Location Category
1 15 1
2 15 1
3 14 2
(every location can have multiple document properties a.k.a. objects)
Table2
DocuVersion
Id DocuProperty DocumentEndDate
1 1 01-01-2022
2 1 20-07-2023
3 2 31-07-2023 etc.
4 3 01-10-2023
(every DocuProperties can have multiple versions, I have to check If they are still valid, but also on some other criteria ).
Table 3
Audit
Id DocuVersion Conclusion
1 1 Not Valid
2 1 Not Valid
3 2 Valid
4 4 Valid
(every version can be audited multiple times. Every audit can have a different conclusion)
Which I would like to translate into the following:
LASTAudit (a.k.a. the most recent audit of the most recent version of the most recent property)
Location DocutPropertyId DocuVersionId AuditId Conclusion
15 2 2 2 Not Valid
14 3 4 4 Valid
The ID’s were easy to get right, as those were just Max(Id) functions. The problem was to get the Conclusion corresponding to that audit of that version of that object.

How do I do a MySQL JOIN on conditions based on mathematical operations across two tables?

I have two tables in a CRM application that I am trying to build.
"Contacts" Table:
id
Name
ContactFrequency (in days)
1
John
7
2
Pete
30
"Events" Table:
id
Contacts_id
Description
Unix_Timestamp
1
1
Sent John an email
1609667504
2
1
Gave John a call
1609645455
1
2
Sent Pete a letterl
1609666755
The "ContactFrequency" is how often I should call that client to stay in touch. Each entry in Events is a call log with a UNIX timestamp. I want to generate a list of clients who need to be called in the next X number of days (or whose next contacts have already passed and are overdue), such as the following (timestamps are completely arbitrary in my examples):
Client
Next Contact Due on (Timestamp)(Ordered by this column)
Pete
1609645352
John
1609634342
How would I do this with a query? I can't wrap my head around it.
Formally:
SELECT Contacts.Name Client,
COALESCE(FROM_UNIXTIME(MAX(Events.Unix_Timestamp)) + INTERVAL Contacts.ContactFrequency DAY, CURRENT_TIMESTAMP) NextContactDueOn
FROM Contacts
LEFT JOIN Events ON Contacts.id = Events.Contacts_id
GROUP BY Client
ORDER BY NextContactDueOn DESC

MySQL - counter query

Have two tables users and user_demographics
users has the basic structure of (does have more fields but not needed here):
id name email gender age ethnicity
1 test1 test1#test.com 1 1 1
2 test2 test2#test.com 1 2 1
3 test3 test3#test.com 2 3 2
4 test4 test4#test.com 3 1 1
5 test5 test5#test.com 2 4 5
**Gender**:
1 - Male,
2 - Female,
3 - Prefer not to say
**Age**:
1 - 16-20,
2 - 21-24,
3 - 25-30,
4 - 31-24
**Ethnicity**:
1 - White,
2 - Black,
5 - Prefer not to say
and so on and currently have around 1000 users.
user_demographics structure is:
coreid, type, option (for the sake of this question 'type' will be text, just to make it clearer)
coreid, type option
1 gender 1
1 gender 2
1 age 1
1 age 3
1 ethnicity 1
2 gender 2
2 gender 3
2 age 3
3 gender 1
On a web based form I have 3 sets of checkbox lists, one for each option gender, age, ethnicity and the a user can select multiple from each. They click update and these details are stored in the mysql database as above. coreid is related to another table, but not relevant here.
What I'm trying to do is get a total count of users for each coreid regardless of what type it is. The count should get smaller the more options you select. So coreid 3 should have the biggest count because I've only selected one option.
Example: coreid 3 is selecting all males
Example: coreid 2 is selecting all (females AND 'prefer not to say') AND age range 25-30
Struggling on how to create a single query that will give me the results I need, hope this makes sense.
The idea behind the over all system is that we have a large form that a user fill outs and we store in the information in the users table. Then a member of the admin team can go in and select these users by selecting options from the various demographics information we have collected. So they might just want to see everyone that has ticked the gender options of 'male' and 'prefer not to say' for example. Another admin member may go in and say they want all males, between the age of 25-30. Or they could just tick all options under gender. The idea is that they can select any combination and get a list of results. At the minute I just need to get a count back for the combination selected.
By the SOUNDS of it, you are probably going to need to do with dynamic SQL where you actually build the query on-the-fly, then execute that. Also, to clarify what I THINK you are asking is as follows. CoreID is like a set of filters that some manager is interested in getting count and or details of specific users. They are interested in
EITHER gender condition (1 or 2)
AND EITHER age condition (1 or 3)
AND just the one ethnicity
to possibly target products that might hit those demographics. So you would pre-query every record for CoreID = 1 then start building your query. You would want to order your query by the TYPE to group common items such as the gender, age, ethnicity categories.
Then, within your either localized code (not indicated such as C#, VB, java, whatever), you would need to build the query in such a way that you parenthesis OR those within same category, and logical AND between different such as
where
( Gender = 1
OR Gender = 2 )
AND ( Age = 1
OR Age = 3 )
AND ( Ethnicity = 1 )
If you are trying to write as a MySQL stored procedure, it would be a type of dynamic SQL query... either way, the WHERE clause needs to be constructed from the Core criteria someone is looking for.
You are correct, the last one would be easiest for CoreID = 3 would be a simple
WHERE ( Gender = 1 )
Clarify language source and I or others might be able to offer additional direction, but if I am accurate, you should try to write your own first pass of code, but I will shoot out a pseudo-code for you something like
Get Records Ordered for one CoreID, order by the type of criteria.
prep variable identifying if pending open Parenthesis
prep variable identifying last "type" building for.
for each record
If new type
if has Open Parenthesis
add closing paren
add logical AND before the next entry we are getting
add open parenthesis
set flag we have open parenthesis
else
since same type as last type, add logical OR
go to next record, repeat.
If after last record we would always need to close parenthesis even if a single criteria

Excluding unique ID in a query if at least one criteria is met

I'm having this problem which I'm unsure how to resolve.
Here's the situation : I want to get a list of all individuals who have not completed a survey. It is however possible for someone to start/complete multiple surveys.
Therefore, I want the list of individuals who have not completed at least one survey.
Here's what my query looks likes to get the list of people with incomplete surveys :
SELECT Survey.UserID, Survey.Fullname
FROM [...]
WHERE Survey.SurveySubmitted = 0 -- 0 = Unsubmitted, 1 = submitted
Now this is what the database could look like
UserID Fullname SurveySubmitted
1 John Smith 0
2 Jane Doe 1
3 Tom Glass 0
3 Tom Glass 1
Now the above query will select both John Smith and Tom Glass. However, since Tom Glass already completed at least one survey, he should be excluded.
Any ideas to proceed? It most likely needs a SELECT within another SELECT but I'm having trouble picturing it.
You could check for the user not in the user that have submited/completed
select Survey.UserID, Survey.Fullname
from [.....]
where UserID NOT IN (
SELECT Survey.UserID, Survey.Fullname
FROM [...]
WHERE Survey.SurveySubmitted = 1
)
You should group by whatever uniquely identifies a User/Survey combination and then sum the # of surveys that have been submitted. You can then use a having clause to filter out rows > 0:
select *
from Survey
group by UserId, FullName
having sum(SurveySubmitted) = 0;
SQLFiddle Example

MySQL Order By and a Limit 1 subquery to return most recent record

I've noticed there are a few similar questions on StackOverflow, but nothing has worked for me so far. I'll try to keep this as short as possible.
I am building a query that needs to return a number of issues that may or may not have contracts, that may or may not be completed (completed_at would be set to a DateTime, not nil). Each row needs to include:
one row containing all the issue record's fields
the description from the budget_item
the completed_at date from the most recent contract that was completed (one budget_item could have 0 contracts, 1 contract, or 5+ contracts and any number of them could be open (completed_at :nil) or closed (completed_at: DateTime)
This is what I have so far (which returns the correct number of rows, but it is returning the most recently created contract, not the most recent
BaseItem.issues
.joins('LEFT JOIN budget_items
ON issues.id = budget_items.issue_id
LEFT JOIN contracts
ON budget_items.id = contracts.budget_item_id')
.select('issues.*, budget_items.description, contracts.completed_at AS resolved_at')
.group('issues.id')
.order('contracts.completed_at')
The code in the models is as follows:
class BaseItem < ActiveRecord::Base
has_many :issues
...
end
class Issue < ActiveRecord::Base
belongs_to :base_item
has_many :budget_items
...
end
class BudgetItem < ActiveRecord::Base
belongs_to :issue
has_many :contracts
...
end
class Contract < ActiveRecord::Base
belongs_to :budget_item
...
end
The end result needs to be something along the line of:
There will likely be multiple issues making up the different rows. Each issue has at least four budget_items which are used only for the budget_item.description which needs to appear in the final query and then are used to join each issue to its many contracts (each budget_item could have 2 or 3 contracts so the issue could end up having 8-12 contracts. From those contracts, the query needs to order them according to their completed_at attribute and return AS resolved_at only the most recent contract's completed_at date. If there were 4 contracts, two had completed_at: nil, the query should return the most recent of the two remaining completed_at dates as the resolved_at field of that particular issue.
Any help would be greatly appreciated and please let me know if I need to provide any additional information.
-Dave
The resulting query (from a comment):
SELECT issues.*, budget_items.description, contracts.completed_at AS resolved_at
FROM issues
LEFT JOIN budget_items ON issues.id = budget_items.issue_id
LEFT JOIN contracts ON budget_items.id = contracts.budget_item_id
WHERE issues.base_item_id = 6
GROUP BY issues.id
ORDER BY contracts.completed_at DESC
LIMIT 1
You don't need to show your actual data in the sample to make it useful...
This is what I mean by it. You could have put into your question the following sample data:
issues
id base_item_id
-- ------------
10 6
20 6
30 6
99 123
budget_items
id issue_id description
-- -------- -----------
1 10 'one contract, none completed'
2 20 'one contract, one completed'
3 30 'two contracts, one completed'
4 30 'three contracts, two completed'
contracts
id budget_item_id completed_at
-- -------------- ------------
1 1 NULL
2 2 2015-01-02
3 3 2015-01-03
4 3 NULL
5 4 2015-01-05
6 4 NULL
7 4 2015-01-07
expected result
issues.id contracts.completed_at budget_item.description
--------- ---------------------- -----------------------
10 NULL NULL
20 2015-01-02 one contract, one completed
30 2015-01-07 three contracts, two completed
Here is SQL Fiddle.
Is it what you want? Does my sample data cover all possible edge cases? If not, add more rows to it and show how it affects the result.
This is how the final query may look like. MySQL doesn't have things like CROSS APPLY or LATERAL JOINS, so it is less efficient than in other databases - the subquery will run twice.
I have no idea how to translate this SQL to Ruby - I never used Ruby.
SELECT
issues.*
,(
SELECT contracts.completed_at
FROM
budget_items
INNER JOIN contracts ON contracts.budget_item_id = budget_items.id
WHERE
budget_items.issue_id = issues.id
AND contracts.completed_at IS NOT NULL
ORDER BY contracts.completed_at DESC
LIMIT 1
) AS resolved_at
,(
SELECT budget_items.description
FROM
budget_items
INNER JOIN contracts ON contracts.budget_item_id = budget_items.id
WHERE
budget_items.issue_id = issues.id
AND contracts.completed_at IS NOT NULL
ORDER BY contracts.completed_at DESC
LIMIT 1
) AS description
FROM issues
WHERE issues.base_item_id = 6
The main idea is simple. We return one row for each issue. For each issue we find one latest contract using whatever conditions you need (like contracts.completed_at IS NOT NULL to look for completed contracts only).
If there is no completed contracts at all for an issue it returns NULL for description and resolved_at. You can add extra filter in the main SELECT to remove such rows if this is what you want (WHERE issues.base_item_id = 6 AND resolved_at IS NOT NULL).
.order('contracts.completed_at DESC LIMIT 1')
(or do I not understand what is missing from your code?)