Rails 5 - I need to return the first record of a group, but there are records that have no group - sql

So as the title suggests I need to return the records from a table, where these records can belong to a group.
If there are several records in a group, return only the last one, and if the record does not belong to any group, return it together.
I have the following tables
(automation_execution) 1 --> n (automation_execution_action) 1 <---> 1 (workable)
I need to return workable table records, where they may or may not be linked to automation tables.
automation_execution
id
company_id
1
1
2
1
automation_execution_ations
id
automation_execution_id
workable_id
1
1
1
2
1
2
workable
id
company_id
status
created_at
1
1
finished
2022-01-19 19:48:24
2
1
processing
2022-01-19 18:00:24
3
1
processing
2022-01-19 18:00:24
4
1
processing
2022-01-19 18:00:24
In the example above, we have 4 workables, 1 and 2 belong to an automation and 3 and 4 do not, in this example I would need to return the record 2, 3 and 4.

So this SQL works:
select workables.*
from (
select workables.*,
automation_execution_actions.automation_execution_id,
row_number()
over (partition by automation_execution_actions.automation_execution_id order by workables.id desc) as rn
from workables
left join automation_execution_actions on automation_execution_actions.workable_id = workables.id
) as workables
where rn = 1
OR automation_execution_id IS NULL
order by id;

Related

Recursive query with CTE

I need some help with one query.
So, I already have CTE with the next data:
ApplicationID
CandidateId
JobId
Row
1
1
1
1
2
1
2
2
3
1
3
3
4
2
1
1
5
2
2
2
6
2
5
3
7
3
2
1
8
3
6
2
9
3
3
3
I need to find one job per candidate in a way, that this job was distinct for table.
I expect that next data from query (for each candidate select the first available jobid that's not taken by the previous candidate):
ApplicationID
CandidateId
JobId
Row
1
1
1
1
5
2
2
2
8
3
6
2
I have never worked with recursive queries in CTE, having read about them, to be honest, I don't fully understand how this can be applied in my case. I ask for help in this regard.
The following query returns the expected result.
WITH CTE AS
(
SELECT TOP 1 *,ROW_NUMBER() OVER(ORDER BY ApplicationID) N,
CONVERT(varchar(max), CONCAT(',',JobId,',')) Jobs
FROM ApplicationCandidateCTE
ORDER BY ApplicationID
UNION ALL
SELECT a.*,ROW_NUMBER() OVER(ORDER BY a.ApplicationID),
CONCAT(Jobs,a.JobId,',') Jobs
FROM ApplicationCandidateCTE a JOIN CTE b
ON a.ApplicationID > b.ApplicationID AND
a.CandidateId > b.CandidateId AND
CHARINDEX(CONCAT(',',a.JobId,','), b.Jobs)=0 AND
b.N = 1
)
SELECT * FROM CTE WHERE N = 1;
However, I have the following concerns:
The recursive CTE may extract too many rows.
The concatenated JobId may exceed varchar(max).
See dbfiddle.

Identify a FK which has the highest value from a list of values in its source table

I have following tables.
Part
id
name
1
Part 1
2
Part 2
3
Part 3
Operation
id
name
part_id
order
1
Op 1
1
10
2
Op 2
1
20
3
Op 3
1
30
4
Op 1
2
10
5
Op 2
2
20
6
Op 1
3
10
Lot
id
part_id
Operation_id
10
1
2
11
2
5
12
3
6
I am selecting the results from Lot table and I want to select a column last_Op which is based on the order value of the operation_id. If value of order for the operation_id is the highest for the respective part_id, return 1 else return 0
SELECT
id,
part_id,
operation_id,
last_Op
FROM Lot
expected result set based on the tables above.
id
part_id
operation_id
last_op
10
1
2
0
11
2
5
1
12
3
6
1
In above example, first row returns last_op = 0 because operation_id = 2 is associated with part_id = 1 and it has the highest order = 30. Since operation_id for this part is not pointing towards the highest order value, 0 is returned.
The other two rows return 1 because operation_id 5 and 6 are associated with part_id 2 and 3 respectively and they are pointing towards the highest 'order' value.
If value of order for the operation_id is the highest for the respective part_id, return 1 else return 0
This sounds like window functions will help:
select l.*,
(case when o.order = o.max_order then 1 else 0 end) as last_op
from lot l left join
(select o.*,
max(o.order) over (partition by o.part_id) as max_order
from operations o
) o
on l.operation_id = o.id;
Note: order is a very poor name for a column because it is a SQL keyword.

Update new foreign key column of existing table with ids from another table in SQL Server

I have an existing table to which I have added a new column which is supposed to hold the Id of a record in another (new) table.
Simplified structure is sort of like this:
Customer table
[CustomerId] [GroupId] [LicenceId] <-- new column
Licence table <-- new table
[LicenceId] [GroupId]
The Licence table has a certain number of licences per group than can be assigned to customers in that same group. There are multiple groups, and each group has a variable number of customers and licences.
So say there are 100 licences available for group 1 and there are 50 customers in group 1, so each can get a license. There are never more customers than there are licences.
Sample
Customer
[CustomerId] [GroupId] [LicenceId]
1 1 NULL
2 1 NULL
3 1 NULL
4 1 NULL
5 2 NULL
6 2 NULL
7 2 NULL
8 3 NULL
9 3 NULL
Licence
[LicenceId] [GroupId]
1 1
2 1
3 1
4 1
5 1
6 1
7 2
8 2
9 2
10 2
11 2
12 3
13 3
14 3
15 3
16 3
17 3
Desired outcome
Customer
[CustomerId] [GroupId] [LicenceId]
1 1 1
2 1 2
3 1 3
4 1 4
5 2 7
6 2 8
7 2 9
8 3 12
9 3 13
So now I have to do this one time update to give every customer a licence and I have no idea how to go about it.
I'm not allowed to use a cursor. I can't seem to do a MERGE UPDATE, because joining the Customer to the Licence table by GroupId will result in multiple hits.
How do I assign each customer the next available LicenceId within their group in one query?
Is this even possible?
You can use window functions:
with c as (
select c.*, row_number() over (partition by groupid order by newid()) as seqnum
from customers c
),
l as (
select l.*, row_number() over (partition by groupid order by newid()) as seqnum
from licenses c
)
update c
set c.licenceid = l.licenseid
from c join
l
on c.seqnum = l.seqnum and c.groupid = l.groupid;
This assigns the licenses randomly. That is really just for fun. The most efficient method is to use:
row_number() over (partition by groupid order by (select null)) as seqnum
SQL Server often avoids an additional sort operation in this case.
But you might want to order them by something else -- for instance by the ordering of the customer ids, or by some date column, or something else.
Gordon has put it very well in his answer.
Let me break it down into simpler steps for you.
Step 1. Use the ROW_NUMBER() function to assign a SeqNum to the Customers. Use PARTITION BY GroupId so that the number starts from 1 in every group. I would ORDER BY CustomerId
Step 2. Use the ROW_NUMBER() function to assign a SeqNum to the Licences. Use PARTITION BY GroupId so that the number starts from 1 in every group. ORDER BY LicenseId because your ask is to "assign each customer the next available LicenceId within their group".
Now use these 2 queries to update LicenseId in Customer table.

Summing up only the values of previous rows with the same ID

As I am preparing my data for predicting no-shows at a hospital, I ran into the following problem: In the query below I tried to get the number of shows/no-shows relatively shown to the number of appointments (APPTS). INDICATION_NO_SHOW means whether a patient showed up at a appointment. 0 means show, and 1 means no-show.
with t1 as
(
select
PAT_ID
,APPT_TIME
,APPT_ID
,ROW_NUMBER () over(PARTITION BY PAT_ID order by pat_id,APPT_TIME) as [TOTAL_APPTS]
,INDICATION_NO_SHOW
from appointments
)
,
t2 as
(
t1.PAT_ID
,t1.APPT_TIME
,INDICATION_NO_SHOW
,sum(INDICATION_NO_SHOW) over(order by PAT_ID, APPT_TIME ) as TOTAL_NO_SHOWS
,TOTAL_APPT
from t1
)
SELECT *
,(TOTAL_APPT- TOTAL_NO_SHOWS) AS TOTAL_SHOWS
FROM T2
order by PAT_ID, APPT_TIME
This resulted into the following dataset:
PAT ID APPT_TIME INDICATION_NO_SHOW TOTAL_SHOWS TOTAL_NO_SHOWS TOTAL_APPTS
1 1-1-2001 0 1 0 1
1 1-2-2001 0 2 0 2
1 1-3-2001 1 2 1 3
1 1-4-2001 0 3 1 4
2 1-1-2001 0 0 1 1
2 2-1-2001 0 1 1 2
2 2-2-2001 1 1 2 3
2 2-3-2001 0 2 2 4
As you can see my query only worked for patient 1, and then it also counts the no-shows for patient 1 for patient 2. So individually it worked for 1 patient, but not over the whole dataset.
The TOTAL_APPTs column worked out, because it counted the number of appts the patient had at the moment of that given appt. My question is: How do I succesfully get these shows and no-shows succesfully added up (as I did for patient 1)? I'm completely aware why this query doesn't work, I'm just completely in the blue on how to fix it..
I think that you can just use window functions. You seem to be looking for window sums of shows and no shows per patient, so:
select
pat_id,
appt_time,
indication_no_show,
sum(1 - indication_no_show)
over(partition by pat_id order by appt_time) total_shows,
sum(indication_no_show)
over(partition by pat_id order by appt_time) total_no_shows
from appointments

Retrieve unique rows based on id

I have two tables:
Report
ReportId CreatedDate
1 2018-01-12
2 2018-02-12
3 2018-03-12
ReportSpecialty
SpecialtyId ReportId IsPrimarySpecialty
1 1 1
2 2 1
3 3 1
1 2 0
1 3 0
I am trying to write a query that will retrieve me the last 10 reports that were published. However, I need to get 1 report from each specialty. Assume there are 100 specialties, I can pass in as an argument any number of specialties, 10, 20, 5, 2, etc...
I'm trying to figure out a way where if I send it all specialties, it will get me the last 10 reports posted based on the last date created, but it won't give me 2 articles from same specialty. If I send it 10 specialties, then I will get 1 of each. If I send it 5, then I'll get 2 of each. If I send it 3 then I'll get 4 of 1 and 3 of other two.
I may need to write multiple queries for this, I'm trying to see if there is a way to do this on the SQL side of things? If there isn't, then how would I break down to multiple queries to get the result I want?
What I have tried is this, however I get multiple reports with same specialties:
SELECT TOP 10 r.ReportId, rs.SpecialtyId, r.CreatedDate
FROM Report r
INNER JOIN ReportSpecialty rs ON r.ReportId = rs.ReportId AND rs.IsPrimarySpecialty = 1
GROUP BY rs.SpecialtyId, r.AceReportid, r.CreatedDate
ORDER BY r.CreatedDate DESC
with cte as (
SELECT R.ReportId, R.CreatedDate, RS.SpecialtyId,
ROW_NUMBER() OVER (PARTITION BY RS.SpecialtyId
ORDER BY R.CreatedDate DESC) as rn
FROM Report R
JOIN ReportSpecialty RS
ON R.ReportId = RS.ReportId
AND RS.IsPrimarySpecialty = 1
WHERE RS.SpecialtyId IN ( .... ids ... )
)
SELECT TOP 10 *
FROM cte
ORDER BY rn, CreatedDate DESC
row_number will create a id for each speciality, so if you pass 3 speciality you will get something like this.
rn speciality_id
1 1
1 2
1 3
2 1
2 2
2 3
3 1
3 2
3 3