Moodle schema changes? - sql

With a recent upgrade to Moodle 2.7, a customer of ours is reporting their CustomSQL reports are failing. For example, this query used to report gradeable items, but fails now:
SELECT
u.firstname AS "First",
u.lastname AS "Last",
c.fullname AS "Course",
a.name AS "Assignment"
FROM prefix_assignment_submissions AS asb
JOIN prefix_assignment AS a ON a.id = asb.assignment
JOIN prefix_user AS u ON u.id = asb.userid
JOIN prefix_course AS c ON c.id = a.course
JOIN prefix_course_modules AS cm ON c.id = cm.course
WHERE asb.grade < 0 AND cm.instance = a.id
AND cm.module = 1
ORDER BY c.fullname, a.name, u.lastname
A quick query or two to the DB shows there are zero rows in prefix_assignment_submissions and prefix_assignment. Suggestions?

The assignment module was replaced by the assign module in Moodle 2.2.
The old assignment module was disabled by default in Moodle 2.5 (I think) and removed completely in Moodle 2.7.
The query will need rewriting to use the assign_submissions table (and any other assign_* tables that are relevant).

I don't have a complete answer for you, but I can tell you that I also admin a Moodle 2.7 system, and my prefix_assignment_submissions table also has no records.
Additionally, I can give you the below query that I wrote to report on course final grades. We use this query for retention modeling through the semester, and for importing final grades to our student information system at the end of each term, where the idnumber in the mdl_course table will always match the course code followed by the year/term code in our student information system. I think it might be helpful because of how it uses the mdl_grade_items table: there are more itemtypes in that table than just course. In this table, an ungraded item would have a NULL value in the finalgrade field. Unfortunately, I don't know the Moodle internals enough to guarantee there will be a record in this table for every assignment, but it's a starting place.
SELECT u.username,u.lastname, u.firstname,c.shortname, left(c.idnumber, character_length(c.idnumber)-6) AS crs_cde,
right(c.idnumber,5) as yearterm,cast((gg.finalgrade/case when gi.grademax = 0 then 1 else gi.grademax end) * 100 as numeric(5,2)) finalgrade,
(SELECT l.letter
FROM mdl_context x
INNER JOIN mdl_grade_letters l ON l.contextid = x.id
WHERE x.instanceid in (c.id, 0) and l.lowerboundary <= round((gg.finalgrade/case when gi.grademax = 0 then 1 else gi.grademax end)*100,2)
ORDER BY x.id desc, lowerboundary desc limit 1) letter
FROM mdl_grade_grades gg
INNER JOIN mdl_grade_items gi ON gi.id=gg.itemid
INNER JOIN mdl_user u ON u.id=gg.userid
INNER JOIN mdl_course c on c.id = gi.courseid
INNER JOIN mdl_course_categories c2 on c2.id = c.category
WHERE gi.itemtype='course' and c2.visible = 1 and gg.finalgrade is not null
and char_length(c.idnumber) > 0 and right(c.idnumber,5)='20151';
We moved from MySQL to PostgreSQL when we updated to 2.7, but the only changes I needed to make to our queries were for date handling.
It's also worth mentioning that the assignment module was completely overhauled for version 2.3, and a lot of the docs for 2.3, 2.4, 2.5, etc were just copied over from the prior version. I've seen other changes be missed by this process. This especially holds for something like a contributed report. It's possible you're still seeing sql that hasn't been valid since 2.3.

Related

Calculate number of distinct instances of value in column

long time lurker. I've searched and searched though none of the solutions work for me.
I'm working in a Sybase (ASE) db (most mssql/mysql transactional db solutions will work just fine)
In my example, I'm trying to calculate/count the number of times a specific 'party_id' is listed in a column. The problem I'm having is that it's only counting FOR each row- so of course the count is always going to be 1.
See output:
(I would like for party_id 130568 to show '2' in the refs column, 125555 to show '5', etc.)
output
Here is my query:
select
count(distinct p.party_id) as refs,
p.party_id,
sp_first_party(casenum),
c.casenum,
mld.mailing_list,
p.our_client
from cases c
inner join party p on c.casenum=p.case_id
inner join names n on n.names_id=p.party_id
inner join mailing_list_defined mld on n.names_id=mld.names_id
where
mld.mailing_list like 'Mattar Stars'
and mld.addr_type like 'Home'
and n.deceased='N'
and p.our_client='Y'
group by p.party_id, c.casenum, mld.mailing_list, p.our_client
order by sp_first_party(casenum) asc
Any tips would be greatly appreciated.
Thank you
Sounds like you need to be using an APPLY statement. Not sure if the join criteria on the APPLY statement is correct, but you should be able to extrapolate the logic. See if that will work with Sybase.
SELECT pic.PartyInstanceCount AS refs
,p.party_id
,sp_first_party(casenum)
,c.casenum
,mld.mailing_list
,p.our_client
FROM cases AS c
INNER JOIN party AS p ON c.casenum = p.case_id
INNER JOIN names AS n ON n.names_id = p.party_id
INNER JOIN mailing_list_defined AS mld ON n.names_id = mld.names_id
OUTER APPLY (
SELECT COUNT(1) AS PartyInstanceCount
FROM party p2
WHERE p2.case_id = c.casenum
) pic
WHERE mld.mailing_list LIKE 'Mattar Stars'
AND mld.addr_type LIKE 'Home'
AND n.deceased = 'N'
AND p.our_client = 'Y'
ORDER BY
sp_first_party(casenum) ASC

Sum up multiple values based off a single column

For context, I work in transportation. Also, I apologize for a poor title - I'm not exactly sure how to summarize my issue.
I am currently editing an existing report which returns a drivers ID, their name, when they were hired, and the total amount of miles they have driven since they have started at the company. It was brought to my attention that drivers who move within the company are assigned a different driverID, which is not counted towards their total miles driven. Using an example provided to me, I was indeed able to confirm this scenario, as indicated below:
DriverCode DriverName
----------- ----------------
WETDE Wethington,Dean
WETDEA Wethington,Dean
This is the query that gets the above (example driver is hardcoded at the moment):
select mpp.mpp_id as DriverCode,
mpp.mpp_lastfirst as DriverName
from manpowerprofile mpp
outer apply (select top 1 mpp_id
from manpowerprofile) as id
where mpp_firstname = 'Dean'
and mpp_lastname = 'Wethington'
This is the current query as it stands:
SELECT lh.lgh_driver1 as DriverCode
,m.mpp_lastfirst as DriverName
,m.mpp_hiredate as HireDate
,SUM(s.stp_lgh_mileage) as TotMiles
FROM stops s (nolock)
INNER JOIN legheader lh (nolock) on lh.lgh_number = s.lgh_number
INNER JOIN manpowerprofile m (nolock) on m.mpp_id = lh.lgh_driver1
/* OUTER APPLY ( SELECT top 1 mpp_id
FROM manpowerprofile) as id */
WHERE m.mpp_terminationdt > GETDATE()
AND m.mpp_id <> 'UNKNOWN'
AND lh.lgh_outstatus = 'CMP'
GROUP BY lh.lgh_driver1, m.mpp_lastfirst, m.mpp_hiredate
HAVING SUM(s.stp_lgh_mileage) > 850000
ORDER BY DriverCode DESC
What I'm looking to do is check to see if a name exists twice, and if it does, add both of those driver code's total miles together to return a single result for that individual driver. I'm a pretty novice SQL Developer still and have only now really started to delve into databases.
My current train of thought was to use an outer apply, but I'm sure there's a better way to do this.
As per your comment, leaving off the driver code and hire date...
(Because they could/would be different for the drivers being combined.)
SELECT
m.mpp_lastfirst as DriverName
,SUM(s.stp_lgh_mileage) as TotMiles
FROM
stops s (nolock)
INNER JOIN
legheader lh (nolock)
on lh.lgh_number = s.lgh_number
INNER JOIN
manpowerprofile m (nolock)
on m.mpp_id = lh.lgh_driver1
WHERE
m.mpp_terminationdt > GETDATE()
AND m.mpp_id <> 'UNKNOWN'
AND lh.lgh_outstatus = 'CMP'
GROUP BY
m.mpp_lastfirst
HAVING
SUM(s.stp_lgh_mileage) > 850000
ORDER BY
m.mpp_lastfirstDESC

SQL query to find whoever has a skill what other skill do they have?

enter image description here
I have two tables :
UserInfo
Skill
and the join table between them called UserSkill as you can see at the
right part of the diagram.
I want to know whoever knows or is skillful in Java, what else he is skillful at. I mean for example I know java, Go, PHP, python and user number 2 knows java and python and CSS. So the answer to the question: whoever knows java what else he knows would be GO, PHP, Python and CSS.
It's like recommendation systems for example whoever but this product what else do they bought? Like what we have in amazon ..
What would be the best query for this ?
Thank you
More information:
UserInfo
U-id U-name
1 A
2 B
3 C
SkillInfo
S-id S-Name
1 Java
2 GO
3 PHP
4 Python
5 CSS
UserSkill:
U-id S-id
1 1
1 2
1 3
1 4
2 1
2 4
2 5
In SQL Server 2017 and Azure SQL DB you can use the new graph database capabilities and the new MATCH clause to answer queries like this, eg
SELECT FORMATMESSAGE ( 'User %s has skill %s and other skill %s.', [user1].[U-name], mainSkill.[S-name], otherSkill.[S-name] ) result
FROM
dbo.users user1,
dbo.hasSkill hasSkill1, dbo.skills mainSkill, dbo.hasSkill hasSkill2, dbo.skills otherSkill
WHERE mainSkill.[S-name] = 'CSS'
AND otherSkill.[S-name] != mainSkill.[S-name]
AND MATCH ( mainSkill<-(hasSkill1)-user1-(hasSkill2)->otherSkill);
My results:
Obviously you can answer the same queries with a relational approach, it's just a different way of doing things. Full script available here.
To make this more dynamic, replace the hard coded 'java' with a variable that you can pass to filter by any skill type, possibly make a stored procedure so you can pass the variable,
Edited column names as I didn't look at the image you provided:
--Outer query selects all skills of users which are not java and user has skill of java,
--the inner query selects all user ids where the user has a skill of java
SELECT sk.[SkillName], ui.[UserName], ui.[UserId]
FROM [dbo].[Skill] AS sk
INNER JOIN [dbo].[UserSkill] AS us
ON us.[SkillId] = sk.[SkillId]
INNER JOIN [dbo].[UserInfo] AS ui
ON ui.[UserId] = us.[UserId]
WHERE sk.[Skill] <> 'java' AND ui.[UserId] IN (
SELECT [UserId]
FROM [dbo].[UserInfo] ui
INNER JOIN [dbo].[UserSkill] us
ON us.[UserId] = ui.[UserId]
INNER JOIN [dbo].[Skill] sk
ON sk.[SkillId] = us.[SkillId]
WHERE sk.[SkillName] = 'java')
This is what I have found
--Formatted query
select
o.UserName, k.SkillName
from
UserSkill S
inner join
UserSkill SS on s.UserID = ss.UserID
and s.SkillID = 1
and ss.SkillID <> 1
inner join
Skill k on k.SkillID = ss.SkillID
inner join
UsersINFO O on o.UserID = ss.UserID

Schema Help; Updating through joins; SQL

below is some code that I have typed as well as some notes pertaining to said code, I tried to provide a thorough description of the three tables involved.
This is the select/join I used to ‘link’ the three tables that will be involved in completing this process.
select * from SMI.dbo.API api
left outer join FM.dbo.MP mpi on api.PEIDS = mpi.PEID
inner join SMI.dbo.SIP sip on mpi.FIDID = sip.FIDID
API- Has a PEID, MaidenName, DOB, and Place of Birth
MP or MPI- Has a PEID, FIDID, FI, MI, and Last names for a Bride & Groom.
SIP- Has a FIDID, this is the destination table
Within the API, each record has an individual’s PEID, as well as their maiden name (If they’re a female) their DOB, and their Place of Birth. Also included within this table is a ‘Gender’ Field.. (I.E. M, F, U)
Within the MPI field, each record has the same PEID for each individual; however it also houses a FIDID that is tied to each Record. Therefore in this table, you will find that the number of total records is nearly double the overall totals of the other two tables.. This is because you are receiving a record for both the Bride and the Groom.
Within the SIP you will find the majority of the index information pertaining to each Marriage, most importantly the FIDID.
Scope: The ultimate goal for this query is to insert the MaidenName, BirthDates, and BirthPlaces, into the SIP table, all the while differentiating between M & F.
With this being said, since there is no direct link between the destination table (SIP), and the source table (API) I felt that joining API and
(MPI) would be the best method of ‘bridging’ this gap.
Since I only have a PEID to go off of, I will need to look at the ‘Gender’ field as well to determine whether or not the particular record is pertaining to the bride or to the groom(M or F), after locating a specific record’s PEID & Gender, the query should then move on to the next step of locating the FIDID from (MPI) via the PEID, and it should then insert the MaidenName, BirthDates, and BirthPlaces into SIP accordingly.
I realize the syntax on the code below is completely wonky. I mainly typed it out while attempting to visualize what I needed done.
USE [SMI]
GO
SELECT * from SMI.dbo.API api
left outer join FM.dbo.MPI mpi on api.PEIDS = mpi.PEID
inner join SMI.dbo.SIP sip on mpi.FIDID = sip.FIDID
CASE
((( WHEN api.PEIDs = mpi.PEID and mpi.FIDID = sip.FIDID
(THEN
Update SIP
set groomdob = api.birthdate, GroomBirthPlace = api.birthplace
where api.gender like 'M')
and
((Update SIP
set bridedob = api.birthdate, BrideBirthPlace = api.birthplace, BrideMaidenName = api.birthlastname
where api.gender like 'F' ))
ELSE null)))
END
FROM SMI.dbo.API api
ORDER BY PEIDs ;
GO
Thanks in advance for any assistance y'all have to offer.
UPDATE SMI.dbo.SIP s
inner join (select * from SMI.dbo.API api
left outer join FM.dbo.MP mpi on api.PEIDS = mpi.PEID
inner join SMI.dbo.SIP sip on mpi.FIDID = sip.FIDID WHERE api.gender like 'm') m
on s.FID = m.FID
SET s.groomdob = m.birthdate, s.GroomBirthPlace = m.birthplace
UPDATE SMI.dbo.SIP s
inner join (select * from SMI.dbo.API api
left outer join FM.dbo.MP mpi on api.PEIDS = mpi.PEID
inner join SMI.dbo.SIP sip on mpi.FIDID = sip.FIDID WHERE api.gender like 'f') as f
on f.FID = s.FID
set s.bridedob = f.birthdate, s.BrideBirthPlace = f.birthplace,
s.BrideMaidenName =f.birthlastname

How to get Django QuerySet 'exclude' to work right?

I have a database that contains schemas for skus, kits, kit_contents, and checklists. Here is a query for "Give me all the SKUs defined for kitcontent records defined for kit records defined in checklist 1":
SELECT DISTINCT s.* FROM skus s
JOIN kit_contents kc ON kc.sku_id = s.id
JOIN kits k ON k.id = kc.kit_id
JOIN checklists c ON k.checklist_id = 1;
I'm using Django, and I mostly really like the ORM because I can express that query by:
skus = SKU.objects.filter(kitcontent__kit__checklist_id=1).distinct()
which is such a slick way to navigate all those foreign keys. Django's ORM produces basically the same as the SQL written above. The trouble is that it's not clear to me how to get all the SKUs not defined for checklist 1. In the SQL query above, I'd do this by replacing the "=" with "!=". But Django's models don't have a not equals operator. You're supposed to use the exclude() method, which one might guess would look like this:
skus = SKU.objects.filter().exclude(kitcontent__kit__checklist_id=1).distinct()
but Django produces this query, which isn't the same thing:
SELECT distinct s.* FROM skus s
WHERE NOT ((skus.id IN
(SELECT kc.sku_id FROM kit_contents kc
INNER JOIN kits k ON (kc.kit_id = k.id)
WHERE (k.checklist_id = 1 AND kc.sku_id IS NOT NULL))
AND skus.id IS NOT NULL))
(I've cleaned up the query for easier reading and comparison.)
I'm a beginner to the Django ORM, and I'd like to use it when possible. Is there a way to get what I want here?
EDIT:
karthikr gave an answer that doesn't work for the same reason the original ORM .exclude() solution doesn't work: a SKU can be in kit_contents in kits that exist on both checklist_id=1 and checklist_id=2. Using the by-hand query I opened my post with, using "checklist_id = 1" produces 34 results, using "checklist_id = 2" produces 53 results, and the following query produces 26 results:
SELECT DISTINCT s.* FROM skus s
JOIN kit_contents kc ON kc.sku_id = s.id
JOIN kits k ON k.id = kc.kit_id
JOIN checklists c ON k.checklist_id = 1
JOIN kit_contents kc2 ON kc2.sku_id = s.id
JOIN kits k2 ON k2.id = kc2.kit_id
JOIN checklists c2 ON k2.checklist_id = 2;
I think this is one reason why people don't seem to find the .exclude() solution a reasonable replacement for some kind of not_equals filter -- the latter allows you to say, succinctly, exactly what you mean. Presumably the former could also allow the query to be expressed, but I increasingly despair of such a solution being simple.
You could do this - get all the objects for checklist 1, and exclude it from the complete list.
sku_ids = skus.values_list('pk', flat=True)
non_checklist_1 = SKU.objects.exclude(pk__in=sku_ids).distinct()