Theoretically this could be done as a check constraint in plain SQL, but it’s pretty convoluted and probably not worth the effort. Lots of nested and replicated substring and cast (to number and string) function calls.
-- Oracle SQL syntax, in the context of a CHECK constraint:
constraint Student_ID_Checksum check (
substr(to_char(Student_ID, 'FM0000000'), 7, 1) =
mod(
mod(
4 * substr(to_char(Student_ID, 'FM0000000'), 1, 1) +
6 * substr(to_char(Student_ID, 'FM0000000'), 2, 1) +
3 * substr(to_char(Student_ID, 'FM0000000'), 3, 1) +
5 * substr(to_char(Student_ID, 'FM0000000'), 4, 1) +
2 * substr(to_char(Student_ID, 'FM0000000'), 5, 1) +
1 * substr(to_char(Student_ID, 'FM0000000'), 6, 1)
, 11)
, 10)
)
-- PostgreSQL syntax (using standard SQL SUBSTRING and CAST expressions):
mod(
mod(
4 * cast(substring(to_char(Student_ID, 'FM0000000') from 1 for 1) as integer) +
6 * cast(substring(to_char(Student_ID, 'FM0000000') from 2 for 1) as integer) +
3 * cast(substring(to_char(Student_ID, 'FM0000000') from 3 for 1) as integer) +
5 * cast(substring(to_char(Student_ID, 'FM0000000') from 4 for 1) as integer) +
2 * cast(substring(to_char(Student_ID, 'FM0000000') from 5 for 1) as integer) +
1 * cast(substring(to_char(Student_ID, 'FM0000000') from 6 for 1) as integer)
, 11)
, 10)
PL/pgSQL arrays are 1 indexed.
create or replace function Valid_Otago_Student_ID(in Student_ID integer) returns boolean as $$
declare
-- weights are in reverse order because digits are numbered from right to left,
-- i.e., digit 1 is the rightmost digit
weights smallint[] := '{0, 1, 2, 5, 3, 6, 4}';
computed_check_digit smallint := 0;
student_check_digit smallint := Student_ID % 10;
digit smallint;
begin
for i in reverse 7..2 loop
digit := trunc(Student_ID / 10.0 ^ (i - 1) % 10.0);
computed_check_digit := computed_check_digit + digit * weights[i];
end loop;
computed_check_digit := computed_check_digit % 11 % 10;
return student_check_digit = computed_check_digit;
end;
$$ language 'plpgsql';
This can be used in a trigger:
create or replace function Valid_Otago_Student_ID() returns trigger as $$
begin
if Valid_Otago_Student_ID(new.Student_ID) then
return new;
else
raise check_violation using message = 'Invalid student ID ' || new.Student_ID;
end if;
end;
$$ language 'plpgsql';
create trigger Valid_Otago_Student_ID
before insert or update of Student_ID on Student
for each row execute procedure Valid_Otago_Student_ID();
// Should work with both strings and numbers; PHP data types are extremely fluid.
function validate_student_ID ( $student_id )
{
$digits = str_split( str_pad( $student_id, 7, '0', STR_PAD_LEFT ) );
$check_digit = ( ( $digits[0] * 4 ) +
( $digits[1] * 6 ) +
( $digits[2] * 3 ) +
( $digits[3] * 5 ) +
( $digits[4] * 2 ) +
( $digits[5] * 1 )
) % 11 % 10;
return ( $check_digit == $digits[6] );
}
This will probably be faster overall, but the code is harder to follow if you don’t know how the maths works.
import java.lang.Math;
public class OtagoStudentId {
// weights are in reverse order because digits are numbered from right to left,
// i.e., digit 0 is the rightmost digit
private static final Integer[] WEIGHTS = new Integer[]{0, 1, 2, 5, 3, 6, 4};
public static Boolean validate(Integer studentId) {
Integer computedCheckDigit = 0;
Integer studentCheckDigit = studentId % 10;
for (Integer i = 6; i > 0; i--) {
Integer digit = (int)(studentId / Math.pow(10, i) % 10);
computedCheckDigit += digit * WEIGHTS[i];
}
computedCheckDigit = computedCheckDigit % 11 % 10;
return studentCheckDigit.equals(computedCheckDigit);
}
public static Boolean validate(String studentId) {
return validate(new Integer(studentId));
}
}
This is easier to understand, but will probably be slower due to additional string jiggery-pokery.
public class OtagoStudentId {
// digits are numbered from left to right, i.e., digit 0 is the leftmost digit
private static final Integer[] WEIGHTS = new Integer[]{4, 6, 3, 5, 2, 1, 0};
public static Boolean validate(String studentId) {
Integer computedCheckDigit = 0;
// left pad input with 0 up to length 7
String paddedId = String.format("%1$7s", studentId).replace(' ', '0');
Integer studentCheckDigit = Integer.parseInt(paddedId.substring(6));
for (Integer i = 0; i < 6; i++) {
Integer digit = Integer.parseInt(paddedId.substring(i, i + 1));
computedCheckDigit += digit * WEIGHTS[i];
}
computedCheckDigit = computedCheckDigit % 11 % 10;
return studentCheckDigit.equals(computedCheckDigit);
}
public static Boolean validate(Integer studentId) {
return validate(String.valueOf(studentId));
}
}
def valid_id(id):
digits = [int(i) for i in str(id).zfill(7)]
multipliers = [4, 6, 3, 5, 2, 1]
mdigits = [a*b for a,b in zip(digits[:6], multipliers)]
return (sum(mdigits) % 11 % 10) == digits[6]