Moodle, mysql query users enrolment status by course - mysql

this is a query in moodle to get alumn status in courses.
A 'X' indicates user is enrolled, empty not enrolled.
We have to add a subquery for every course we have...
Could anyone help to do a better query?
SELECT
user1.firstname AS Firstname,
user1.lastname AS Lastname,
user1.email AS Email,
if (exists( SELECT
ue.id
FROM mdl_user_enrolments AS ue
JOIN mdl_enrol AS en ON en.id = ue.enrolid
JOIN mdl_course AS course ON course.id = en.courseid
WHERE user1.id = ue.userid AND course.id = '1'), 'X', '') as "Enrolled Course One"
FROM mdl_user AS user1
LEFT JOIN mdl_user_enrolments AS ue ON ue.userid = user1.id
LEFT JOIN mdl_enrol AS en ON en.id = ue.enrolid
LEFT JOIN mdl_course AS course ON course.id = en.courseid
[...]
WHERE user1.deleted='0'

You could try something like this but it will take a long time to process if you have a lot of users and courses
SELECT CONCAT(u.id, '_', c.id) AS uniqueid,
u.id AS userid,
u.firstname,
u.lastname,
u.email,
MAX(CASE WHEN ue.id IS NULL THEN '' ELSE 'X' END) AS enrolled,
c.id AS courseid,
c.fullname AS coursename
FROM mdl_user u
CROSS JOIN mdl_course c
LEFT JOIN mdl_enrol e ON e.courseid = c.id
LEFT JOIN mdl_user_enrolments ue ON u.id = ue.userid AND ue.enrolid = e.id
WHERE u.deleted = 0
GROUP BY u.id, c.id

Related

MySQL how to get the max through inner join

For a swimming school, parents pay tuition fees for their kids. On a monthly basis, I need to collect the payable fees from a table that holds open balances, but only if their child is marked as 'active'. Here is the catch: an open balance for an "inactive child" is also considered payable IF at least one other child in the same family is marked active.
I have the following MySQL query:
SELECT sum(b.amount) as amount, f.name, u.email
FROM balance b
LEFT JOIN student s ON b.user_id = s.user_id
LEFT JOIN student_family sf ON s.user_id = sf.user_id
LEFT JOIN family f ON sf.fam_id = f.fam_id
LEFT JOIN user u ON u.user_id = s.user_id
WHERE b.status = 'open' AND s.active = 1
GROUP BY u.email
I currently check s.active=1 but this part needs to be adjusted in such as way that we check if there is at least one student in the same family that have active=1.
My attempt (that didn't work):
...
WHERE b.status = 'open' AND ( SELECT max(active) from student s2 LEFT JOIN student_family sf2 ON sf2.user_id = s2.user_id WHERE sf2.fam_id = sf.fam_id ) = 1
How to accomplish this query?
Using the exists clause will resolve the issue:
SELECT sum(b.amount) as amount, f.name, u.email
FROM balance b
LEFT JOIN student s ON b.user_id = s.user_id
LEFT JOIN student_family sf ON s.user_id = sf.user_id
LEFT JOIN family f ON sf.fam_id = f.fam_id
LEFT JOIN user u ON u.user_id = s.user_id
WHERE b.status = 'open' AND exists
(select 1 from student s1
JOIN student_family sf1 ON s1.user_id = sf1.user_id
JOIN family f ON sf1.fam_id = f1.fam_id
where s1.active = 1 and f.fam_id=f1.fam_id)
GROUP BY u.email
You could use a IN clause in your WHERE to see if the student_family.user_id is in a family with at least one active student:
Something like:
SELECT
sum(b.amount) as amount,
f.name,
u.email
FROM
balance b
LEFT JOIN student s ON b.user_id = s.user_id
LEFT JOIN student_family sf ON s.user_id = sf.user_id
LEFT JOIN family f ON sf.fam_id = f.fam_id
LEFT JOIN user u ON u.user_id = s.user_id
WHERE
b.status = 'open'
AND (sf.user_id IN (SELECT sf.user_id FROM student_family sf INNER JOIN student s ON sf.user_id = s.user_id WHERE s.active = 1 GROUP BY sf.user_id ))
GROUP BY
u.email

Modify MySQL statement to allow update

How can this be modified with an update statement? Want to update the field "email" in the "mdl_user" table with a value of "users#domain.com" with all records that are returned by this statement
SELECT DISTINCT u.id AS userid, c.id AS courseid
FROM mdl_user u
JOIN mdl_user_enrolments ue ON ue.userid = u.id
JOIN mdl_enrol e ON e.id = ue.enrolid
JOIN mdl_role_assignments ra ON ra.userid = u.id
JOIN mdl_context ct ON ct.id = ra.contextid
AND ct.contextlevel =50
JOIN mdl_course c ON c.id = ct.instanceid
AND e.courseid = c.id
JOIN mdl_role r ON r.id = ra.roleid
AND r.shortname = 'student'
WHERE e.status =0
AND u.suspended =0
AND u.deleted =0
AND ue.status =0
AND courseid =1538
This will update all the selected users:
UPDATE mdl_user
JOIN (
SELECT DISTINCT u.id AS userid, c.id AS courseid
FROM mdl_user u
JOIN mdl_user_enrolments ue ON ue.userid = u.id
JOIN mdl_enrol e ON e.id = ue.enrolid
JOIN mdl_role_assignments ra ON ra.userid = u.id
JOIN mdl_context ct ON ct.id = ra.contextid
AND ct.contextlevel =50
JOIN mdl_course c ON c.id = ct.instanceid
AND e.courseid = c.id
JOIN mdl_role r ON r.id = ra.roleid
AND r.shortname = 'student'
WHERE e.status =0
AND u.suspended =0
AND u.deleted =0
AND ue.status =0
AND courseid =1538
) AS a
ON a.userid = mdl_user.id
SET email = 'users#domain.com'

Select multipl query and every query got 2 JOIN

I want combine multiple query become 1:
SELECT b.fname FROM almanak as a
JOIN users as b on (b.userid = a.staf1)
SELECT c.fname FROM almanak as a
JOIN users as c on (c.userid = a.staf2)
SELECT d.fname FROM almanak as a
JOIN users as d on (d.userid = a.staf3)
SELECT e.fname FROM almanak as a
JOIN users as e on (e.userid = a.staf4)
SELECT f.fname FROM almanak as a
JOIN users as f on (f.userid = a.staf5)
i want make every field userid will select fname field at another table
Here is one way to do it:
SELECT
b.fname fname1,
c.fname fname2,
d.fname fname3,
e.fname fname4,
f.fname fname5
FROM almanak as a
INNER JOIN users as b ON b.userid = a.staf1
INNER JOIN users as c ON c.userid = a.staf2
INNER JOIN users as d ON d.userid = a.staf3
INNER JOIN users as e ON e.userid = a.staf4
INNER JOIN users as f ON f.userid = a.staf5
If there might be missing relations, you can turn the INNER JOINs to LEFT JOINs.
Alternatively, you can do conditional aggregation. Assuming that the primary key of the almanak table is id, that would look like:
SELECT
MAX(CASE WHEN u.userid = a.staf1 THEN u.fname END) fname1,
MAX(CASE WHEN u.userid = a.staf2 THEN u.fname END) fname2,
MAX(CASE WHEN u.userid = a.staf3 THEN u.fname END) fname3,
MAX(CASE WHEN u.userid = a.staf4 THEN u.fname END) fname4,
MAX(CASE WHEN u.userid = a.staf5 THEN u.fname END) fname5
FROM almanak as a
INNER JOIN users as u
ON u.userid IN (a.staf1, a.staf2, a.staf3, a.staf4, a.staf5)
GROUP BY a.id

MYSQL combining two counts that use same columns

I have two queries that i need to combine into one query. The problem is they are using the same columns in where clause depending on what i need to fetch.
Query 1
SELECT c.fullname, COUNT( DISTINCT sst.id ) AS 'Liczba rozpoczetych szkolen'
FROM mdl_course c
INNER JOIN mdl_scorm s ON s.course = c.id
INNER JOIN mdl_scorm_scoes_track sst ON s.id = sst.scormid
INNER JOIN mdl_user u ON u.id = sst.userid
WHERE sst.element = 'x.start.time' AND u.deleted =0
GROUP BY c.fullname ORDER BY `Liczba rozpoczetych szkolen` ASC
Query 2
SELECT c.fullname, COUNT(DISTINCT sst.userid ) AS 'Liczba_ukonczonych_szkolen'
FROM mdl_course c
INNER JOIN mdl_scorm s ON s.course=c.id
INNER JOIN mdl_scorm_scoes_track sst ON s.id = sst.scormid
INNER JOIN mdl_user u ON sst.userid=u.id
where `element`='cmi.core.score.raw' and `value` = 100 and u.deleted = 0
GROUP BY c.fullname ORDER BY `Liczba_ukonczonych_szkolen` DESC
They are depending on the same column named 'element'.
How i can display the result as
fullname Liczba rozpoczetych szkolen Liczba_ukonczonych_szkolen
A1 34 4
A2 5 3
A3 34 33
I've came up with this one, whitch works. Thanks for advice #HoneyBadger
SELECT t1.fullname, t1.Liczba_rozpoczetych_szkolen, t2.Liczba_ukonczonych_szkolen,
round(((t2.Liczba_ukonczonych_szkolen /t1.Liczba_rozpoczetych_szkolen)*100),2) as procentowo
FROM
(SELECT c.fullname, COUNT( DISTINCT sst.id ) AS Liczba_rozpoczetych_szkolen
FROM mdl_course c
INNER JOIN mdl_scorm s ON s.course = c.id
INNER JOIN mdl_scorm_scoes_track sst ON s.id = sst.scormid
INNER JOIN mdl_user u ON u.id = sst.userid
WHERE sst.element = 'x.start.time' AND u.deleted =0
GROUP BY c.fullname ) AS t1,
(SELECT c.fullname, COUNT(DISTINCT sst.userid ) AS Liczba_ukonczonych_szkolen
FROM mdl_course c
INNER JOIN mdl_scorm s ON s.course=c.id
INNER JOIN mdl_scorm_scoes_track sst ON s.id = sst.scormid
left join mdl_user u ON sst.userid=u.id
where `element`='cmi.core.score.raw' and `value` = 100 and u.deleted = 0
GROUP BY c.fullname) as t2
WHERE t1.fullname = t2.fullname
You can join them together like this:
SELECT COALESCE(R.fullname, U.fullname) AS fullname
, COALESCE(R.Liczba_rozpoczetych_szkolen, 0) AS Liczba_rozpoczetych_szkolen
, COALESCE(R.Liczba_ukonczonych_szkolen, 0) AS Liczba_ukonczonych_szkolen
FROM (
SELECT c.fullname
, COUNT( DISTINCT sst.id ) AS Liczba_rozpoczetych_szkolen
FROM mdl_course c
INNER JOIN mdl_scorm s
ON s.course = c.id
INNER JOIN mdl_scorm_scoes_track sst
ON s.id = sst.scormid
INNER JOIN mdl_user u
ON u.id = sst.userid
WHERE sst.element = 'x.start.time'
AND u.deleted =0
GROUP BY c.fullname
) AS R
FULL JOIN (
SELECT c.fullname
, COUNT(DISTINCT sst.userid ) AS Liczba_ukonczonych_szkolen
FROM mdl_course c
INNER JOIN mdl_scorm s
ON s.course=c.id
INNER JOIN mdl_scorm_scoes_track sst
ON s.id = sst.scormid
INNER JOIN mdl_user u
ON sst.userid=u.id
where `element`='cmi.core.score.raw'
AND `value` = 100
AND u.deleted = 0
GROUP BY c.fullname
) AS U
ON R.fullname = U.fullname
Try the query with the ORed predicates and extra filtering with CASE in the SELECT list. Kind of
SELECT c.fullname, COUNT(DISTINCT CASE WHEN sst.element = 'x.start.time' THEN sst.id END) AS 'Liczba rozpoczetych szkolen'
, COUNT(DISTINCT CASE WHEN sst.element = 'cmi.core.score.raw' and `value` = 100 THEN sst.userid END) AS 'Liczba_ukonczonych_szkolen'
FROM mdl_course c
INNER JOIN mdl_scorm s ON s.course = c.id
INNER JOIN mdl_scorm_scoes_track sst ON s.id = sst.scormid
INNER JOIN mdl_user u ON u.id = sst.userid
WHERE sst.element = IN( 'x.start.time', 'cmi.core.score.raw') AND u.deleted =0
GROUP BY c.fullname ORDER BY `Liczba rozpoczetych szkolen` ASC

Convert SQL DATEFORMAT to Postgres

Could someone please change the following query to work on postgres?
SELECT
user2.id AS ID,
ul.timeaccess,
user2.firstname AS Firstname,
user2.lastname AS Lastname,
user2.email AS Email,
user2.username AS IDNumber,
user2.institution AS Institution,
IF (user2.lastaccess = 0,'never',
DATE_FORMAT(FROM_UNIXTIME(user2.lastaccess),'%Y-%m-%d')) AS dLastAccess
,(SELECT DATE_FORMAT(FROM_UNIXTIME(timeaccess),'%Y-%m-%d') FROM mdl_user_lastaccess WHERE userid=user2.id AND courseid=c.id) AS CourseLastAccess
,(SELECT r.name
FROM mdl_user_enrolments AS uenrol
JOIN mdl_enrol AS e ON e.id = uenrol.enrolid
JOIN mdl_role AS r ON e.id = r.id
WHERE uenrol.userid=user2.id AND e.courseid = c.id) AS RoleName
FROM mdl_user_enrolments AS ue
JOIN mdl_enrol AS e ON e.id = ue.enrolid
JOIN mdl_course AS c ON c.id = e.courseid
JOIN mdl_user AS user2 ON user2 .id = ue.userid
LEFT JOIN mdl_user_lastaccess AS ul ON ul.userid = user2.id
WHERE c.id=14 AND ue.userid NOT IN (SELECT qa.userid FROM mdl_quiz_attempts AS qa
JOIN mdl_quiz AS q ON qa.quiz = q.id
JOIN mdl_course AS c ON q.course = c.id
WHERE c.id = 14 AND q.name LIKE '%quiz name goes here%')
It is for Moodle
i understand that it has to go to_char etc but don't know how to change it
Thanks in advance
SELECT
user2.id AS "ID",
ul.timeaccess,
user2.firstname AS "Firstname",
user2.lastname AS "Lastname",
user2.email AS "Email",
user2.username AS "IDNumber",
user2.institution AS "Institution",
(CASE WHEN user2.lastaccess = 0 THEN'never' END ||
to_char(to_timestamp(user2.lastaccess)::date,'YYYY-MM-DD')) AS "dLastAccess"
,(SELECT to_char(to_timestamp(timeaccess)::date,'YYYY-MM-DD') FROM mdl_user_lastaccess WHERE userid=user2.id AND courseid=c.id) AS "CourseLastAccess"
,(SELECT r.name
FROM mdl_user_enrolments AS uenrol
JOIN mdl_enrol AS e ON e.id = uenrol.enrolid
JOIN mdl_role AS r ON e.id = r.id
WHERE uenrol.userid=user2.id AND e.courseid = c.id) AS "RoleName"
FROM mdl_user_enrolments AS ue
JOIN mdl_enrol AS e ON e.id = ue.enrolid
JOIN mdl_course AS c ON c.id = e.courseid
JOIN mdl_user AS user2 ON user2 .id = ue.userid
LEFT JOIN mdl_user_lastaccess AS ul ON ul.userid = user2.id
WHERE c.id=14 AND ue.userid NOT IN (SELECT qa.userid FROM mdl_quiz_attempts AS qa
JOIN mdl_quiz AS q ON qa.quiz = q.id
JOIN mdl_course AS c ON q.course = c.id
WHERE c.id = 14 AND q.name LIKE '%quiz name goes here%')
Hey I tried.
FROM_UNIXTIME -is equivalent to to_timestamp on postgres.
IF ELSE are CASE WHEN in postgres.
to_char - if you want to format the date use this.
Have a look at: Data Type Formatting Functions on Postgres docs.
Use to_char(date, 'YYYY-MM-DD')
For example:
select to_char('20170501'::date, 'YYYY-MM-DD')
Returns:
2017-05-01
Check it here: http://rextester.com/JDZA16474