Skip to content

Commit 33e3819

Browse files
committed
docs: how to alter column type postgres
1 parent 02762f5 commit 33e3819

File tree

2 files changed

+295
-2
lines changed

2 files changed

+295
-2
lines changed

content/docs/_layout.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -281,10 +281,10 @@ expand_section_list: ['Self-host']
281281

282282
### [Schema Definition Language](/reference/schema-definition-language)
283283

284-
### [PostgreSQL SQL Review Guide](/how-to/sql-review/postgres-sql-review-guide)
285-
286284
### Postgres
287285

286+
### [PostgreSQL SQL Review Guide](/how-to/sql-review/postgres-sql-review-guide)
287+
288288
#### [How to Check Postgres version](/how-to/postgres/how-to-check-postgres-version)
289289

290290
#### [How to Fix Permission denied for table](/how-to/postgres/permission-denied-for-table-postgres)
@@ -293,6 +293,8 @@ expand_section_list: ['Self-host']
293293

294294
#### [How to Create Index in Postgres](/how-to/postgres/how-to-create-index-postgres)
295295

296+
#### [How to Alter Column Type in Postgres](/how-to/postgres/how-to-alter-column-type-postgres)
297+
296298
### ClickHouse
297299

298300
#### [How to Create a Database](/how-to/clickhouse/how-to-create-a-database-clickhouse)
Lines changed: 291 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,291 @@
1+
---
2+
title: How to Alter Column Type in Postgres
3+
updated_at: 2025/02/28 09:00:00
4+
---
5+
6+
_Official documentation: [ALTER TABLE](https://www.postgresql.org/docs/current/sql-altertable.html)_
7+
8+
<HintBlock type="info">
9+
10+
Changing column type should be conducted with caution. Some organizations have strict approval process and even disallow altering column type at all. You can enforce [approval process](/docs/administration/custom-approval/) or [disallowing altering column type](/docs/sql-review/review-rules/#column.disallow-change-type) via Bytebase.
11+
12+
</HintBlock>
13+
14+
## Simple Type Conversions
15+
16+
For straightforward conversions that don't require data transformation:
17+
18+
```sql
19+
-- Change an integer column to bigint
20+
ALTER TABLE orders
21+
ALTER COLUMN order_id
22+
TYPE bigint;
23+
24+
-- Change a varchar column to text
25+
ALTER TABLE customers
26+
ALTER COLUMN notes
27+
TYPE text;
28+
29+
-- Change a float column to numeric with precision
30+
ALTER TABLE products
31+
ALTER COLUMN price
32+
TYPE numeric(10,2);
33+
```
34+
35+
## Using USING Clause for Data Transformation
36+
37+
When the conversion requires transformation, use the `USING` clause:
38+
39+
```sql
40+
-- Convert text to integer
41+
ALTER TABLE employees
42+
ALTER COLUMN age
43+
TYPE integer USING (age::integer);
44+
45+
-- Convert string to date
46+
ALTER TABLE events
47+
ALTER COLUMN event_date
48+
TYPE date USING (event_date::date);
49+
50+
-- Convert string to timestamp
51+
ALTER TABLE logs
52+
ALTER COLUMN created_at
53+
TYPE timestamp USING (created_at::timestamp);
54+
55+
-- Complex transformation with conditional logic
56+
ALTER TABLE users
57+
ALTER COLUMN status
58+
TYPE boolean USING (CASE WHEN status = 'active' THEN TRUE ELSE FALSE END);
59+
```
60+
61+
## Converting Between Text Types
62+
63+
```sql
64+
-- varchar to text (no data loss)
65+
ALTER TABLE messages
66+
ALTER COLUMN content
67+
TYPE text;
68+
69+
-- text to varchar with potential truncation
70+
ALTER TABLE products
71+
ALTER COLUMN description
72+
TYPE varchar(255) USING substring(description, 1, 255);
73+
```
74+
75+
## Converting Numeric Types
76+
77+
```sql
78+
-- integer to bigint (safe, no data loss)
79+
ALTER TABLE measurements
80+
ALTER COLUMN value
81+
TYPE bigint;
82+
83+
-- decimal to integer (truncation of fractional part)
84+
ALTER TABLE products
85+
ALTER COLUMN price
86+
TYPE integer USING (price::integer);
87+
88+
-- float to numeric (fixed precision)
89+
ALTER TABLE financial
90+
ALTER COLUMN amount
91+
TYPE numeric(15,2) USING (amount::numeric(15,2));
92+
```
93+
94+
## Date and Time Conversions
95+
96+
```sql
97+
-- timestamp to date (drops time portion)
98+
ALTER TABLE events
99+
ALTER COLUMN event_timestamp
100+
TYPE date USING (event_timestamp::date);
101+
102+
-- date to timestamp (adds 00:00:00 time)
103+
ALTER TABLE appointments
104+
ALTER COLUMN appointment_date
105+
TYPE timestamp USING (appointment_date::timestamp);
106+
107+
-- timestamp to timestamptz (applies server timezone)
108+
ALTER TABLE logs
109+
ALTER COLUMN log_time
110+
TYPE timestamptz USING (log_time::timestamptz);
111+
```
112+
113+
## UUID and Identifier Conversions
114+
115+
```sql
116+
-- Convert text to UUID
117+
ALTER TABLE sessions
118+
ALTER COLUMN session_id
119+
TYPE uuid USING (session_id::uuid);
120+
121+
-- Convert integer to UUID (requires custom function)
122+
CREATE OR REPLACE FUNCTION int_to_uuid(i integer) RETURNS uuid AS $$
123+
BEGIN
124+
RETURN ('00000000-0000-0000-0000-' || lpad(i::text, 12, '0'))::uuid;
125+
END;
126+
$$ LANGUAGE plpgsql;
127+
128+
ALTER TABLE legacy_users
129+
ALTER COLUMN user_id
130+
TYPE uuid USING int_to_uuid(user_id);
131+
```
132+
133+
## Array Type Conversions
134+
135+
```sql
136+
-- Convert text to string array using delimiters
137+
ALTER TABLE products
138+
ALTER COLUMN tags
139+
TYPE text[] USING string_to_array(tags, ',');
140+
141+
-- Convert array element types
142+
ALTER TABLE measurements
143+
ALTER COLUMN values
144+
TYPE numeric[] USING (SELECT array_agg(v::numeric) FROM unnest(values) AS v);
145+
```
146+
147+
## JSON/JSONB Conversions
148+
149+
```sql
150+
-- Convert text to JSON
151+
ALTER TABLE api_responses
152+
ALTER COLUMN response
153+
TYPE json USING (response::json);
154+
155+
-- Convert JSON to JSONB
156+
ALTER TABLE configurations
157+
ALTER COLUMN config
158+
TYPE jsonb USING (config::jsonb);
159+
160+
-- Convert JSONB to text
161+
ALTER TABLE archived_data
162+
ALTER COLUMN data
163+
TYPE text USING (data::text);
164+
```
165+
166+
## Handling Special Cases
167+
168+
### Converting NULL Values
169+
170+
```sql
171+
-- Set default value for NULLs during conversion
172+
ALTER TABLE users
173+
ALTER COLUMN last_login
174+
TYPE timestamp USING (COALESCE(last_login::timestamp, '1970-01-01'::timestamp));
175+
```
176+
177+
### Converting with Length Constraints
178+
179+
```sql
180+
-- Handle possible truncation with warning
181+
DO $$
182+
DECLARE
183+
over_length INTEGER;
184+
BEGIN
185+
SELECT COUNT(*) INTO over_length
186+
FROM products
187+
WHERE LENGTH(description) > 100;
188+
189+
IF over_length > 0 THEN
190+
RAISE WARNING 'Warning: % rows will have data truncated', over_length;
191+
END IF;
192+
END $$;
193+
194+
ALTER TABLE products
195+
ALTER COLUMN description
196+
TYPE varchar(100) USING substring(description, 1, 100);
197+
```
198+
199+
## Performance Considerations
200+
201+
### Using Transactions
202+
203+
For large tables, wrap the alteration in a transaction:
204+
205+
```sql
206+
BEGIN;
207+
-- Check if the conversion is safe
208+
-- ...
209+
ALTER TABLE large_table
210+
ALTER COLUMN data
211+
TYPE new_type USING (data::new_type);
212+
COMMIT;
213+
```
214+
215+
### Low-Impact Approaches for Production
216+
217+
For large tables in production, you might prefer multi-step approach:
218+
219+
```sql
220+
-- 1. Add a new column
221+
ALTER TABLE large_table ADD COLUMN new_column new_type;
222+
223+
-- 2. Update data in batches
224+
DO $$
225+
DECLARE
226+
batch_size INTEGER := 10000;
227+
max_id INTEGER;
228+
current_id INTEGER := 0;
229+
BEGIN
230+
SELECT MAX(id) INTO max_id FROM large_table;
231+
WHILE current_id < max_id LOOP
232+
EXECUTE 'UPDATE large_table
233+
SET new_column = old_column::new_type
234+
WHERE id > $1 AND id <= $2'
235+
USING current_id, current_id + batch_size;
236+
237+
current_id := current_id + batch_size;
238+
COMMIT;
239+
END LOOP;
240+
END $$;
241+
242+
-- 3. Add constraints if needed
243+
ALTER TABLE large_table ALTER COLUMN new_column SET NOT NULL;
244+
245+
-- 4. Drop the old column when ready
246+
ALTER TABLE large_table DROP COLUMN old_column;
247+
248+
-- 5. Rename the new column to the old name
249+
ALTER TABLE large_table RENAME COLUMN new_column TO old_column;
250+
```
251+
252+
## Common Errors and Solutions
253+
254+
### "cannot cast type X to Y"
255+
256+
```sql
257+
-- Use explicit conversion function instead
258+
ALTER TABLE products
259+
ALTER COLUMN code
260+
TYPE uuid USING uuid_generate_v5(uuid_ns_url(), code);
261+
```
262+
263+
### "value too long for type character varying(N)"
264+
265+
```sql
266+
-- Check and handle long values first
267+
UPDATE table_name
268+
SET column_name = substring(column_name, 1, 50)
269+
WHERE LENGTH(column_name) > 50;
270+
271+
-- Then alter the type
272+
ALTER TABLE table_name
273+
ALTER COLUMN column_name
274+
TYPE varchar(50);
275+
```
276+
277+
### "operator does not exist: X = Y"
278+
279+
```sql
280+
-- Create a custom cast or use an explicit function
281+
CREATE FUNCTION custom_text_to_uuid(text) RETURNS uuid AS $$
282+
SELECT CASE
283+
WHEN $1 ~ '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$' THEN $1::uuid
284+
ELSE uuid_nil()
285+
END;
286+
$$ LANGUAGE SQL;
287+
288+
ALTER TABLE items
289+
ALTER COLUMN item_id
290+
TYPE uuid USING custom_text_to_uuid(item_id);
291+
```

0 commit comments

Comments
 (0)