UPDATE en Python

Actualizar datos existentes en MySQL desde Python es una operación que realizarás frecuentemente en cualquier aplicación. El conector mysql-connector-python permite ejecutar sentencias UPDATE parametrizadas de forma segura, verificar cuántas filas se modificaron y controlar las transacciones de manera explícita. En este artículo aprenderás las diferentes técnicas para actualizar registros de forma efectiva.

Requisitos previos

Necesitas una conexión configurada y la tabla productos con datos de prueba. Si seguiste los artículos anteriores, ya deberías tener todo preparado.

Código completo

Este ejemplo actualiza el precio y stock de un producto específico:

import mysql.connector
 
def actualizar_producto():
    conexion = mysql.connector.connect(
        host='localhost',
        user='root',
        password='tu_contraseña',
        database='tienda'
    )
 
    cursor = conexion.cursor()
    cursor.execute(
        'UPDATE productos SET precio = %s, stock = %s WHERE id = %s',
        (13499.99, 30, 1)
    )
    conexion.commit()
 
    print(f'Filas afectadas: {cursor.rowcount}')
 
    cursor.close()
    conexion.close()
 
actualizar_producto()

Salida esperada:

Filas afectadas: 1

Explicación paso a paso

El resultado de una sentencia UPDATE se refleja en la propiedad cursor.rowcount, que indica cuántas filas fueron afectadas por la operación. Si el valor es 0, significa que ninguna fila cumplió la condición WHERE, lo que normalmente indica que el registro no existe o ya fue eliminado.

A diferencia de Node.js, el conector de Python no distingue entre filas encontradas y filas realmente cambiadas en rowcount. Sin embargo, puedes activar la opción client_flags con CLIENT.FOUND_ROWS para que rowcount devuelva el número de filas encontradas en lugar de las cambiadas:

from mysql.connector.constants import ClientFlag
 
conexion = mysql.connector.connect(
    host='localhost',
    user='root',
    password='tu_contraseña',
    database='tienda',
    client_flags=[ClientFlag.FOUND_ROWS]
)

Verificar si la actualización tuvo efecto

def actualizar_precio(producto_id, nuevo_precio):
    conexion = mysql.connector.connect(
        host='localhost',
        user='root',
        password='tu_contraseña',
        database='tienda'
    )
 
    cursor = conexion.cursor()
    cursor.execute(
        'UPDATE productos SET precio = %s WHERE id = %s',
        (nuevo_precio, producto_id)
    )
    conexion.commit()
 
    if cursor.rowcount == 0:
        print(f'No se encontró el producto con ID {producto_id}')
        resultado = False
    else:
        print(f'Precio del producto {producto_id} actualizado a ${nuevo_precio}')
        resultado = True
 
    cursor.close()
    conexion.close()
    return resultado
 
actualizar_precio(1, 12499.99)
actualizar_precio(999, 100.00)

Salida esperada:

Precio del producto 1 actualizado a $12499.99
No se encontró el producto con ID 999

Actualización dinámica

Cuando el usuario puede modificar solo algunos campos, necesitas construir la consulta dinámicamente:

def actualizar_campos(producto_id, **campos):
    columnas_permitidas = {'nombre', 'categoria', 'precio', 'stock', 'activo'}
    campos_validos = {k: v for k, v in campos.items() if k in columnas_permitidas}
 
    if not campos_validos:
        print('No se proporcionaron campos válidos para actualizar')
        return False
 
    conexion = mysql.connector.connect(
        host='localhost',
        user='root',
        password='tu_contraseña',
        database='tienda'
    )
 
    set_clause = ', '.join(f'{campo} = %s' for campo in campos_validos)
    valores = list(campos_validos.values()) + [producto_id]
 
    cursor = conexion.cursor()
    cursor.execute(
        f'UPDATE productos SET {set_clause} WHERE id = %s',
        tuple(valores)
    )
    conexion.commit()
 
    modificado = cursor.rowcount > 0
    print(f'Campos actualizados: {list(campos_validos.keys())}' if modificado
          else 'Producto no encontrado')
 
    cursor.close()
    conexion.close()
    return modificado
 
# Actualizar solo precio y stock
actualizar_campos(1, precio=11999.99, stock=50)
 
# Intentar actualizar con campo no permitido
actualizar_campos(1, precio=10000, campo_malicioso='valor')

Salida esperada:

Campos actualizados: ['precio', 'stock']
Campos actualizados: ['precio']

Actualización con parámetros nombrados

El conector permite usar parámetros con nombre para mayor claridad:

def actualizar_con_nombres(producto_id, datos):
    conexion = mysql.connector.connect(
        host='localhost',
        user='root',
        password='tu_contraseña',
        database='tienda'
    )
 
    cursor = conexion.cursor()
    cursor.execute(
        'UPDATE productos SET nombre = %(nombre)s, precio = %(precio)s '
        'WHERE id = %(id)s',
        {'nombre': datos['nombre'], 'precio': datos['precio'], 'id': producto_id}
    )
    conexion.commit()
 
    print(f'Filas actualizadas: {cursor.rowcount}')
    cursor.close()
    conexion.close()
 
actualizar_con_nombres(2, {'nombre': 'Mouse Logitech MX Master 3S', 'precio': 1799.00})

Caso práctico

Veamos un caso real: una función para actualizar inventario después de recibir mercancía, con verificación y registro de auditoría:

import mysql.connector
from datetime import datetime
 
def recibir_mercancia(movimientos):
    """
    Actualiza el stock de múltiples productos al recibir mercancía.
    movimientos: lista de diccionarios {'producto_id': int, 'cantidad': int}
    """
    conexion = mysql.connector.connect(
        host='localhost',
        user='root',
        password='tu_contraseña',
        database='tienda',
        autocommit=False
    )
 
    try:
        cursor = conexion.cursor(dictionary=True)
        actualizados = 0
        no_encontrados = []
 
        for mov in movimientos:
            # Verificar que el producto existe
            cursor.execute(
                'SELECT id, nombre, stock FROM productos WHERE id = %s',
                (mov['producto_id'],)
            )
            producto = cursor.fetchone()
 
            if not producto:
                no_encontrados.append(mov['producto_id'])
                continue
 
            stock_anterior = producto['stock']
            nuevo_stock = stock_anterior + mov['cantidad']
 
            # Actualizar stock
            cursor.execute(
                'UPDATE productos SET stock = %s WHERE id = %s',
                (nuevo_stock, mov['producto_id'])
            )
 
            # Registrar en historial
            cursor.execute(
                'INSERT INTO historial_inventario '
                '(producto_id, tipo, cantidad, stock_anterior, stock_nuevo, fecha) '
                'VALUES (%s, %s, %s, %s, %s, NOW())',
                (mov['producto_id'], 'entrada', mov['cantidad'],
                 stock_anterior, nuevo_stock)
            )
 
            print(f"  {producto['nombre']}: {stock_anterior} -> {nuevo_stock} (+{mov['cantidad']})")
            actualizados += 1
 
        conexion.commit()
 
        print(f'\nResumen: {actualizados} productos actualizados')
        if no_encontrados:
            print(f'No encontrados: {no_encontrados}')
 
    except Exception as error:
        conexion.rollback()
        print(f'Error: {error}')
 
    finally:
        cursor.close()
        conexion.close()
 
# Uso
recibir_mercancia([
    {'producto_id': 1, 'cantidad': 10},
    {'producto_id': 2, 'cantidad': 50},
    {'producto_id': 3, 'cantidad': 25}
])

Salida esperada:

  Laptop HP Pavilion: 25 -> 35 (+10)
  Mouse Logitech MX Master: 150 -> 200 (+50)
  Teclado Mecánico Corsair K70: 80 -> 105 (+25)

Resumen: 3 productos actualizados

Manejo de errores

Las actualizaciones pueden fallar por violaciones de restricciones o problemas de concurrencia:

from mysql.connector import Error, errorcode
 
def actualizar_seguro(conexion, producto_id, datos):
    try:
        cursor = conexion.cursor()
        cursor.execute(
            'UPDATE productos SET nombre = %s, precio = %s WHERE id = %s',
            (datos.get('nombre'), datos.get('precio'), producto_id)
        )
        conexion.commit()
 
        if cursor.rowcount == 0:
            return {'error': 'NOT_FOUND', 'mensaje': 'Producto no encontrado'}
 
        return {'exito': True}
 
    except Error as error:
        conexion.rollback()
        if error.errno == errorcode.ER_DUP_ENTRY:
            return {'error': 'DUPLICADO', 'mensaje': 'Ya existe un producto con ese nombre'}
        elif error.errno == errorcode.ER_DATA_TOO_LONG:
            return {'error': 'LONGITUD', 'mensaje': 'El nombre excede el límite de caracteres'}
        elif error.errno == errorcode.ER_LOCK_WAIT_TIMEOUT:
            return {'error': 'TIMEOUT', 'mensaje': 'El registro está siendo modificado por otro proceso'}
        else:
            return {'error': 'INTERNO', 'mensaje': f'Error: {error.msg}'}
 
    finally:
        cursor.close()

Ahora que conoces las técnicas para actualizar registros, en el siguiente artículo aprenderás a eliminar datos de MySQL desde Python.

Escrito por Eduardo Lázaro