What you will be able to do
- Cast string columns from bronze to proper types with cast in PySpark and SQL
- Predict when cast raises an error, and use try_cast to get NULL instead
- Write a cleaned DataFrame to a silver table with saveAsTable and choose the right mode
- Protect a silver table with enforced NOT NULL and CHECK constraints
1.Casting bronze strings to real types
Bronze tables usually hold most fields as strings so that unexpected schema changes don't break ingestion. Type casting is one of the operations the silver layer is expected to perform. In PySpark, Column.cast(dataType) takes either a type name string or a DataType instance, and astype is an alias for cast. In SQL, cast(expr AS type) does the same thing, and the :: operator is a synonym for it.
from pyspark.sql.types import StringType
df = spark.createDataFrame(
[(2, "Alice"), (5, "Bob")], ["age", "name"])
df.select(df.age.cast("string").alias('ages')).collect()
# [Row(ages='2'), Row(ages='5')]Not every pair of source and target types can be cast. The cast function documents a full matrix of valid combinations. These are the rows that matter most for cleaning bronze:
| Source type | Target type | Valid? |
|---|---|---|
| STRING | numeric | Y |
| STRING | DATE / TIMESTAMP | Y |
| STRING | BOOLEAN | Y |
| DATE | numeric | N |
| TIMESTAMP | numeric | Y |
| BOOLEAN | numeric | Y |
A valid type pair still doesn't guarantee that every value converts cleanly. Casting a numeric to an integral type truncates toward zero, so cast(5.6 AS INT) returns 5 and cast(-5.6 AS INT) returns -5. Casting a string reads the string as a literal of the target type, which is where malformed bronze values cause trouble.
Date and timestamp strings often need more than a plain cast. Other built-in functions cast between types using format directives: to_date(expr[,fmt]) returns expr cast to a date using an optional format, and to_timestamp(expr[,fmt]) does the same for timestamps. Like cast, they are sensitive to ANSI mode. With an invalid expr or format string they raise an error in ANSI mode and return NULL when ANSI mode is off.
Checkpoint 1 of 6· Check yourself
What does SELECT cast(5.6 AS INT) return?
Casting to an integral numeric truncates to a whole number rather than rounding, so 5.6 becomes 5.
“If targetType is an integral numeric, the result is sourceExpr truncated to a whole number.”Source: docs.databricks.com
2.When cast fails: errors, ANSI mode and try_cast
> SELECT cast('123' AS INT);
123
> SELECT cast('123.0' AS INT);
Error: CAST_INVALID_INPUTValues that are out of range fail too. cast(128 AS TINYINT) raises CAST_OVERFLOW. This strict behaviour depends on ANSI mode. In Databricks Runtime, if spark.sql.ansi.enabled is false, an overflow wraps instead of raising an error, and a string with an invalid format or invalid characters for the target type becomes NULL. So the CAST_INVALID_INPUT error above is what you see with ANSI mode on. Databricks recommends using an explicit cast or try_cast rather than relying on ANSI_MODE = FALSE. For cleaning, try_cast is usually the right tool. It performs the same cast, but returns NULL where cast would raise an overflow or invalid-format error, and it behaves the same way in both ANSI modes. A row that won't convert then becomes an ordinary null, and the null-handling techniques from bronze cleaning apply to it.
from pyspark.sql.types import LongType
df = spark.createDataFrame(
[(2, "123"), (5, "Bob"), (3, None)], ["age", "name"])
df.select(df.name.try_cast(LongType())).show()
# +----+
# |name|
# +----+
# | 123|
# |NULL|
# |NULL|
# +----+PySpark's Column.try_cast was added in Databricks Runtime 15.0. There is one limit to how forgiving try_cast is: it only tolerates bad values. If the type pair itself isn't a valid cast combination, it still raises DATATYPE_MISMATCH.
Checkpoint 2 of 6· Fill the gap
This silver job must turn unparseable strings into NULL instead of failing. Which method completes the call?
df = spark.createDataFrame(
[(2, "123"), (5, "Bob"), (3, None)], ["age", "name"])
df.select(df.name. ? ("double")).show()try_cast returns null when a conversion fails. cast and its alias astype can raise an error on invalid input, and fillna doesn't convert types.
Source: docs.databricks.comCheckpoint 3 of 6· Exam question
A data engineer is cleaning a bronze `sensor_readings` table where a valid row needs at least 3 of its 5 metric columns populated to be usable, and rows below that threshold should be discarded before writing to silver. Which call achieves this?
Correct answer: A — Call `df.na.drop(thresh=3)` to keep only rows with at least three non-null values across their columns
- A. The `thresh` parameter counts non-null values per row and keeps only rows that meet or exceed that count, which directly implements the "at least 3 of 5 populated" rule described in the scenario.
- B. `how="all"` only drops a row when every column is null, so rows with just one populated metric out of five would still be kept, failing the minimum-threshold requirement.
- C. Filtering on a single named column ignores the other four metrics entirely, so a row could pass with only `metric_1` populated even though the rule requires at least three populated fields.
- D. Filling every missing value with zero keeps sparse rows in the table by fabricating readings, which hides the exact rows the threshold rule is meant to discard.
3.Writing the cleaned result to a silver table
Once the data is clean and typed, it goes to a new silver table. In SQL that is INSERT INTO silver_table SELECT ... FROM bronze_table. When inserting, Databricks casts data types when it can do so safely without losing information. In PySpark, DataFrameWriter.saveAsTable saves a DataFrame as a named table:
spark.createDataFrame([
(100, "Alice"), (120, "Bob"), (140, "Tom")],
schema=["age", "name"]
).write.saveAsTable("tblA")What happens when the target already exists depends on mode. The accepted values are 'append', 'overwrite', 'error' or 'errorifexists' (the default), and 'ignore'. The source documents the behaviour of the first three as follows; it lists 'ignore' as an accepted value but does not describe what it does, so check the documentation before relying on it.
| mode | Behaviour |
|---|---|
| 'error' / 'errorifexists' (default) | Throws an exception |
| 'overwrite' | Replaces the data; the DataFrame schema does not need to match the existing table schema |
| 'append' | Adds rows using the existing table's format and options |
When appending to an existing table, saveAsTable uses column names to find the correct column positions, unlike insertInto.
Checkpoint 4 of 6· Check yourself
A nightly job calls df.write.saveAsTable("silver.orders") with no mode, and the table already exists from yesterday's run. What happens?
The default mode is 'error'/'errorifexists', which throws when the table exists. Set mode='append' or mode='overwrite' explicitly.
“If the table already exists, the behavior depends on the mode parameter (default is to throw an exception).”Source: docs.databricks.com
Checkpoint 5 of 6· Exam question
In Spark SQL, which expression fills a null `shipping_country` with the value from `billing_country` only when `shipping_country` is missing, and leaves it unchanged otherwise?
Correct answer: A — Use `COALESCE(shipping_country, billing_country)` so the billing value fills in only when shipping is missing
- A. `COALESCE` returns its first non-null argument, so it keeps the existing `shipping_country` when present and substitutes `billing_country` only for the null rows, matching the described fallback rule exactly.
- B. `NULLIF` returns null when the two arguments are equal and otherwise returns the first argument, which does the opposite of filling a null and can never introduce the `billing_country` value.
- C. Casting to `STRING` only changes the declared type of the column and performs no null substitution, so any missing `shipping_country` values remain null after this expression runs.
- D. String concatenation with `||` combines both columns into one text value, which corrupts populated rows and also yields null whenever either operand is null in most engines.
4.Making the silver table enforce what you cleaned
Cleaning code can regress, so the silver table itself can reject bad data on write. Delta Lake applies schema enforcement and constraint checks when data is written. Two constraint types are enforced: NOT NULL and CHECK. Primary and foreign keys are informational only. A CHECK constraint can enforce a value range or a regex pattern (using REGEXP or RLIKE):
CREATE TABLE table_name (
column_name INT CHECK (column_name >= 0 AND column_name <= 100)
);NOT NULL blocks new rows that have nulls in the column. You can add it to an existing table only if the column currently contains no nulls. So clean the existing nulls first, then add the constraint.
Checkpoint 6 of 6· Match them up
Match each constraint to how Databricks treats it
Tap a term, then the definition that fits it.
NOT NULL and CHECK are enforced on write. Primary and foreign keys are informational.
“Enforced constraints include NOT NULL and CHECK constraints.”Source: docs.databricks.com
Sources8
Exam traps
Each one states something that sounds right. Open it to see what is actually true.
1.try_cast never raises an error, whatever types you give it.Why is that wrong?
try_cast returns NULL for malformed or out-of-range values only. An invalid source and target type combination still raises DATATYPE_MISMATCH.
2.Casting an invalid string always raises CAST_INVALID_INPUT.Why is that wrong?
That is the ANSI-mode behaviour. With spark.sql.ansi.enabled false, a string with an invalid format or invalid characters becomes NULL instead.
3.saveAsTable appends to an existing table by default.Why is that wrong?
The default mode throws an exception when the table exists. Appending requires mode='append'.
Covered in Writing the cleaned result to a silver table
4.You can add NOT NULL to an existing silver table at any time, and it only affects future rows.Why is that wrong?
NOT NULL can be enabled on an existing table only if the column has no nulls yet, so existing nulls must be cleaned first.
Practise it for real
Fill nulls, cast a column, write the result to a table and read it back, using only the documented PySpark calls
1.Create the fillna sample: df = spark.createDataFrame([(10, 80.5, "Alice", None), (5, None, "Bob", None), (None, None, "Tom", None), (None, None, None, True)], schema=["age", "height", "name", "bool"])
Why: This gives you a small stand-in for a bronze DataFrame, with nulls in several columns.
You should see: A four-row DataFrame with NULLs in age, height, name and bool.
2.Run filled = df.na.fill({'age': 50, 'name': 'unknown'}), then filled.show()
Why: A dict fill sets a default for each column. Assigning the result matters, because fillna returns a new DataFrame and leaves df unchanged.
You should see: age nulls become 50, the null name becomes 'unknown', and height stays NULL.
3.On the filled DataFrame, run filled.select(filled.age.cast("string").alias('ages')).show()
Why: This practises Column.cast with a type name string, on the column you just filled.
You should see: An ages column holding string values such as '10', '5' and '50'.
4.Write the cleaned DataFrame with filled.write.saveAsTable("tblA"), then run it again unchanged
Why: This shows the default mode's behaviour when the table already exists.
You should see: The first write succeeds. The second throws an exception, because the default mode is 'error'.
5.Run spark.read.table("tblA").sort("age").show(), then spark.sql("DROP TABLE tblA")
Why: Reading the table back confirms what was written, and dropping it cleans up.
You should see: The cleaned rows, sorted by age.
Stuck? Get a nudge
If the second write didn't fail, check whether you passed a mode argument.
Sources
Every claim above is drawn from one of these pages, quoted as it was written on the date shown.
- 1.
“This operator is a synonym for :: (colon colon sign) operator”
↩︎ Casting bronze strings to real types“Use try_cast to turn overflow and invalid format errors into NULL.”
↩︎ When cast fails: errors, ANSI mode and try_cast“if spark.sql.ansi.enabled is false, an overflow will not cause an error”
↩︎ When cast fails: errors, ANSI mode and try_cast“A sourceExpr value with an invalid format or invalid characters for targetType will result in a NULL.”
↩︎ When cast fails: errors, ANSI mode and try_cast“A sourceExpr value with an invalid format or invalid characters for targetType will result in a NULL.”
↩︎ Exam trap 2“If targetType is an integral numeric, the result is sourceExpr truncated to a whole number.”
↩︎ Checkpoint“If sourceExpr doesn't comply with the format for literal values, Databricks raises CAST_INVALID_INPUT.”
↩︎ Prediction - 2.
“Convert the column to a different data type.”
↩︎ Casting bronze strings to real types - 3.
“Alias for cast().”
↩︎ Casting bronze strings to real types - 4.
“Other builtin functions cast between types using provided format directives.”
↩︎ Casting bronze strings to real types - 5.
“Returns expr cast to a date using an optional formatting.”
↩︎ Casting bronze strings to real types“Databricks recommends using the explicit cast or try_cast function instead of relying on ANSI_MODE = FALSE.”
↩︎ When cast fails: errors, ANSI mode and try_cast - 6.
“Try to convert the column to a different data type. Returns null if conversion fails.”
↩︎ When cast fails: errors, ANSI mode and try_cast - 7.https://docs.databricks.com/aws/en/pyspark/reference/classes/dataframewriter/saveAsTableOfficial docs
“When mode is 'overwrite', the schema of the DataFrame does not need to match the existing table schema.”
↩︎ Writing the cleaned result to a silver table“Unlike DataFrameWriter.insertInto, DataFrameWriter.saveAsTable uses column names to find the correct column positions.”
↩︎ Writing the cleaned result to a silver table“If the table already exists, the behavior depends on the mode parameter (default is to throw an exception).”
↩︎ Exam trap 3“If the table already exists, the behavior depends on the mode parameter (default is to throw an exception).”
↩︎ Checkpoint - 8.
“When inserting or updating data in a table, Databricks casts data types when it can do so safely without losing information.”
↩︎ Writing the cleaned result to a silver table“Informational constraints include primary key and foreign key constraints.”
↩︎ Making the silver table enforce what you cleaned“prevents new records with null values from being inserted into a table.”
↩︎ Making the silver table enforce what you cleaned“It can only be enabled on an existing table if no existing records in the column are null”
↩︎ Exam trap 4“Enforced constraints include NOT NULL and CHECK constraints.”
↩︎ Checkpoint
Also cited
“If the source and target types are not a valid cast combination, a DATATYPE_MISMATCH error is returned.”
↩︎ Exam trap 1