MySQL raises SQLSTATE[23000] (error 1048) when a PHP PDO statement sends NULL to a column declared NOT NULL. The declaration is doing its job: NOT NULL rejects missing values; it does not make a PHP variable non-null. In the SitePoint attendance example, the rejected column is present. Inspect the value and type reaching execute(), then pass an intentional value that matches the column’s schema.
What the error means
MySQL’s 8.4 Error Reference defines error 1048 (ER_BAD_NULL_ERROR) with SQLSTATE 23000 and the message template “Column ‘%s’ cannot be null.” The named column is the one for which MySQL received SQL NULL during the insert or update.
A database constraint cannot change the contents of $present, $memberId, or any other PHP variable. PDO converts the parameter value it receives and sends it to MySQL; MySQL then applies the column constraints. If the value is null, a NOT NULL column rejects it instead of silently inventing a value.
What the SitePoint example establishes—and what it does not
The forum post describes inserting an attendance row after checking whether an absence record exists. The prepared INSERT names member_id, member_email, member_phone, present, and attend_state. The exception identifies present as the column that received NULL.
Recommended Free Tools
#1 Best Overall
The discussion does not provide enough final code to prove one unique typo, framework defect, or database bug. Treat it as a parameter-flow problem: determine which branch, assignment, scope, or binding leaves $present null at the exact execution point.
A later attempt assigned empty strings and produced an “incorrect integer value” error for present. That confirms that an empty string is not SQL NULL, but it is not a general fix. If the column is numeric, an empty string may be rejected or converted according to the server’s SQL mode. Choose a value that has a defined meaning in the application and is valid for the actual column type.
Rank #2
Debug the value at the failing execute()
- Read the complete exception. Record the SQLSTATE, vendor code, column name, and the application line where
execute()fails. Here, those clues point to error 1048 andpresent. - Inspect every parameter immediately before execution. In development, use
var_dump($present);or a log entry that safely redacts personal data. Check both the value and PHP type. Repeat for every value in the statement, because fixingpresentmay reveal another missing parameter. - Verify input names and validation. A form field called
presentmust be read using the same name. Checkisset()logic, validation failures, checkbox behavior, and any conversion that turns an absent value intoNULL. - Trace every branch. Confirm that each
if/elsepath assigns$presentbefore execution. Also check variable scope and whether a later assignment overwrites a previously valid value. - Compare the runtime schema. Inspect the database that the application is actually using, including the column type,
NOT NULLconstraint, default, and any triggers. A local schema may not match the production table.
Understand bindParam() timing
PHP’s manual states that, unlike PDOStatement::bindValue(), bindParam() binds a variable by reference and evaluates it when PDOStatement::execute() is called. Therefore, the value at binding time is not necessarily the value sent to MySQL.
For example, this code sends the value assigned immediately before execute():
$stmt->bindParam(':present', $present, PDO::PARAM_INT);
$present = 1;
$stmt->execute();
That reference behavior can be useful in loops, but it makes control flow harder to audit. If you use bindParam(), trace the referenced variable at the execution line and make sure it remains in scope. For a one-time value, bindValue() is usually clearer because it associates the current value with the placeholder immediately.
Use one clear parameter-passing style
Pass a complete array to execute()
For a simple insert, passing all values at the execution point makes the data flow visible:
Rank #4
$stmt = $pdo->prepare(
'INSERT INTO attendance
(member_id, member_email, member_phone, present, attend_state)
VALUES (:member_id, :member_email, :member_phone, :present, :attend_state)'
);
$stmt->execute([
'member_id' => $memberId,
'member_email' => $memberEmail,
'member_phone' => $memberPhone,
'present' => $present,
'attend_state' => $attendState,
]);
PHP documents that values supplied in the execute() array are treated as PDO::PARAM_STR. That is often acceptable for MySQL, but it does not replace validation or guarantee that an empty string is valid for an integer column.
Bind values explicitly when type matters
Use bindValue() when you want the type to be explicit at the point of binding:
Do these 3 things before closing this tab:
1Repair Windows errors before they cause bigger problems2Scan for outdated or missing drivers - takes under a minute3Clear out junk files and repair common Windows errors$stmt->bindValue(':member_id', $memberId, PDO::PARAM_INT);
$stmt->bindValue(':member_email', $memberEmail, PDO::PARAM_STR);
$stmt->bindValue(':member_phone', $memberPhone, PDO::PARAM_STR);
$stmt->bindValue(':present', $present, PDO::PARAM_INT);
$stmt->bindValue(':attend_state', $attendState, PDO::PARAM_STR);
$stmt->execute();
Do not mix a partial set of bindings with an unrelated parameter array unless you have a specific reason and have verified the resulting behavior. A complete array or a complete set of bindings is easier to review.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.NULL, empty string, and a valid attendance value are different
| Value reaching PDO | What MySQL receives | Typical consequence for an integer-like present column |
|---|---|---|
NULL |
SQL NULL |
Rejected by NOT NULL; error 1048. |
'' |
An empty string | Not the same as null; may produce an incorrect-integer-value error or mode-dependent conversion. |
0 or 1 |
A numeric value (when bound or converted appropriately) | Valid only if those values match the column definition and the application’s attendance rules. |
Decide what “present” means before choosing a replacement. If the domain is binary, the schema may expect 0/1; if attendance can be unknown, the schema and application may need an explicit state rather than an accidental null. Check the real column definition and constraints instead of substituting an empty string.
Common parameter-flow failures to check
- The request omitted a field, especially a checkbox that is absent when unchecked.
$_POSTor another input array uses a different key from the code that reads it.- A validation branch exits without assigning a fallback or returning an error.
- A variable is assigned inside a conditional or function scope but used outside it.
- The code binds one variable and later executes with another variable or placeholder name.
- A later branch resets the variable to
NULL. - The statement’s placeholder name and the array key do not match exactly.
- The application connects to a different database or table than the one inspected manually.
Keep the prepared statement
Do not fix this by concatenating user input into SQL. PHP’s PDO::prepare() documentation recommends parameters for user-supplied data. Prepared statements address injection risk; they do not validate business values, so still validate and normalize present and the other fields before execution.
Quick Recap
A practical verification sequence
- Reproduce the exception with development logging enabled.
- Print or log the type and value of
$presentimmediately beforeexecute(). - Log the other parameter names (with sensitive values redacted) and confirm that each has an intentional value.
- Confirm the placeholder names in the SQL exactly match the array keys or bindings.
- Check whether
bindParam()is involved; if so, inspect the referenced variable at execution time. - Verify the live schema and decide whether the domain value should be 0, 1, another allowed value, or a deliberately nullable design.
- Retry with the complete execute array or explicit
bindValue()calls, then remove verbose value logging outside development.
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.




