Skip to content

Latest commit

 

History

History
807 lines (609 loc) · 23.8 KB

File metadata and controls

807 lines (609 loc) · 23.8 KB

Работа с MS Access через Python + COM

Полное руководство по автоматизации MS Access


Содержание

  1. Обзор технологии
  2. Установка и настройка
  3. Базовые операции
  4. Создание таблиц
  5. Работа с данными
  6. Создание связей
  7. Создание запросов
  8. Создание форм
  9. Работа с VBA кодом
  10. Типичные ошибки и решения
  11. Лучшие практики
  12. Полный пример

Обзор технологии

Что используется

  • Python 3.x - основной язык программирования
  • pywin32 (win32com) - библиотека для работы с COM объектами Windows
  • MS Access - целевое приложение (Office 2007+)

Принцип работы

Python через COM (Component Object Model) управляет приложением MS Access как если бы пользователь работал вручную. Мы получаем доступ к объектной модели Access и можем:

  • Создавать/открывать базы данных
  • Выполнять SQL команды
  • Создавать таблицы, запросы, формы, отчёты
  • Управлять VBA кодом
  • Экспортировать/импортировать объекты

Установка и настройка

Установка pywin32

pip install pywin32

Импорты

import win32com.client
import os

Решение проблемы с кодировкой в Windows консоли

import sys
import io
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace')

Важно: Без этого Unicode символы (✓, ─, и т.д.) вызовут ошибку UnicodeEncodeError в консоли Windows с кодировкой cp1251.


Базовые операции

Создание объекта Access Application

access = win32com.client.Dispatch("Access.Application")
access.Visible = False  # True для отладки

Создание новой базы данных

DB_PATH = r"C:\path\to\database.accdb"
access.NewCurrentDatabase(DB_PATH)

Открытие существующей базы данных

access.OpenCurrentDatabase(DB_PATH)

Закрытие базы данных

access.CloseCurrentDatabase()
access.Quit()

Важно: Всегда закрывайте Access в блоке finally или используйте try/except, иначе процесс MSACCESS.EXE останется висеть в памяти.

Принудительное завершение Access (если завис)

taskkill /F /IM MSACCESS.EXE

Создание таблиц

Через SQL (рекомендуемый способ)

sql = """
CREATE TABLE Товары (
    КодТовара COUNTER PRIMARY KEY,
    Наименование TEXT(100) NOT NULL,
    Количество DOUBLE,
    Цена CURRENCY,
    ДатаПоступления DATETIME
);
"""
access.CurrentDb().Execute(sql)

Типы данных Access SQL

Тип Access SQL синтаксис Описание
Счётчик COUNTER или AUTOINCREMENT Автоинкремент
Текст TEXT(n) или VARCHAR(n) Строка до 255 символов
Поле МЕМО MEMO или LONGTEXT Длинный текст
Числовой LONG, SHORT, DOUBLE, SINGLE Числа
Денежный CURRENCY Денежный формат
Дата/время DATETIME Дата и время
Логический YESNO или BIT Да/Нет

Квадратные скобки для имён с пробелами/кириллицей

sql = "CREATE TABLE [Мои Данные] ([Код записи] COUNTER PRIMARY KEY);"

Работа с данными

INSERT

# Важно: даты в формате #MM/DD/YYYY#
from datetime import datetime

date = datetime(2024, 6, 15)
date_str = date.strftime("#%m/%d/%Y#")

sql = f"""
INSERT INTO Товары (Наименование, Количество, Цена, ДатаПоступления)
VALUES ('Нарзан 0.5л', 100, 45.50, {date_str});
"""
access.CurrentDb().Execute(sql)

SELECT (чтение данных)

rs = access.CurrentDb().OpenRecordset("SELECT * FROM Товары")
while not rs.EOF:
    print(rs.Fields("Наименование").Value)
    rs.MoveNext()
rs.Close()

UPDATE

sql = "UPDATE Товары SET Цена = 50.00 WHERE КодТовара = 1;"
access.CurrentDb().Execute(sql)

DELETE

sql = "DELETE FROM Товары WHERE КодТовара = 1;"
access.CurrentDb().Execute(sql)

Создание связей

Программное создание связи один-ко-многим

# Создаём связь между таблицами "Товары" и "Продажи"
relation = access.CurrentDb().CreateRelation(
    "rel_Товары_Продажи",  # Имя связи
    "Товары",              # Главная таблица
    "Продажи",             # Подчинённая таблица
    0                      # Атрибуты (0 = без каскадного удаления)
)

# Добавляем поле связи
field = relation.CreateField("КодТовара")
field.ForeignName = "КодТовара"
relation.Fields.Append(field)

# Добавляем связь в базу
access.CurrentDb().Relations.Append(relation)

Атрибуты связей

Значение Константа Описание
0 - Без каскадных операций
1 dbRelationUnique Связь один-к-одному
2 dbRelationDontEnforce Не обеспечивать целостность
256 dbRelationInherited Наследуемая связь
4096 dbRelationUpdateCascade Каскадное обновление
8192 dbRelationDeleteCascade Каскадное удаление

Создание запросов

Создание QueryDef

query_sql = """
SELECT Товары.Наименование, Продажи.Дата, Продажи.Количество,
       [Количество]*[Цена] AS Сумма
FROM Товары INNER JOIN Продажи ON Товары.КодТовара = Продажи.КодТовара;
"""

qd = access.CurrentDb().CreateQueryDef("Запрос_Продажи", query_sql.strip())

Параметрические запросы

# Параметр в квадратных скобках с двоеточием
query_sql = """
SELECT * FROM Товары
WHERE Наименование LIKE [Введите название:] & '*';
"""
access.CurrentDb().CreateQueryDef("Запрос_Поиск", query_sql)

При выполнении такого запроса Access покажет диалог ввода параметра.


Создание форм

Константы Access для работы с формами

acForm = 2
acTable = 0
acQuery = 1
acTextBox = 109
acLabel = 100
acCommandButton = 104
acComboBox = 111
acLine = 102
acDetail = 0      # Секция "Область данных"
acHeader = 1      # Заголовок формы
acFooter = 2      # Примечание формы
acSaveYes = 1
acDesign = 1      # Режим конструктора
acNormal = 0      # Обычный режим

Создание пустой формы

form = access.CreateForm()
form_name = form.Name  # Автоматически сгенерированное имя

# Привязка к таблице
form.RecordSource = "Товары"

# Настройки формы
form.Caption = "Форма товаров"
form.DefaultView = 0      # Single Form
form.NavigationButtons = True
form.RecordSelectors = True
form.AutoCenter = True

# Настройка секции
section = form.Section(acDetail)
section.Height = 5000     # В твипах (1440 твипов = 1 дюйм)
section.BackColor = 0xFFF8F0  # Цвет в формате BGR

Добавление элементов управления

# Функция для установки свойств (обход ограничений COM)
def set_prop(ctrl, prop_name, value):
    try:
        ctrl.Properties(prop_name).Value = value
    except:
        pass

# Создание Label
label = access.CreateControl(
    form_name,      # Имя формы
    acLabel,        # Тип элемента
    acDetail,       # Секция
    "",             # Родительский элемент
    "",             # Источник данных
    100,            # Left (твипы)
    100,            # Top (твипы)
    2000,           # Width (твипы)
    300             # Height (твипы)
)
set_prop(label, "Caption", "Наименование:")
set_prop(label, "FontSize", 10)

# Создание TextBox (связанного с полем)
textbox = access.CreateControl(
    form_name,
    acTextBox,
    acDetail,
    "",
    "Наименование",  # Имя поля из RecordSource
    2200,
    100,
    4000,
    300
)
set_prop(textbox, "FontSize", 10)
set_prop(textbox, "BackColor", 0xFFFFFF)

# Создание ComboBox
combo = access.CreateControl(form_name, acComboBox, acDetail, "", "КодТовара", 2200, 500, 4000, 300)
set_prop(combo, "RowSourceType", "Table/Query")
set_prop(combo, "RowSource", "SELECT КодТовара, Наименование FROM Товары;")
set_prop(combo, "ColumnCount", 2)
set_prop(combo, "ColumnWidths", "500;3000")
set_prop(combo, "BoundColumn", 1)

# Создание CommandButton
button = access.CreateControl(form_name, acCommandButton, acDetail, "", "", 100, 1000, 1500, 400)
set_prop(button, "Name", "cmdSave")
set_prop(button, "Caption", "Сохранить")
set_prop(button, "BackColor", 0xADD8E6)

Сохранение и переименование формы

# Закрываем с сохранением
access.DoCmd.Close(acForm, form_name, acSaveYes)

# Переименовываем
access.DoCmd.Rename("Форма_Товары", acForm, form_name)

Работа с VBA кодом

Доступ к VBE (Visual Basic Editor)

vbe = access.VBE
project = vbe.VBProjects(1)  # Первый (обычно единственный) проект

Типы компонентов VBA

Type Описание
1 Стандартный модуль (Module)
2 Модуль класса (Class Module)
3 Форма UserForm
100 Документ (форма/отчёт Access)

Создание стандартного модуля

module = project.VBComponents.Add(1)  # 1 = стандартный модуль
module.Name = "modUtils"

vba_code = '''
Option Compare Database
Option Explicit

Public Sub TestSub()
    MsgBox "Hello!"
End Sub
'''
module.CodeModule.AddFromString(vba_code)

Добавление кода в модуль формы (ВАЖНО!)

Для работы кнопок код должен быть в модуле самой формы, а не в отдельном модуле.

form_name = "Форма_Товары"

# Открываем форму в режиме конструктора
access.DoCmd.OpenForm(form_name, acDesign)

# Включаем модуль формы
frm = access.Forms(form_name)
frm.HasModule = True

# Находим компонент формы в VBE
vbe = access.VBE
proj = vbe.VBProjects(1)

for j in range(1, proj.VBComponents.Count + 1):
    comp = proj.VBComponents(j)
    if comp.Name == "Form_" + form_name:  # Внимание: префикс "Form_"
        code_module = comp.CodeModule

        # Очищаем существующий код
        if code_module.CountOfLines > 0:
            code_module.DeleteLines(1, code_module.CountOfLines)

        # Добавляем новый код
        vba_code = '''Option Compare Database

Private Sub cmdSave_Click()
    On Error Resume Next
    DoCmd.RunCommand acCmdSaveRecord
End Sub

Private Sub cmdNew_Click()
    On Error Resume Next
    DoCmd.GoToRecord , , acNewRec
End Sub
'''
        code_module.AddFromString(vba_code)
        break

# Сохраняем форму
access.DoCmd.Close(acForm, form_name, acSaveYes)

Привязка события к кнопке

# Открываем форму в режиме конструктора
access.DoCmd.OpenForm(form_name, acDesign)
frm = access.Forms(form_name)

# Устанавливаем обработчик события
frm.Controls("cmdSave").OnClick = "[Event Procedure]"

access.DoCmd.Close(acForm, form_name, acSaveYes)

Удаление модуля

for j in range(proj.VBComponents.Count, 0, -1):
    comp = proj.VBComponents(j)
    if comp.Name == "modButtons" and comp.Type == 1:
        proj.VBComponents.Remove(comp)
        break

Типичные ошибки и решения

1. UnicodeEncodeError в консоли

Ошибка:

UnicodeEncodeError: 'charmap' codec can't encode character '\u2713'

Решение:

import sys
import io
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace')

2. PermissionError - файл заблокирован

Ошибка:

PermissionError: [WinError 32] Процесс не может получить доступ к файлу

Причина: Access процесс не завершился.

Решение:

# В начале скрипта
import subprocess
subprocess.run(['taskkill', '/F', '/IM', 'MSACCESS.EXE'],
               capture_output=True, shell=True)

3. AttributeError при установке свойств элементов

Ошибка:

AttributeError: object has no attribute 'Caption'

Причина: COM объекты Access не всегда позволяют напрямую устанавливать свойства.

Решение: Использовать Properties():

def set_prop(ctrl, prop_name, value):
    try:
        ctrl.Properties(prop_name).Value = value
    except:
        pass

4. Duplicate Option statement

Ошибка:

Compile error: Duplicate Option statement

Причина: Код добавлялся несколько раз без очистки.

Решение:

# Сначала очищаем модуль
if code_module.CountOfLines > 0:
    code_module.DeleteLines(1, code_module.CountOfLines)
# Затем добавляем код
code_module.AddFromString(vba_code)

5. Кнопки не работают (Event Procedure не срабатывает)

Причина: Код в отдельном модуле (Public Sub), а не в модуле формы (Private Sub).

Решение:

  • Код должен быть в модуле формы (Form_ИмяФормы)
  • Процедуры должны быть Private Sub, а не Public Sub
  • Имя процедуры должно точно совпадать: cmdButton_Click
  • Свойство OnClick должно быть [Event Procedure]

6. Ошибка при DoCmd.Save()

Ошибка:

pywintypes.com_error: Аргумент "Тип объекта" недопустим

Решение: Не использовать DoCmd.Save(), вместо этого:

access.DoCmd.Close(acForm, form_name, acSaveYes)

7. Форма не видит изменения в VBA

Причина: Trust Center блокирует макросы.

Решение для пользователя:

  1. При открытии базы нажать "Включить содержимое"
  2. Или: Файл → Параметры → Центр управления безопасностью → Включить все макросы

Лучшие практики

1. Структура скрипта

# -*- coding: utf-8 -*-
import sys
import io
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace')

import win32com.client
import os

DB_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), "database.accdb")

access = None
try:
    access = win32com.client.Dispatch("Access.Application")
    access.Visible = True  # Для отладки

    # ... ваш код ...

finally:
    if access:
        try:
            access.CloseCurrentDatabase()
        except:
            pass
        access.Quit()

2. Используйте Visible = True для отладки

Это позволяет видеть, что происходит в Access, и отлавливать диалоговые окна.

3. Сохраняйте формы после каждого изменения

access.DoCmd.Close(acForm, form_name, acSaveYes)

4. Проверяйте наличие объектов перед удалением

if os.path.exists(DB_PATH):
    os.remove(DB_PATH)

5. Используйте квадратные скобки для кириллицы

sql = "SELECT * FROM [Товары] WHERE [Наименование] = 'Нарзан';"

6. Единственный Option Compare Database

В модуле формы должен быть только один Option Compare Database в самом начале.


Полный пример

# -*- coding: utf-8 -*-
"""
Полный пример создания базы данных Access с формами и кнопками
"""

import sys
import io
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace')

import win32com.client
import os
from datetime import datetime, timedelta
import random

# Константы
acForm = 2
acTextBox = 109
acLabel = 100
acCommandButton = 104
acDetail = 0
acSaveYes = 1
acDesign = 1

DB_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), "example.accdb")

def set_prop(ctrl, prop, val):
    try:
        ctrl.Properties(prop).Value = val
    except:
        pass

# Удаляем старую базу
if os.path.exists(DB_PATH):
    os.remove(DB_PATH)

access = None
try:
    # Создаём Access
    access = win32com.client.Dispatch("Access.Application")
    access.Visible = True
    access.NewCurrentDatabase(DB_PATH)

    # === ТАБЛИЦА ===
    sql = """
    CREATE TABLE Товары (
        Код COUNTER PRIMARY KEY,
        Название TEXT(100),
        Цена CURRENCY,
        Дата DATETIME
    );
    """
    access.CurrentDb().Execute(sql)

    # === ДАННЫЕ ===
    for i in range(10):
        date = datetime.now() - timedelta(days=random.randint(1, 30))
        date_str = date.strftime("#%m/%d/%Y#")
        sql = f"INSERT INTO Товары (Название, Цена, Дата) VALUES ('Товар {i+1}', {random.randint(10, 100)}, {date_str});"
        access.CurrentDb().Execute(sql)

    # === ФОРМА ===
    form = access.CreateForm()
    fname = form.Name
    form.RecordSource = "Товары"
    form.Caption = "Форма товаров"
    form.Section(acDetail).Height = 3000

    # Поля
    y = 200
    for field in ["Код", "Название", "Цена", "Дата"]:
        lbl = access.CreateControl(fname, acLabel, acDetail, "", "", 200, y, 1500, 300)
        set_prop(lbl, "Caption", field + ":")

        txt = access.CreateControl(fname, acTextBox, acDetail, "", field, 1800, y, 3000, 300)
        y += 400

    # Кнопки
    y += 200
    for btn_name, btn_text, x in [("cmdPrev", "< Назад", 200), ("cmdNext", "Вперед >", 1500), ("cmdNew", "Новая", 2800)]:
        btn = access.CreateControl(fname, acCommandButton, acDetail, "", "", x, y, 1200, 350)
        set_prop(btn, "Name", btn_name)
        set_prop(btn, "Caption", btn_text)

    access.DoCmd.Close(acForm, fname, acSaveYes)
    access.DoCmd.Rename("Форма_Товары", acForm, fname)

    # === VBA КОД ===
    access.DoCmd.OpenForm("Форма_Товары", acDesign)
    frm = access.Forms("Форма_Товары")
    frm.HasModule = True

    vbe = access.VBE
    proj = vbe.VBProjects(1)

    for j in range(1, proj.VBComponents.Count + 1):
        comp = proj.VBComponents(j)
        if comp.Name == "Form_Форма_Товары":
            cm = comp.CodeModule
            if cm.CountOfLines > 0:
                cm.DeleteLines(1, cm.CountOfLines)

            vba = '''Option Compare Database

Private Sub cmdPrev_Click()
    On Error Resume Next
    DoCmd.GoToRecord , , acPrevious
End Sub

Private Sub cmdNext_Click()
    On Error Resume Next
    DoCmd.GoToRecord , , acNext
End Sub

Private Sub cmdNew_Click()
    On Error Resume Next
    DoCmd.GoToRecord , , acNewRec
End Sub
'''
            cm.AddFromString(vba)
            break

    # Привязываем события
    frm.Controls("cmdPrev").OnClick = "[Event Procedure]"
    frm.Controls("cmdNext").OnClick = "[Event Procedure]"
    frm.Controls("cmdNew").OnClick = "[Event Procedure]"

    access.DoCmd.Close(acForm, "Форма_Товары", acSaveYes)

    print("База данных создана успешно!")
    print(f"Файл: {DB_PATH}")

finally:
    if access:
        access.CloseCurrentDatabase()
        access.Quit()

Заметки для создания MCP сервера

Рекомендуемые инструменты MCP

  1. create_database - создание новой базы данных
  2. execute_sql - выполнение SQL запросов
  3. create_table - создание таблицы (высокоуровневый)
  4. create_form - создание формы с полями
  5. add_form_buttons - добавление кнопок с VBA
  6. create_query - создание запроса
  7. create_relation - создание связи
  8. export_form - экспорт формы в текст
  9. import_form - импорт формы из текста

Важные моменты для MCP

  1. Управление жизненным циклом Access - один экземпляр на сессию
  2. Обработка диалоговых окон - Visible=True и уведомления пользователю
  3. Транзакции - сохранение после каждой операции
  4. Валидация - проверка существования объектов
  5. Логирование - подробный вывод операций

Ссылки


Документ создан на основе практического опыта автоматизации MS Access через Python + COM. Дата: Январь 2025