Проверка данных из SQLite соответствует ограничениям типа «число в диапазоне х-у» - PullRequest
2 голосов
/ 23 февраля 2012

Я пишу скрипт на Python, который использует данные сомнительного качества. Данные хранятся в базе данных SQLite .

Я бы хотел компактный способ указать ограничения на данные. Ограничения бывают двух типов:

  1. Ошибки данных - будет выдано сообщение об ошибке.
    • "Столбец A должен быть целым числом в диапазоне 0-10"
    • «Столбец B должен быть непустой строкой» и т. Д.
  2. Предупреждения о качестве данных - " Вы уверены, что это правильно ?" Будет выпущено предупреждение. Ограничения будут такими вещами, как
    • "предупредить, если для столбца C установлено значение по умолчанию 0" - вы уверены, что машинистка не пропустила запись?
    • "предупредить, если число в столбце D необычно велико (> 1000)".

В идеале я бы хотел бы , чтобы выразить свои ограничения в удобочитаемом формате, например:

'kV' MUST BE float IN RANGE 0-10
'Rating' SHOULD NOT BE DEFAULT 1.0
'Description' SHOULD NOT BE DEFAULT ""

... но я улучшу свой текущий подход (ниже). Я был бы рад принять решение, которое включает в себя применение ограничений либо в Python, либо в схеме SQLite.

Вот что я сейчас использую:

 def is_number_in_range(number, expected_type, lower, upper):
    if type(number) != expected_type:
        return "not an %s" % expected_type
    elif ((number < lower) or (number > upper)):
        return "%s out of range [%i-%i]." % (expected_type, upper, lower)
    else:
        return "OK"

def not_default (value, expected_type, default_value):
    if type(value) != expected_type:
        return "not an %s" % expected_type
    elif value == default_value:
        return "default value of %s - make sure this is what you want." % default_value
    else:
        return "OK"

def Check_Cable_Lib(db_conn):
    res = db_conn.execute("SELECT * FROM Lib_Cable LIMIT 1")

    constraints = (
        ('kV', lambda x: is_number_in_range(x, float, 0, 1000) ),
        ('kA1', lambda x: is_number_in_range (x, float, 0, 10) ),
        ('kA1', lambda x: not_default(x, float, 1.0))
    )

    for cable_type in res:
        for constraint in constraints:
            constraint_variable = constraint[0]
            constraint_data = cable_type[constraint_variable]
            constraint_function = constraint[1]

            validation_message = constraint_function(constraint_data)
            print ("%(constraint_variable)s = %(constraint_data)s : %(validation_message)s" % locals())

stage1_db_path = "stage1.sqlite3";
db_conn = sqlite3.connect(stage1_db_path)
db_conn.row_factory = sqlite3.Row
Check_Cable_Lib(db_conn)

Пример вывода:

kV = 11.0 : OK
kA1 = 1.0 : OK
kA1 = 1.0 : default value of 1.0 - make sure this is what you want.

РЕДАКТИРОВАТЬ: Я знаю, что невежливо явно проверять типы в Python. Однако ради кода, который использует данные, мне нужно убедиться, что SQLite не хранил неожиданные вещи в столбцах («привет мир» в столбце INT и т. Д.) Помните, что данные имеют сомнительное качество, и SQLite с радостью поместите любой тип данных в любой столбец. Улавливание этих типов ошибок при вводе данных является одной из целей этого кода.

Ответы [ 3 ]

3 голосов
/ 23 февраля 2012

Может быть интересна следующая статья:

Вербализация бизнес-правил Терри Хэлпина

Правила Alethic навязывают потребности, которые не могут даже в принципенарушать бизнес, как правило, из-за какого-то физического или логического закона.Например: каждый сотрудник родился не более чем на одном свидании;ни один продукт не является компонентом самого себя.Деонтические правила налагают обязательства, которые могут быть нарушены, хотя они не должны.Например: обязательно, чтобы каждый сотрудник состоял в браке не более чем с одним человеком;Курение запрещено в любом офисе.

С точки зрения SQL, напишите запрос для возврата данных, которые нарушали бы правило, например

SELECT * 
  FROM T 
 WHERE Column_A < 0

Затем проверьте, чтокаждое правило - это пустой набор.Посмотрите, чтобы сделать их гранулированными, например, имея отдельные тесты для Column_A < 0 и Column_A > 10 соответственно.

2 голосов
/ 23 февраля 2012

Если вы хотите использовать немного более сложную реализацию, вы можете использовать словарь словарей:

Спецификация может быть:

{
'kv':{'type':'float','range':(0,10)},
'Rating':{'not':1.0}
}
1 голос
/ 23 февраля 2012

Объединение идеи @ onedaywhen при использовании SQL для проверки ограничений и идеи @ ABS по определению ограничений более читабельным образом, вот что я придумал.

Заключение этого в класс, вероятно, не особенно полезно (как используется в примере, это прославленная оболочка вокруг функции check()), но это означает, что позже я могу испечь немного более приятное форматирование вывода. *

class Constraint:
    def __init__(self, table, column, constraint, error_or_warning):
        """
        examples:
            Constraint('Lib_Cable','kV', '> 0', 'error')
            Constraint('Lib_Cable','Insulation', '!= 0', 'warning')

        """
        self.table, self.column, self.constraint, self.error_or_warning = \
        table, column, constraint, error_or_warning

    def check (self, db_conn):
        c = db_conn.cursor()
        c.row_factory = sqlite3.Row

        query = "SELECT * FROM %(table)s WHERE NOT (%(column)s %(constraint)s)" % \
                {'table'      : self.table,
                 'column'     : self.column,
                 'constraint' : self.constraint }
        # [FIXME] start a transaction here - guard against novice SQL injections?
        res = c.execute(query)

        for row in res:
            print ( "%(error_or_warning)s: Row with key %(key)s in table %(table)s violates constraint %(column)s %(constraint)s)." % \
                {'key'        : row['KeyName'],
                 'table'      : self.table,
                 'column'     : self.column,
                 'constraint' : self.constraint,
                 'error_or_warning' : self.error_or_warning} )

        # [FIXME] discard transaction

Constraint_1 = Constraint('Lib_Cable', 'Insulation', '!= 0', 'error')        
Constraint_1.check(db_conn)
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...