dmlreadexception (sql syntax problem)

dmlreadexception (sql syntax problem)

by Jason Hollowell -
Number of replies: 9
Picture of Particularly helpful Moodlers

Hello,

Sorry, I searched the forums directly and via Google and found some distantly related posts but none that directly helped me to fix my problem.

I'm writing a new function for a customized version of the face to face module and am oh so close to having it working but sad I'm getting an SQL error that I can't figure out.

Here is my sql query from the function:

$count = $DB->count_records_sql
('SELECT COUNT(su.userid)
FROM
{facetoface_signups} su
JOIN
{facetoface_signups_status} ss
ON
su.id = ss.signupid
JOIN
{facetoface_sessions} fs
ON
su.sessionid = fs.id
JOIN
{facetoface_sessions_dates} sd
ON
fs.id = sd.sessionid
WHERE
ss.superceded = ?
AND
su.userid = ?
AND
fs.facetoface = ?
AND
ss.statuscode >= 60
AND
FROM_UNIXTIME(sd.timestart), "%%d%%B%%Y" = ?', array(0, $userid, $facetofaceid, $thisdate));

$thisdate is defined at the beginning of the function and is correct as shown in my debug info (pasted below). The two dates (26 March 2014 in this case) are being compared...or so it would seem (one from the variable and the other from the query) but for some reason I'm getting an error.....I have this terrible feeling I'm missing something elementary but I can't figure it out. sad

Here is the debug and stack trace info:

Debug info: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ' "%%d%%B%%Y" = '26 March 2014'' at line 25
SELECT COUNT(su.userid)
FROM
mdl_facetoface_signups su
JOIN
mdl_facetoface_signups_status ss
ON
su.id = ss.signupid
JOIN
mdl_facetoface_sessions fs
ON
su.sessionid = fs.id
JOIN
mdl_facetoface_sessions_dates sd
ON
fs.id = sd.sessionid
WHERE 
ss.superceded = ?
AND
su.userid = ?
AND
fs.facetoface = ?
AND
ss.statuscode >= 60
AND
FROM_UNIXTIME(sd.timestart), "%%d%%B%%Y" = ?
[array (
0 => 0,
1 => '3',
2 => '1',
3 => '26 March 2014', <-----That's the date from the database.....which is the same as the one above from the variable....sad
)]
Error code: dmlreadexception
Stack trace:
    • line 441 of /lib/dml/moodle_database.php: dml_read_exception thrown
    • line 996 of /lib/dml/mysqli_native_moodle_database.php: call to moodle_database->query_end()
    • line 1415 of /lib/dml/moodle_database.php: call to mysqli_native_moodle_database->get_records_sql()
    • line 1488 of /lib/dml/moodle_database.php: call to moodle_database->get_record_sql()
    • line 1659 of /lib/dml/moodle_database.php: call to moodle_database->get_field_sql()
    • line 3097 of /mod/facetoface/lib.php: call to moodle_database->count_records_sql()
    • line 157 of /mod/facetoface/renderer.php: call to facetoface_dailycount() //this is my function
    • line 193 of /mod/facetoface/view.php: call to mod_facetoface_renderer->print_session_list_table()
    • line 102 of /mod/facetoface/view.php: call to facetoface_print_session_list()

Any advice and/or help will be greatly appreciated.

Jason

Average of ratings: -
In reply to Jason Hollowell

Re: dmlreadexception (sql syntax problem)

by Davo Smith -
Picture of Core developers Picture of Particularly helpful Moodlers Picture of Peer reviewers Picture of Plugin developers

Should that be:

AND
FROM_UNIXTIME(sd.timestart, "%%d%%B%%Y") = ?',

Instead of

AND
FROM_UNIXTIME(sd.timestart), "%%d%%B%%Y" = ?',

 

In reply to Davo Smith

Re: dmlreadexception (sql syntax problem)

by Jason Hollowell -
Picture of Particularly helpful Moodlers

Davo,

Thanks but actually I tried that and get the exact same error. sad I'm lost.

Jason

In reply to Jason Hollowell

Re: dmlreadexception (sql syntax problem)

by Darko Miletić -

It would seem that you enjoy pain and suffering. You took the more complex route to something that should not be too difficult. 

timestart field is integer inteded to hold UNIX timestamp. So why not give that to the query instead of this ackward conversion? it would be even faster for the database itself. I assume you want all items that are made on some specific date regardless of the hour. For that try this:

 

$datestart = new DateTime();
$datestart->setDate(2014, 01, 01);
$dateend = new DateTime();
$datestart->setDate(2014, 01, 02);

$count = $DB->count_records_sql
('SELECT COUNT(su.userid)
FROM {facetoface_signups} su
INNER JOIN {facetoface_signups_status} ss ON su.id = ss.signupid
INNER JOIN {facetoface_sessions} fs ON su.sessionid = fs.id
INNER JOIN {facetoface_sessions_dates} sd ON fs.id = sd.sessionid
WHERE 
ss.superceded = ?
AND
su.userid = ?
AND
fs.facetoface = ?
AND
ss.statuscode >= 60
AND
sd.timestart >= ? 
AND 
sd.timestart < ?'), array(0, $userid, $facetofaceid, $datestart->getTimestamp(), $dateend->getTimestamp()));

In reply to Darko Miletić

Re: dmlreadexception (sql syntax problem)

by Jason Hollowell -
Picture of Particularly helpful Moodlers

Darko,

Thanks. "Pain and suffering", yes, that's me. smile That's why I've been known to ride my bicycle for 260 km straight. wink

I don't quite follow the >= and < part of the query there though. I am sending a value (in my case $thisdate) to the query and wanting to see how many instances match that date (yes, just the date not time). The $thisdate value will, of course, thus change each time the renderer loops through to write the session list. I am trying to have this function determine (for each session) whether the user viewing the session list is registered for any sessions that are on the same day as the one being listed by the renderer (thus the other WHERE parts of the query) and return a value based upon the result that I then use in a condition in the renderer. (e.g. if a user is already registered for a session on '26 March 2014' they will receive a message saying "You have already made the maximum number of reservations allowed per day" and not be able to make a reservation for that session.)

So, sorry for my ignorance but what does the following accomplish:

$datestart = new DateTime();
$datestart->setDate(2014, 01, 01);
$dateend = new DateTime();
$datestart->setDate(2014, 01, 02);

 Maybe I need to send the start time and end time from the renderer....instead of the already formatted (%%d%%B%%Y) value...? 

Regards

Jason

In reply to Jason Hollowell

Re: dmlreadexception (sql syntax problem)

by Darko Miletić -

Actually it should be

 
$datestart = new DateTime();
$datestart->setDate(2014, 01, 01);
$dateend = new DateTime();
$dateend->setDate(2014, 01, 02);

And it accomplishes several things. In order to determine if some UNIX timestamp is in specific date regardless of the hour we need to compare it with the two timestamps. First is the start of that day and second one is the end of that day. So if the date in question is january first 2014 we need to make sure that timestamp of entry in database is between 01/01/2014 00:00 and 01/01/2014 23:59 or less than 01/02/2014 00:00. So in order to make that check we need to construct these two unix timestamps which I did with DateTime class.

$datestart = new DateTime(); // Initializaes datetime object
$datestart->setDate(2014, 01, 01); // Sets the date, by default hour is 00:00 so we do not do here anything
$timestart = $datestart->getTimestamp(); // get unix timestamp from datetime

$dateend = new DateTime(); // Initializaes datetime object
$dateend->setDate(2014, 01, 02); // Sets the date, by default hour is 00:00 so we do not do here anything
$timeend = $dateend->getTimestamp(); // get unix timestamp from datetime

// timestart is now pointing to the 01/01/2014 00:00
// timeend is now pointing to the 01/02/2014 00:00
// our time must be greater or equeal to timestart and less than timeend

 

Average of ratings: Useful (1)
In reply to Darko Miletić

Re: dmlreadexception (sql syntax problem)

by Jason Hollowell -
Picture of Particularly helpful Moodlers

Darko,

Sorry, I spent quite a bit of time writing the response I just posted and didn't see your response here until after posting it.

I'm still a little confused about the logic of the solution you have presented. I have a start time and end time that can be obtained from the renderer (or more specifically from each session in the module) so can I use the method you have presented here to strip the time information from those two values and use those for my comparison? My sessions are 40 minutes long which means the date value for time start and time end are going to be the same (e.g. 3/27/2014) so I don't understand how I can find something between that value...?

At any rate, I have managed to get my query functioning and for now the number of users in our system shouldn't be significant enough to be over stressing our database server so I have a little more time to try and get my head around a more efficient method. smile

Thanks for trying to enlighten me and I promise I will continue to work toward figuring it out. smile

Regards

Jason

In reply to Jason Hollowell

Re: dmlreadexception (sql syntax problem)

by Darko Miletić -

You just need to know one date, the date you wish to compare sessions against. Nothing else. 

A function like this would do:

function check_sessions($year, $month, $day) {
    $datestart = new DateTime();
    $datestart->setDate($year, $month, $day);
    $timestart = $datestart->getTimestamp();

    $dateend = clone $datestart; // make a copy
    $dateend = $dateend->add(new DateInterval('P1D')); // add one day
    $timeend = $dateend->getTimestamp();
    
    // rest of the code
}

// somewhere in the code
check_sessions(2014, 01, 01);
In reply to Jason Hollowell

Re: dmlreadexception (sql syntax problem)

by Jason Hollowell -
Picture of Particularly helpful Moodlers

Thanks everyone for your suggestions. I finally managed to get this working the way I want. I had a couple of very small syntax errors in my query. The final properly functioning query and function look like this:

function facetoface_dailycount($userid, $facetofaceid, $thissessiondate){
global $CFG, $DB, $USER;

$thisdate = $thissessiondate;

$count = $DB->count_records_sql
('SELECT COUNT(su.userid)
FROM
{facetoface_signups} su
JOIN
{facetoface_signups_status} ss
ON
su.id = ss.signupid
JOIN
{facetoface_sessions} fs
ON
su.sessionid = fs.id
JOIN
{facetoface_sessions_dates} sd
ON
fs.id = sd.sessionid
WHERE
ss.superceded = ?
AND
su.userid = ?
AND
fs.facetoface = ?
AND
ss.statuscode >= 60
AND
FROM_UNIXTIME(sd.timestart, "%e %M %Y") = ?', array(0, $userid, $facetofaceid, $thisdate));

// Condition below
$perday = $DB->get_field('facetoface', 'perday', array('id' => $facetofaceid));

if ($count >= $perday) {
return true; //this blocks the user from booking any more sessions for that day
} else { //Less than the value in perday

return false; //allow booking for that day
}
}

I had d B Y for the unixtime formatting, which came from Moodle code. I don't know where the B comes from but MySQL didn't like it. smile Changed it to e M Y (d M Y should work too...or rather, I need to figure out if B in Moodle results in single characters for single digit dates or if it returns 01 for the first etc.). Also, I needed a space after each formatting option.) And, double quotes...single and no quotes wasn't working. 

At any rate, this seems to be working now how I want it to (fingers crossed). I need to do some more testing but so far so good. smile

Posting the details here just in case someone is searching for something related to this. 

Regards

Jason

In reply to Jason Hollowell

Re: dmlreadexception (sql syntax problem)

by Darko Miletić -

Just FYI, the way you did that comparison would force database server to convert every record in the table to date and than perform comparison thus making it potentially much slower than it should be. That is the reason why I suggested the timestamps comparison. It uses fixed numerical values and thus should go much faster, 

Average of ratings: Useful (1)