--- loncom/interface/loncoursedata.pm 2004/04/30 20:09:18 1.112.2.2 +++ loncom/interface/loncoursedata.pm 2004/03/07 21:42:19 1.122 @@ -1,6 +1,6 @@ # The LearningOnline Network with CAPA # -# $Id: loncoursedata.pm,v 1.112.2.2 2004/04/30 20:09:18 matthew Exp $ +# $Id: loncoursedata.pm,v 1.122 2004/03/07 21:42:19 matthew Exp $ # # Copyright Michigan State University Board of Trustees # @@ -182,7 +182,7 @@ sub get_sequence_assessment_data { next; } next if (! ref($curRes)); - next if (! $curRes->is_problem());# && !$curRes->randomout); + next if (! $curRes->is_problem() && $curRes->src() !~ /\.survey$/); # Okay, from here on out we only deal with assessments $title = $curRes->title(); $title =~ s/\:/\&\#058;/g; @@ -401,21 +401,13 @@ characters) and a KEY on 'part_id'. =item $student_table -The student_table has two columns. The first is a 'student_id' and the second -is the text description of the 'student' (typically username:domain) (less -than 100 characters). The 'student_id' is automatically generated by MySQL. -The use of the name 'student_id' is loaded, I know, but this ID is used ONLY -internally to the MySQL database and is not the same as the students ID -(stored in the students environment). This table has its PRIMARY KEY on the -'student' (100 characters). - -=item $studentdata_table - -The studentdata_table has four columns: 'student_id' (the unique id of -the student), 'updatetime' (the time the students data was last updated), -'fullupdatetime' (the time the students full data was last updated), -'section', and 'classification'( the students current classification). -This table has its PRIMARY KEY on 'student_id'. +The student_table has 7 columns. The first is a 'student_id' assigned by +MySQL. The second is 'student' which is username:domain. The third through +fifth are 'section', 'status' (enrollment status), and 'classification' +(to be used in the future). The sixth and seventh ('updatetime' and +'fullupdatetime') contain the time of last update and full update of student +data. This table has its PRIMARY KEY on the 'student_id' column and is indexed +on 'student', 'section', and 'status'. =back @@ -521,7 +513,6 @@ my $current_course =''; my $symb_table; my $part_table; my $student_table; -my $studentdata_table; my $performance_table; my $parameters_table; my $fulldump_response_table; @@ -581,7 +572,7 @@ sub init_dbs { restrictions => 'NOT NULL', auto_inc => 'yes', }, { name => 'part', - type => 'VARCHAR(100) BINARY', + type => 'VARCHAR(100)', restrictions => 'NOT NULL'}, ], 'PRIMARY KEY' => ['part (100)'], @@ -596,31 +587,25 @@ sub init_dbs { restrictions => 'NOT NULL', auto_inc => 'yes', }, { name => 'student', - type => 'VARCHAR(100) BINARY', + type => 'VARCHAR(100)', + restrictions => 'NOT NULL UNIQUE'}, + { name => 'section', + type => 'VARCHAR(100)', + restrictions => 'NOT NULL'}, + { name => 'status', + type => 'VARCHAR(15)', restrictions => 'NOT NULL'}, { name => 'classification', - type => 'varchar(100) BINARY', }, - ], - 'PRIMARY KEY' => ['student (100)'], - 'KEY' => [{ columns => ['student_id']},], - }; - # - my $studentdata_table_def = { - id => $studentdata_table, - permanent => 'no', - columns => [{ name => 'student_id', - type => 'MEDIUMINT UNSIGNED', - restrictions => 'NOT NULL UNIQUE',}, + type => 'varchar(100)', }, { name => 'updatetime', type => 'INT UNSIGNED'}, { name => 'fullupdatetime', type => 'INT UNSIGNED'}, - { name => 'section', - type => 'VARCHAR(100) BINARY'}, - { name => 'classification', - type => 'VARCHAR(100) BINARY', }, ], 'PRIMARY KEY' => ['student_id'], + 'KEY' => [{ columns => ['student (100)', + 'section (100)', + 'status (15)',]},], }; # my $performance_table_def = { @@ -636,7 +621,7 @@ sub init_dbs { type => 'MEDIUMINT UNSIGNED', restrictions => 'NOT NULL' }, { name => 'part', - type => 'VARCHAR(100) BINARY', + type => 'VARCHAR(100)', restrictions => 'NOT NULL'}, { name => 'solved', type => 'TINYTEXT' }, @@ -716,7 +701,7 @@ sub init_dbs { { name => 'awarddetail', type => 'TINYTEXT' }, # { name => 'message', -# type => 'CHAR BINARY' }, +# type => 'CHAR' }, { name => 'response_specific', type => 'TINYTEXT' }, { name => 'response_specific_value', @@ -798,13 +783,6 @@ sub init_dbs { return 3; } # - $tableid = &Apache::lonmysql::create_table($studentdata_table_def); - if (! defined($tableid)) { - &Apache::lonnet::logthis("error creating studentdata_table: ". - &Apache::lonmysql::get_error()); - return 4; - } - # $tableid = &Apache::lonmysql::create_table($performance_table_def); if (! defined($tableid)) { &Apache::lonnet::logthis("error creating preformance_table: ". @@ -1041,9 +1019,9 @@ sub get_student_id { $have_read_student_table = 1; } if (! exists($ids_by_student{$student})) { - &Apache::lonmysql::store_row($student_table, - [undef,$student,undef]); + &populate_student_table(); undef(%ids_by_student); + undef(%students_by_id); my @Result = &Apache::lonmysql::get_rows($student_table); foreach (@Result) { $ids_by_student{$_->[1]}=$_->[0]; @@ -1067,6 +1045,39 @@ sub get_student { return undef; # error } +sub populate_student_table { + my ($courseid) = @_; + if (! defined($courseid)) { + $courseid = $ENV{'request.course.id'}; + } + # + &setup_table_names($courseid); + my $dbh = &Apache::lonmysql::get_dbh(); + my $request = 'INSERT IGNORE INTO '.$student_table. + "(student,section,status) VALUES "; + my $classlist = &get_classlist($courseid); + my $student_count=0; + while (my ($student,$data) = each %$classlist) { + my ($section,$status) = ($data->[&CL_SECTION()], + $data->[&CL_STATUS()]); + if ($section eq '' || $section =~ /^\s*$/) { + $section = 'none'; + } + $request .= "('".$student."','".$section."','".$status."'),"; + $student_count++; + } + return if ($student_count == 0); + chop($request); + $dbh->do($request); + if ($dbh->err()) { + &Apache::lonnet::logthis("error ".$dbh->errstr(). + " occured executing \n". + $request); + } + return; +} + + ################################################ ################################################ @@ -1125,8 +1136,7 @@ a description of the error. Once the "fulldump" tables are updated, the tables used for chart and spreadsheet (which hold only the current state of the student on their homework, not historical data) are updated. If all updates have occured -successfully, the studentdata table is updated to reflect the time of the -update. +successfully, $student_table is updated to reflect the time of the update. Notice we do not insert the data and immediately query it. This means it is possible for there to be data returned this first time that is not @@ -1317,9 +1327,14 @@ sub update_full_student_data { ## ## Update the students time...... if ($returnstatus eq 'okay') { - &Apache::lonmysql::replace_row - ($studentdata_table, - [$student_id,$time_of_retrieval,$time_of_retrieval,undef,undef]); + &store_updatetime($student_id,$time_of_retrieval,$time_of_retrieval); + if ($dbh->err) { + if ($returnstatus eq 'okay') { + $returnstatus = 'error updating student time'; + } else { + $returnstatus = 'error updating student time'; + } + } } return $returnstatus; } @@ -1387,13 +1402,31 @@ sub update_student_data { # # Set the students update time if ($Results[0] eq 'okay') { - &Apache::lonmysql::replace_row($studentdata_table, - [$student_id,$time_of_retrieval,undef,undef,undef]); + &store_updatetime($student_id,$time_of_retrieval,$time_of_retrieval); } # return @Results; } +sub store_updatetime { + my ($student_id,$updatetime,$fullupdatetime)=@_; + my $values = ''; + if (defined($updatetime)) { + $values = 'updatetime='.$updatetime.' '; + } + if (defined($fullupdatetime)) { + if ($values ne '') { + $values .= ','; + } + $values .= 'fullupdatetime='.$fullupdatetime.' '; + } + return if ($values eq ''); + my $dbh = &Apache::lonmysql::get_dbh(); + my $request = 'UPDATE '.$student_table.' SET '.$values. + ' WHERE student_id='.$student_id.' LIMIT 1'; + $dbh->do($request); +} + sub store_student_data { my ($sname,$sdom,$courseid,$student_data) = @_; # @@ -1513,21 +1546,20 @@ sub ensure_tables_are_set_up { # # if the tables do not exist, make them my @CurrentTable = &Apache::lonmysql::tables_in_db(); - my ($found_symb,$found_student,$found_part,$found_studentdata, + my ($found_symb,$found_student,$found_part, $found_performance,$found_parameters,$found_fulldump_part, $found_fulldump_response,$found_fulldump_timestamp); foreach (@CurrentTable) { $found_symb = 1 if ($_ eq $symb_table); $found_student = 1 if ($_ eq $student_table); $found_part = 1 if ($_ eq $part_table); - $found_studentdata = 1 if ($_ eq $studentdata_table); $found_performance = 1 if ($_ eq $performance_table); $found_parameters = 1 if ($_ eq $parameters_table); $found_fulldump_part = 1 if ($_ eq $fulldump_part_table); $found_fulldump_response = 1 if ($_ eq $fulldump_response_table); $found_fulldump_timestamp = 1 if ($_ eq $fulldump_timestamp_table); } - if (!$found_symb || !$found_studentdata || + if (!$found_symb || !$found_student || !$found_part || !$found_performance || !$found_parameters || !$found_fulldump_part || !$found_fulldump_response || @@ -1550,7 +1582,7 @@ Input: $sname, $sdom, $courseid Output: $status, $data This routine ensures the data for a given student is up to date. -The $studentdata_table is queried to determine the time of the last update. +The $student_table is queried to determine the time of the last update. If the students data is out of date, &update_student_data() is called. The return values from the call to &update_student_data() are returned. @@ -1572,11 +1604,11 @@ sub ensure_current_data { $Apache::lonnet::perlvar{'lonUsersDir'}); # my $student_id = &get_student_id($sname,$sdom); - my @Result = &Apache::lonmysql::get_rows($studentdata_table, + my @Result = &Apache::lonmysql::get_rows($student_table, "student_id ='$student_id'"); my $data = undef; if (@Result) { - $updatetime = $Result[0]->[1]; + $updatetime = $Result[0]->[5]; # Ack! This is dumb! } if ($modifiedtime > $updatetime) { ($status,$data) = &update_student_data($sname,$sdom,$courseid); @@ -1597,7 +1629,7 @@ Output: $status This routine ensures the fulldata (the data from a lonnet::dump, not a lonnet::currentdump) for a given student is up to date. -The $studentdata_table is queried to determine the time of the last update. +The $student_table is queried to determine the time of the last update. If the students fulldata is out of date, &update_full_student_data() is called. @@ -1620,11 +1652,11 @@ sub ensure_current_full_data { $Apache::lonnet::perlvar{'lonUsersDir'}); # my $student_id = &get_student_id($sname,$sdom); - my @Result = &Apache::lonmysql::get_rows($studentdata_table, + my @Result = &Apache::lonmysql::get_rows($student_table, "student_id ='$student_id'"); my $updatetime; if (@Result && ref($Result[0]) eq 'ARRAY') { - $updatetime = $Result[0]->[2]; + $updatetime = $Result[0]->[6]; } if (! defined($updatetime) || $modifiedtime > $updatetime) { $status = &update_full_student_data($sname,$sdom,$courseid); @@ -1835,7 +1867,7 @@ populated and all local caching variable properly. This means you need to call &ensure_current_data for the students you are concerned with prior to calling this routine. -Inputs: $students, $symb, $part, $courseid +Inputs: $students, $symb, $part, $courseid, $starttime, $endtime =over 4 @@ -1848,6 +1880,9 @@ Each hash must contain at least the 'use =item $courseid is the course id, of course! +=item $starttime and $endtime are unix times which to use to limit +the statistical data. + =back Outputs: See the code for up to date information. A hash reference is @@ -1883,7 +1918,7 @@ able to answer it correctly. ################################################ ################################################ sub get_problem_statistics { - my ($students,$symb,$part,$courseid) = @_; + my ($Sections,$status,$symb,$part,$courseid,$starttime,$endtime) = @_; return if (! defined($symb) || ! defined($part)); $courseid = $ENV{'request.course.id'} if (! defined($courseid)); # @@ -1897,18 +1932,36 @@ sub get_problem_statistics { # $dbh->do('DROP TABLE '.$stats_table); # May return an error my $request = - 'CREATE TEMPORARY TABLE '.$stats_table. - ' SELECT student_id,solved,award,awarded,tries FROM '.$performance_table. - ' WHERE symb_id='.$symb_id.' AND part_id='.$part_id; - if (defined($students)) { + 'CREATE TEMPORARY TABLE '.$stats_table.' '. + 'SELECT a.student_id,a.solved,a.award,a.awarded,a.tries '. + 'FROM '.$performance_table.' AS a '; + if ((defined($Sections) && lc($Sections->[0]) ne 'all') || + (defined($status) && lc($status) ne 'any')) { + $request .= 'NATURAL LEFT JOIN '.$student_table.' AS b '; + } + $request .= ' WHERE a.symb_id='.$symb_id.' AND a.part_id='.$part_id; + if (defined($Sections) && lc($Sections->[0]) ne 'all') { $request .= ' AND ('. - join(' OR ', map {'student_id='. - &get_student_id($_->{'username'}, - $_->{'domain'}) - } @$students + join(' OR ', map { "b.section='".$_."'" } @$Sections ).')'; } -# &Apache::lonnet::logthis($request); + if (defined($status) && lc($status) ne 'any') { + $request .= " AND b.status='".$status."'"; + } + # + &Apache::lonnet::logthis('starttime = '.$starttime); + my $time_requirements = undef; + if (defined($starttime)) { + $time_requirements .= 'a.timestamp>='.$starttime; + if (defined($endtime)) { + $time_requirements .= ' AND a.timestamp<='.$endtime; + } + } elsif (defined($endtime)) { + $time_requirements .= 'a.timestamp<='.$endtime; + } + if (defined($time_requirements)) { + $request .= ' AND '.$time_requirements; + } $dbh->do($request); # &Apache::lonnet::logthis('request = '.$/.$request); $request = 'SELECT COUNT(*),SUM(tries),MAX(tries),AVG(tries),STD(tries) '. @@ -2079,6 +2132,63 @@ sub get_response_data { } } + +sub RDs_awarddetail { return 3; } +sub RDs_submission { return 2; } +sub RDs_timestamp { return 1; } +sub RDs_tries { return 0; } +sub RDs_awarded { return 4; } + +sub get_response_data_by_student { + my ($student,$symb,$response,$courseid) = @_; + return undef if (! defined($symb) || + ! defined($response)); + $courseid = $ENV{'request.course.id'} if (! defined($courseid)); + # + &setup_table_names($courseid); + my $symb_id = &get_symb_id($symb); + my $response_id = &get_part_id($response); + # + my $student_id = &get_student_id($student->{'username'}, + $student->{'domain'}); + # + my $dbh = &Apache::lonmysql::get_dbh(); + return undef if (! defined($dbh)); + my $request = 'SELECT '. + 'c.tries, b.timestamp, a.submission, a.awarddetail, e.awarded '. + 'FROM '.$fulldump_response_table.' AS a '. + 'LEFT JOIN '.$fulldump_timestamp_table.' AS b '. + 'ON a.symb_id=b.symb_id AND a.student_id=b.student_id AND '. + 'a.transaction = b.transaction '. + 'LEFT JOIN '.$fulldump_part_table.' AS c '. + 'ON a.symb_id=c.symb_id AND a.student_id=c.student_id AND '. + 'a.part_id=c.part_id AND a.transaction = c.transaction '. + 'LEFT JOIN '.$student_table.' AS d '. + 'ON a.student_id=d.student_id '. + 'LEFT JOIN '.$performance_table.' AS e '. + 'ON a.symb_id=e.symb_id AND a.part_id=e.part_id AND '. + 'a.student_id=e.student_id AND c.tries=e.tries '. + 'WHERE '. + 'a.symb_id='.$symb_id.' AND a.response_id='.$response_id. + ' AND a.student_id='.$student_id.' ORDER BY b.timestamp'; + # &Apache::lonnet::logthis("request =\n".$request); + my $sth = $dbh->prepare($request); + $sth->execute(); + if ($dbh->err) { + &Apache::lonnet::logthis('error = '.$dbh->errstr()); + return undef; + } + my $dataset = $sth->fetchall_arrayref(); + if (ref($dataset) eq 'ARRAY' && scalar(@$dataset)>0) { + # Clear the \'s from around the submission + for (my $i =0;$i[$i]->[2] =~ s/(\'$|^\')//g; + } + return $dataset; + } + return undef; # error occurred +} + sub RT_student_id { return 0; } sub RT_awarded { return 1; } sub RT_tries { return 2; } @@ -2132,6 +2242,101 @@ sub get_response_time_data { =pod +=item &get_student_scores($Sections,$Symbs,$enrollment,$courseid) + +=cut + +################################################ +################################################ +sub get_student_scores { + my ($Sections,$Symbs,$enrollment,$courseid,$starttime,$endtime) = @_; + $courseid = $ENV{'request.course.id'} if (! defined($courseid)); + &setup_table_names($courseid); + my $dbh = &Apache::lonmysql::get_dbh(); + return (undef) if (! defined($dbh)); + my $tmptable = $courseid.'_temp_'.time; + # + my $symb_requirements; + if (defined($Symbs) && @$Symbs) { + $symb_requirements = '('. + join(' OR ', map{ "(a.symb_id='".&get_symb_id($_->{'symb'}). + "' AND a.part_id='".&get_part_id($_->{'part'}). + "')" + } @$Symbs).')'; + } + # + my $student_requirements; + if ( (defined($Sections) && $Sections->[0] ne 'all')) { + $student_requirements = '('. + join(' OR ', map { "b.section='".$_."'" } @$Sections + ).')'; + } + # + my $enrollment_requirements=undef; + if (defined($enrollment) && $enrollment ne 'Any') { + $enrollment_requirements = "b.status='".$enrollment."'"; + } + # + my $time_requirements = undef; + if (defined($starttime)) { + $time_requirements .= "a.timestamp>='".$starttime."'"; + if (defined($endtime)) { + $time_requirements .= " AND a.timestamp<='".$endtime."'"; + } + } elsif (defined($endtime)) { + $time_requirements .= "a.timestamp<='".$endtime."'"; + } + ## + ## + my $request = 'CREATE TEMPORARY TABLE IF NOT EXISTS '.$tmptable. + ' SELECT a.student_id,SUM(a.awarded) AS score FROM '. + $performance_table.' AS a '; + if (defined($student_requirements) || defined($enrollment_requirements)) { + $request .= ' NATURAL LEFT JOIN '.$student_table.' AS b '; + } + if (defined($symb_requirements) || + defined($student_requirements) || + defined($enrollment_requirements) ) { + $request .= ' WHERE '; + } + if (defined($symb_requirements)) { + $request .= $symb_requirements.' AND '; + } + if (defined($student_requirements)) { + $request .= $student_requirements.' AND '; + } + if (defined($enrollment_requirements)) { + $request .= $enrollment_requirements.' AND '; + } + if (defined($time_requirements)) { + $request .= $time_requirements.' AND '; + } + $request =~ s/ AND $//; # Strip of the trailing ' AND '. + $request .= ' GROUP BY a.student_id'; +# &Apache::lonnet::logthis("request = \n".$request); + my $sth = $dbh->prepare($request); + $sth->execute(); + if ($dbh->err) { + &Apache::lonnet::logthis('error = '.$dbh->errstr()); + return undef; + } + $request = 'SELECT score,COUNT(*) FROM '.$tmptable.' GROUP BY score'; +# &Apache::lonnet::logthis("request = \n".$request); + $sth = $dbh->prepare($request); + $sth->execute(); + if ($dbh->err) { + &Apache::lonnet::logthis('error = '.$dbh->errstr()); + return undef; + } + my $dataset = $sth->fetchall_arrayref(); + return $dataset; +} + +################################################ +################################################ + +=pod + =item &setup_table_names() input: course id @@ -2170,7 +2375,6 @@ sub setup_table_names { $symb_table = $base_id.'_'.'symb'; $part_table = $base_id.'_'.'part'; $student_table = $base_id.'_'.'student'; - $studentdata_table = $base_id.'_'.'studentdata'; $performance_table = $base_id.'_'.'performance'; $parameters_table = $base_id.'_'.'parameters'; $fulldump_part_table = $base_id.'_'.'partdata'; @@ -2181,7 +2385,6 @@ sub setup_table_names { $symb_table, $part_table, $student_table, - $studentdata_table, $performance_table, $parameters_table, $fulldump_part_table,