Understanding SQL Data Types: A Complete Guide

Introduction
In SQL, every column in a database table is designed to hold a specific kind of data. SQL data types are the rules that define what type of value can be stored in a column. For instance, if you have a column for a user’s age, you would set its data type to INT to ensure only whole numbers are stored. Using the correct data type is fundamental for maintaining data integrity, optimizing storage, and ensuring your database runs efficiently.
SQL data types can be broadly divided into the following categories:
- Numeric data types such as INT, TINYINT, BIGINT, FLOAT, REAL, etc.
- Date and Time data types such as DATE, TIME, DATETIME, etc.
- Character and String data types such as CHAR, VARCHAR, TEXT, etc.
- Unicode character string data types such as NCHAR, NVARCHAR, NTEXT, etc.
- Binary data types such as BINARY, VARBINARY, etc.
- Miscellaneous data types such as CLOB, BLOB, XML, CURSOR, TABLE, etc.
While many data types are standardized, their names and behavior can vary slightly across different SQL database systems like MySQL, PostgreSQL, and SQL Server. In this guide, you’ll learn everything you need to know about choosing and using the right SQL data types for your projects.
Selecting the appropriate data type isn’t just a technical formality; it has real-world consequences for your application.
- Storage Efficiency: Why use a BIGINT, which consumes 8 bytes of storage, for a column that will only ever hold numbers from 1 to 10? Choosing the smallest data type that safely accommodates your data range saves significant disk space, especially in large tables. This directly relates to the SQL data type size.
- Performance: Smaller data types mean the database can read more data from memory or disk in a single operation, leading to faster queries. Correctly configured numeric types are also processed much faster than numbers stored as strings.
- Data Integrity: Data types enforce rules on your data. A DATE column will reject a nonsensical value like ‘Hello, World!’, preventing corrupt data from entering your database. This is your first line of defense against application-level bugs. For more on data integrity, see how to use primary keys in SQL.
While the SQL data types covered in this guide are widely used, it’s important to remember that their implementation can differ from one database system to another. Before finalizing your database schema, always keep these key differences in mind:
- Varying Support for Standard Types: Not all database vendors support the same set of data types. For example, the CLOB (Character Large Object) type is common in Oracle but is not supported in MySQL, which uses the TEXT family for the same purpose.
- Proprietary Data Types: Many vendors introduce their own specialized data types that are not part of the ANSI SQL standard. A classic example is Microsoft SQL Server, which offers the convenient MONEY and SMALLMONEY types for handling currency.
- Different Maximum Size Limits: The maximum size for a given data type, such as VARCHAR, can also vary between database systems.
Because of these variations, the best practice is to always consult the official documentation for your specific database vendor. This ensures that the data types you choose are fully supported and optimally configured for your particular scenario. Be sure to select the appropriate data type for your particular scenario.
Numeric data types are used for any data that consists of numbers. They are split into several categories, including exact types for whole numbers (integers) and fractional numbers (decimals), as well as approximate types for scientific calculations where absolute precision is not the primary goal (floating-point).
Here is a comprehensive table of the most common SQL numeric data types, including their storage size and value ranges.
Data Type | Storage Size | Range of Values | Common Use Case |
---|---|---|---|
TINYINT | 1 byte | 0 to 255 (if unsigned) or -128 to 127 (if signed). | A very small integer. Used for small, whole numbers like a person’s age or a quantity less than 256. |
SMALLINT | 2 bytes | -32,768 to 32,767 | A small integer. Ideal for values like the number of pages in a book or a year of manufacture. |
INT | 4 bytes | -2,147,483,648 to 2,147,483,647 | The standard integer. The most common choice for general-purpose whole numbers, like user IDs or product counts. |
BIGINT | 8 bytes | -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 | A very large integer. Used for extremely large numbers, like transaction IDs in a global financial system or scientific data. |
DECIMAL(p,s) | Variable | -10^38 + 1 to 10^38 – 1 | An exact-value decimal number with user-defined precision. Crucial for financial and monetary data. p is total digits, s is digits after decimal. |
NUMERIC(p,s) | Variable | -10^38 + 1 to 10^38 – 1 | Functionally identical to DECIMAL. Often used interchangeably. |
FLOAT(n) | 4 or 8 bytes | -1.79E+308 to 1.79E+308 | An approximate-value, floating-point number. Used for scientific calculations where slight precision loss is acceptable. |
REAL | 4 bytes | -3.40E+38 to 3.40E+38 | A single-precision, approximate-value floating-point number. A smaller and less precise version of FLOAT. |
Storing dates and times correctly is crucial for logging, scheduling, and tracking events. The key difference is scope. Use DATE for values like a person’s birthday, where the time of day is irrelevant. Use DATETIME or TIMESTAMP when you need to record the exact moment an event occurred, such as when a user logs in or an order is placed.
Data Type | Description |
---|---|
DATE | Stores only the date in the format YYYY-MM-DD |
TIME | Stores only the time in the format HH:MI:SS |
DATETIME | Stores both date and time information in the format YYYY-MM-DD HH:MI:SS |
TIMESTAMP | Stores number of seconds passed since the Unix epoch (‘1970-01-01 00:00:00’ UTC) |
YEAR | Stores year in a 2-digit or 4-digit format. Range 1901 to 2155 in 4-digit format. Range 70 to 69, representing 1970 to 2069. |
String data types are used to store all forms of text data, from single letters to entire documents. The most common decision you will face is choosing between fixed-length types (which always take up the same amount of space) and variable-length types (which adapt their size to the data). Another critical consideration is whether you need to support international characters using Unicode types.
This table provides an overview of the most common character and string data types.
Data Type | Description & Purpose | Unicode Support | Max Size (SQL Server) | Common Use Cases |
---|---|---|---|---|
CHAR(n) | A fixed-length string. It is always padded with spaces to meet the specified length n. | No | 8,000 characters | Two-letter state codes (CA), product IDs (SKU123), Y/N flags. Use when data is always the same length. |
VARCHAR(n) | A variable-length string with a maximum length of n. Uses only the storage required for the actual text. | No | 8,000 characters (Up to 65,535 bytes in MySQL) | Usernames, email addresses, titles. The most common and versatile string type for non-Unicode text. |
TEXT | A variable-length type for storing very large blocks of text. | No | 2 GB | Long product descriptions, article content, user comments. |
NCHAR(n) | National character set. A fixed-length Unicode string. Essential for multilingual data that is always a consistent length. | Yes | 4,000 characters | Storing fixed-length identifiers or codes in multiple languages. |
NVARCHAR(n) | National character set. A variable-length Unicode string. The standard choice for storing multilingual text. | Yes | 4,000 characters (or NVARCHAR(MAX) for up to 2GB) | Storing names, comments, and general text from users around the world. |
NTEXT | National character set. A legacy type for large blocks of Unicode text. | Yes | 2 GB | Previously used for storing large documents in multiple languages. NVARCHAR(MAX) is now preferred in modern SQL Server. |
Note: MySQL handles Unicode differently. It does not use NCHAR, NVARCHAR, NTEXT. Instead, you use the standard VARCHAR or TEXT types and set the column’s character set to a Unicode-compatible one, like utf8mb4. For example: username VARCHAR(100) CHARACTER SET utf8mb4.
Binary data types are used for storing raw binary data, such as files, instead of human-readable text. These types are essential when you need to store things like images, audio clips, or compiled code directly in your database.
Data Type | Description | Common Use Cases | Maximum Size |
---|---|---|---|
BINARY(n) | A fixed-length binary type of n bytes. It’s padded with zero bytes if the data stored is shorter than n. | Storing data that is always the same size, like cryptography hashes (an MD5 hash is always 16 bytes), or fixed-length identifiers. | Up to 8,000 bytes (SQL Server); 255 bytes (MySQL) |
VARBINARY(n) | A variable-length binary type with a maximum size of n bytes. It does not pad the data, saving space. | Storing small, variable-sized binary data like user-uploaded thumbnails, QR codes, or other items where you have a known maximum size. | Up to 8,000 bytes (SQL Server); 65,535 bytes (MySQL) |
BLOB | Binary Large Object. A type for storing very large binary data. In MySQL, this is a family of types (TINYBLOB, BLOB, MEDIUMBLOB, LONGBLOB) to handle different size requirements. | Storing files directly in the database, such as images, audio files, PDF documents, or serialized programming objects. | Up to 4 GB (LONGBLOB in MySQL) |
IMAGE (Legacy) | A legacy data type from older versions of SQL Server, functionally similar to BLOB. | Historically used for storing images. In modern SQL Server, VARBINARY(MAX) is the recommended and more flexible replacement. | 2 GB |
Note: While storing small images or files in a BLOB can be convenient, for larger, high-traffic applications, it’s often better to store the files on a dedicated file system or object storage service (like DigitalOcean Spaces) and just store the URL or file path in the database.
SQL databases now include a variety of specialized data types designed to handle modern data structures like JSON, geographical data, or simple truth values.
Data Type | Description | Common Use Cases | Maximum Size / Storage |
---|---|---|---|
JSON | Stores text in the JavaScript Object Notation format. Modern databases provide special functions and operators to query this structured data. | Storing application settings, logs, or data from third-party web APIs. Useful for semi-structured data without needing a rigid schema. | Varies, often up to 1-4 GB (limited by underlying text/blob storage). |
XML | Stores data in the eXtensible Markup Language format. Often includes methods for validation against XML schemas. | Storing configuration files, data from legacy enterprise systems (like SOAP APIs), or other structured documents. | 2 GB (SQL Server) |
CLOB | Character Large Object. A standard SQL term for storing exceptionally large pieces of text. | Storing entire text files or book-length documents. (This keyword is common in Oracle; MySQL uses the TEXT family, and SQL Server uses VARCHAR(MAX)). | Varies, often 2 GB or more. |
BOOLEAN | Stores logical truth values: TRUE or FALSE. Can sometimes also hold an “unknown” state (NULL). | Storing flags like is_active, has_subscribed, or is_verified. (Note: MySQL uses TINYINT(1) as its equivalent). | 1 byte |
UUID | Universally Unique Identifier. A 128-bit number used to uniquely identify information across different systems. | Generating unique primary keys, especially in distributed databases where auto-incrementing integers from different servers could clash. | 16 bytes (128 bits) |
While the core SQL data types like INT and VARCHAR are widely supported, each major database system has its own unique “flavor,” offering proprietary data types or different behaviors for standard ones. Understanding these variations is key to writing portable and efficient SQL code.
MySQL Data Types
MySQL is known for its ease of use and offers some convenient, non-standard features.
- ENUM: A very popular MySQL feature that creates a string object where a column’s value must be chosen from a predefined list. It’s highly efficient as it stores the values as small integers internally. While efficient, adding new items to the list later can be a slow and locking operation on large tables.
- SET: Similar to ENUM, but a single column can hold multiple values from a predefined list.
- UNSIGNED Attribute: This isn’t a data type but a crucial attribute for numeric types. By declaring a number as UNSIGNED, you prevent it from storing negative values, effectively doubling its positive range. An INT UNSIGNED has a range of 0 to 4,294,967,295, making it a very common choice for primary keys.
- YEAR: A 1-byte type for storing a year. It can be defined as YEAR(4) to store four-digit years or the legacy YEAR(2) format, which is generally discouraged.
PostgreSQL Data Types
PostgreSQL (or “Postgres”) is renowned for its strict adherence to the SQL standard and its rich set of complex data types.
- ARRAY: A powerful feature allowing a single column to store an array of values. This can simplify schemas where you might otherwise need a separate related table.
- JSONB vs. JSON: PostgreSQL offers two types for storing JSON. The JSON type stores an exact textual copy, while the preferred JSONB type stores the data in a decomposed binary format. JSONB is faster to query and supports indexing, making it superior for most applications.
- UUID: A native type for storing 128-bit Universally Unique Identifiers. This is often preferred over SERIAL for primary keys in distributed systems to avoid ID clashes.
- Network Address Types: PostgreSQL has native types like INET and CIDR for storing and validating IPv4 and IPv6 host and network addresses.
SQL Server Data Types
Microsoft’s SQL Server offers a robust set of types with a focus on its ecosystem.
- MONEY / SMALLMONEY: These types are optimized for currency, storing values with a fixed precision of four decimal places. While convenient, many developers still prefer the standard DECIMAL type for full control over precision.
- DATETIME2: This is the modern, recommended replacement for the older DATETIME type. It offers a larger date range (year 0001 to 9999), user-defined fractional-second precision, and is more compliant with the ANSI SQL standard.
- UNIQUEIDENTIFIER: This is SQL Server’s native data type for storing UUIDs, often used in conjunction with the NEWID() function to generate primary keys.
- VARCHAR(MAX)/NVARCHAR(MAX): These types replace the legacy TEXT and NTEXT types. They provide the storage capacity of large objects (up to 2 GB) while retaining many of the functional benefits of regular VARCHAR columns.
Oracle SQL Data Types
Oracle’s database has a long history and some unique data type implementations that developers should be aware of.
- VARCHAR2(n): This is the primary variable-length string type in Oracle. While Oracle also has a VARCHAR type, its use is discouraged, and VARCHAR2 should always be used for string data to ensure consistent behavior.
- NUMBER: A highly versatile, all-in-one numeric type. It can be defined as NUMBER(p,s) to act as a fixed-point decimal (like DECIMAL), NUMBER(p) to act as an integer, or simply NUMBER to store floating-point values.
- DATE: This is a common point of confusion. Unlike the standard SQL DATE type which stores only the date, Oracle’s DATE type always includes both date and time components, making it functionally equivalent to DATETIME in other databases.
- CLOB/BLOB/NCLOB: Oracle uses a well-defined family of “Large Object” (LOB) types for storing large amounts of data: BLOB for binary, CLOB for character (text), and NCLOB for national character (Unicode) text.
Type casting, or data type conversion, is the process of converting a value from one data type to another. This is a frequent necessity in SQL when you need to compare different data types in a query, format data for display, or perform calculations on data that might be stored as text.
There are two forms of conversion: implicit (automatic) and explicit (manual).
-
Implicit Conversion: This is when the database automatically converts a data type for you behind the scenes. For example, in the query WHERE my_integer_column = ‘123’, the database will implicitly convert the string ‘123’ to an integer to perform the comparison. While convenient, relying on implicit conversion is often considered bad practice as it can lead to unexpected results or performance issues.
-
Explicit Conversion: This is when you manually use a function to convert a data type. This is the recommended approach because it makes your code’s intent perfectly clear and ensures the conversion is predictable and reliable. The two main functions for this are CAST() and CONVERT():
-
CAST(): The CAST() function is the ANSI SQL standard, which means it is available in almost all database systems, including MySQL, PostgreSQL, and SQL Server. This makes it the most portable and widely used conversion function. The syntax is:
CAST(expression AS target_type)
-
CONVERT(): The CONVERT() function is primarily used in SQL Server and provides more flexibility than CAST(), especially for formatting dates and times. The syntax is:
CONVERT(target_type, expression, [style_code])
-
The optional style_code is a number that specifies the output format for date/time or numeric-to-string conversions.
In database design, smaller is often better. While modern storage is relatively cheap, the size of your data types has a profound impact that goes far beyond just disk space. It directly affects query speed, memory usage, and overall application performance. Here’s why and how to choose the most efficient size for your data.
1. Use the Smallest Type That Safely Fits (The “Goldilocks Rule”)
This is the most fundamental rule. Don’t default to INT if a TINYINT will do.
- For Integers:
- User’s Age: A person’s age will realistically not exceed 200. A TINYINT UNSIGNED (range 0-255) is perfect and uses only 1 byte. Using an INT (4 bytes) is wasteful.
- User ID: An INT (up to 2.1 billion) is sufficient for the vast majority of applications. Do not default to a BIGINT (up to 9 quintillion). A BIGINT uses double the storage (8 bytes) of an INT, which adds up across millions of rows and foreign keys.
- For Strings:
- Email Address: While emails can be long, they have defined limits. A VARCHAR(255) is a safe, standard choice. There is no need for VARCHAR(4000).
- Status Flags: For a status that can be ‘active’, ‘pending’, or ‘deleted’, don’t use a VARCHAR(20). Use an ENUM in MySQL or a foreign key to a lookup table with a TINYINT ID.
2. Understand Your Data
Before choosing a type, understand the domain of your data. Ask questions: Will this number ever be negative? If not, consider UNSIGNED in MySQL. Will it have fractional parts? Then you need DECIMAL. What is the absolute maximum length a product SKU could ever be? For example, consider US Zip Codes. It’s tempting to use INT for a Zip code, but this is a classic mistake. US Zip codes can have leading zeros (e.g., 07660), which would be lost if stored as a number. The correct type is CHAR(5), which preserves the format and uses a predictable amount of space.
3. Plan for the Future, But Be Realistic
It’s wise to plan for growth, but don’t over-engineer.
- Reasonable Future-Proofing: Choosing INT over SMALLINT for a primary key is often a smart, long-term decision. The storage difference is minimal (2 bytes) and it gives you plenty of room to grow.
- Unnecessary Over-Engineering: Automatically using NVARCHAR for all text fields “just in case” you need to support Unicode later is wasteful if your application has a clear, defined scope for English-only content. NVARCHAR uses double the storage of VARCHAR. Make conscious decisions based on requirements.
4. Understand the CHAR vs. VARCHAR Trade-off
- CHAR(n): Use this only when the data is always a fixed length. For example, use CHAR(2) for two-letter country codes, CHAR(36) for UUIDs stored as strings. It will pad shorter entries with spaces, which can be wasteful.
- VARCHAR(n): This is the correct choice for almost all other text. It only stores the characters you enter plus a small 1 or 2-byte overhead for the length, making it highly efficient for variable-length data.
1. What are the main categories of data types in SQL?
SQL data types can be grouped into several logical categories based on the kind of data they store. The primary categories are:
- Numeric Types: These are for any data that is purely mathematical. This category includes integers for whole numbers (like INT and BIGINT) and decimal/floating-point types for numbers with fractional parts (like DECIMAL and FLOAT). You use them for things like IDs, quantities, and prices.
- Character/String Types: This category is for text data. It includes fixed-length types (CHAR) for predictable strings like state codes, and variable-length types (VARCHAR) for most other text where the length can change, like names and titles. For very long text, such as comments or articles, you would use a large object type like TEXT.
- Date and Time Types: These are used to store temporal information, answering the question of “when” an event occurred. This ranges from just the date (DATE) for things like a birthday, to a specific time (TIME), to a combination of both (DATETIME or TIMESTAMP) for logging exact moments.
- Binary Types: These types store raw binary data, which is a sequence of bytes. This is not human-readable text but is used for storing files, images, or other objects directly in the database. Common types include BINARY, VARBINARY, and BLOB (Binary Large Object).
- Miscellaneous/Specialized Types: As databases have evolved, they’ve added types for modern data structures. This includes BOOLEAN for true/false states, JSON for storing data from web APIs, XML for structured documents, and spatial types for geographical data.
2. What is the difference between VARCHAR and TEXT in SQL?
The difference comes down to three key areas: specified length, storage, and intended use.
- VARCHAR (Variable Character): When you define a VARCHAR column, you must specify a maximum length (e.g., VARCHAR(255)). The database will enforce this limit, preventing larger values from being inserted. It’s ideal for text with a known and reasonable maximum length, like usernames, email addresses, or city names. VARCHAR data is typically stored “in-line” with the other table data, which makes it very fast to access.
- TEXT: You do not specify a maximum length for a TEXT column, as it’s designed for long-form, unpredictable text. Because it can hold very large amounts of data (megabytes or even gigabytes), databases often store it in a separate location from the rest of the row’s data. This can result in a minor performance overhead for retrieval compared to VARCHAR. It’s the right choice for blog post content, lengthy descriptions, and user-generated comments. In short, use VARCHAR when you can confidently define a maximum length and require the fastest possible access. Use TEXT when the data is too long to fit in a VARCHAR or its length is completely unknown.
3. How do SQL data types differ between MySQL and PostgreSQL?
While both database systems support all the standard SQL types, they each offer unique “flavors” and extensions that cater to different needs.
- PostgreSQL is known for its comprehensive data type support and strong compliance with SQL standards. It offers powerful options not found in MySQL, such as:
- ARRAY: A column can store a list of values (e.g., an array of integers or strings) in a single cell.
- UUID: A native data type for storing 128-bit universally unique identifiers, which is excellent for primary keys in distributed systems.
- JSONB: A binary, indexed version of JSON that is highly efficient for storing and querying complex JSON documents.
- A true BOOLEAN type that only accepts true or false.
- MySQL provides some convenient, user-friendly data types that are very popular:
- ENUM: Allows you to define a column with a list of permitted string values (e.g., status ENUM(‘active’, ‘inactive’)). It’s very efficient as it stores these values as small numbers internally.
- SET: Similar to ENUM, but a column can hold multiple values from the predefined list.
- UNSIGNED: MySQL makes it very easy to declare numeric types as UNSIGNED, preventing negative values and doubling the positive range. For example, an UNSIGNED TINYINT has a range of 0 to 255, instead of -128 to 127.
4. What is the default size of INT in SQL?
Across virtually all modern SQL databases (including MySQL, PostgreSQL, and SQL Server), an INT or INTEGER data type is a 4-byte (32-bit) integer.
- A byte consists of 8 bits, so 4 bytes give you 32 bits of storage. This can represent 2^32 (or 4,294,967,296) distinct values.
- For a standard signed INT, these values are split between positive and negative numbers, giving it a range from -2,147,483,648 to 2,147,483,647. This is the default behavior.
- In systems that support it, like MySQL, an UNSIGNED INT uses all 32 bits for positive numbers, giving it a range from 0 to 4,294,967,295.
This article provided a complete overview of SQL data types, starting with their fundamental impact on performance, integrity, and storage. We covered every major category in detail, from numeric and string types to specialized ones like JSON and UUID. We also explored the critical differences and unique features across popular database systems, including MySQL, PostgreSQL, SQL Server, and Oracle. To complete your understanding, we delved into advanced skills such as type casting with CAST() and storage optimization best practices, giving you a well-rounded and practical foundation.
Understanding data types is a huge step in mastering SQL. To continue building your skills and putting your new knowledge into practice, refer to the following tutorials: