【问题标题】:How to extract correct data from Sqlite database using Python?如何使用 Python 从 Sqlite 数据库中提取正确的数据?
【发布时间】:2015-11-29 10:46:33
【问题描述】:

我有一个包含人名和生日的数据库。生日格式为mm/dd/yyyy,如“3/13/1960”。

我想提取在特定日期之后出生的人的列表。我称这个日期为“基准”。

您在下面看到的程序首先创建了一个人员数据库(以模拟我想要使用的真实数据库),然后提取所需的列表。问题是结果不符合我的预期:

import datetime as dt
import peewee as pw
db = pw.SqliteDatabase('people1.db')

class Person(pw.Model):
    name = pw.CharField()
    birthday = pw.DateField(formats=['%m/%d/%Y'])
    class Meta:
        database = db # This model uses the "people.db" database.

db.create_tables([Person])


bob0 = Person(name='Bob0', birthday='4/13/1940')
bob1 = Person(name='Bob1', birthday='5/13/1950')
bob2 = Person(name='Bob2', birthday='3/13/1960')
bob3 = Person(name='Bob3', birthday='3/13/1970')
bob4 = Person(name='Bob4', birthday='3/13/1980')
bob5 = Person(name='Bob5', birthday='3/13/1990')

base = Person(name="base", birthday='3/13/1960')

bob0.save()
bob1.save()
bob2.save()
bob3.save()
bob4.save()
bob5.save()
base.save()

for item in Person.select().where(Person.birthday > base.birthday):
    print item.name , item.birthday

输出:

>>> ================================ RESTART ================================
>>> 
Bob0 1940-04-13
Bob1 1950-05-13
Bob3 1970-03-13
Bob4 1980-03-13
Bob5 1990-03-13
>>> 

如上所示,base = 3/13/1960。所以我不应该在输出中有 Bob0Bob1 !我该如何处理?

请注意,我不想更改数据库中生日的格式。我也不想获取所有行并稍后检查它们!我只想获取所需的行。

【问题讨论】:

  • 他们确实在文档中警告说“为了确保比较正常工作,需要对日期进行格式化,以便按字典顺序对日期进行排序”。您是否查看过 peewee 的自定义字段和运算符?
  • @PauloAlmeida 啊,那是不是意味着日期必须以yyyy/mm/dd的格式保存?如果是这样,现在怎么办?我现在该怎么办?其实我查了文档,没有看到这个表达式。
  • 好吧,您没有必须使用任何格式,但如果您可以使用默认格式,那当然会让您的生活变得更轻松。除此之外,据我所知,您将不得不使用自定义字段和/或用户定义的运算符。他们在文档中为后者提供了mod 的示例。对我来说,你会怎么做并不是很明显,因为这不仅仅是一个手术,但我想这是可能的。
  • 无意冒犯,但常识是,如果您将日期存储为字符串并希望对其进行排序,则需要按字典顺序对它们进行排序。只需更新您的数据库,就可以省去很多麻烦。

标签: python sqlite date peewee


【解决方案1】:

SQlite 将日期时间存储为字符串。因此,正如其他人在 cmets 和其他 answers 中所建议的那样,您应该使用不同的格式来存储日期,以便“日期排序和词法排序结果相同”:

import datetime as dt
import peewee as pw

db = pw.SqliteDatabase('people1.db')

class Person(pw.Model):
    name = pw.CharField()
    birthday = pw.DateField(formats=['%Y-%m-%d'])
    class Meta:
        database = db # This model uses the "people.db" database.

db.create_tables([Person])


Person.create(name='Bob0', birthday=dt.date(1940, 4, 13))
Person.create(name='Bob1', birthday=dt.date(1950, 5, 13))
Person.create(name='Bob2', birthday=dt.date(1960, 3, 13))
Person.create(name='Bob3', birthday=dt.date(1970, 3, 13))
Person.create(name='Bob4', birthday=dt.date(1980, 3, 13))
Person.create(name='Bob5', birthday=dt.date(1990, 3, 13))

base = Person.create(name="base", birthday=dt.date(1960, 3, 13))

for item in Person.select().where(Person.birthday > base.birthday):
    print item.name , item.birthday

这给出了:

Bob3 1970-03-13
Bob4 1980-03-13
Bob5 1990-03-13

更新

我没有注意到您不想更改数据库的评论。

这是一种提取部分日期的疯狂方法:

SELECT
    birthday,
    CAST(substr(birthday, 1, instr(birthday, '/') - 1) AS integer),
    CAST(substr(substr(birthday, instr(birthday, '/') + 1), 1, instr(substr(birthday, instr(birthday, '/') + 1), '/') - 1) AS integer),
    CAST(substr(birthday, instr(birthday, '/') + instr(substr(birthday, instr(birthday, '/') + 1), '/') + 1) AS integer)
FROM person

我的测试数据给出的:

4/13/1940   4   13  1940
12/13/1950  12  13  1950
3/3/1960    3   3   1960
3/25/1970   3   25  1970
3/13/1980   3   13  1980
3/13/1990   3   13  1990
3/13/1960   3   13  1960

您可以使用这些表达式将它们与给定日期的部分内容进行比较:

query = """
SELECT *
FROM person
WHERE
    (
        substr('0000' || CAST(substr(birthday, instr(birthday, '/') + instr(substr(birthday, instr(birthday, '/') + 1), '/') + 1) AS integer), -4, 4) || '-' || -- year
        substr('00' || CAST(substr(birthday, 1, instr(birthday, '/') - 1) AS integer), -2, 2) || '-' || -- month
        substr('00' || CAST(substr(substr(birthday, instr(birthday, '/') + 1), 1, instr(substr(birthday, instr(birthday, '/') + 1), '/') - 1) AS integer), -2, 2) -- day
    ) > '1960-03-03'
"""
for item in Person.raw(query):
    print item.name, item.birthday

我在这里重建 ISO 日期并使用它进行比较。

【讨论】:

  • 其实我已经知道这个解决方案了。正如我在问题中所说,我不想更改数据库。这是一个不断变化的大在线文件,我宁愿不要更改它。
  • stackoverflow.com/questions/1975737/sqlite-datetime-comparison 回答者是对的。 @Abraham 我看到的唯一解决方案是使用一些字符串操作函数(如substr)来比较您的日期,如果您不更改日期的格式。
  • @dmitry 我尝试了其中一些,但我失败了。我收到了一些奇怪的异常。我可以请你提供一个例子吗? :)
  • Peewee 有 fn 用于在表达式中定义 SQL 函数调用。它看起来像fn.substr(Person.birthday, 5, 4) > '1960'。您可以使用 substr 创建一些表达式。虽然初始日期格式仍然存在问题:日和月部分的长度可变。
  • 为您的任务使用 数据库 的唯一明智方法是将格式更改为支持。
【解决方案2】:

您可以使用 sqlite3.Connection.create_function 指定您自己的 sqlite 函数,该函数会将您的日期转换为可以按字典顺序排序的内容:

import datetime as dt
import peewee as pw

# custom sqlite function to reformat our date string
def _sqlite_reformat_date(unfortunate_date_string):
    return dt.datetime \
        .strptime(unfortunate_date_string,'%m/%d/%Y') \
        .strftime('%Y-%m-%d')

# Subclass pw.SqliteDatabase to add our custom sqlite function
class MySqliteDatabase(pw.SqliteDatabase):
    def __init__(self, *args, **kwargs):
        super(MySqliteDatabase, self).__init__(*args, **kwargs)

    def _add_conn_hooks(self, conn):
        conn.create_function('reformat_date', 1, _sqlite_reformat_date)
        super(MySqliteDatabase, self)._add_conn_hooks(conn)

db = MySqliteDatabase('people1.db')

# ...
# Your model definition and data inserts from your example above
# ...

rd = pw.fn.reformat_date # Use our custom sqlite function
for item in Person.select().where(rd(Person.birthday) > rd(base.birthday)):
    print item.name , item.birthday

虽然这种方法会“只获取所需的行”,但它仍然会为每一行运行这个 python 函数!在python中进行日期比较比只获取所有行好一点,甚至可能更慢!

不过,_sqlite_reformat_date 函数可以重构得更快,而且很高兴知道将自定义函数添加到 sqlite 中是多么容易。

【讨论】:

  • 看起来很棒。我会检查的。
  • @Jeremy,这个“添加自己的功能”的功能是否也适用于 MySQL 数据库?
  • @MehdiJafarniaJahromi 您可以通过多种不同方式添加新的 sql 函数:请参阅 Adding New Functions to MySQL。但是我不能立即看到类似于这种“sql 函数调用 python 函数”方法的方法
  • 这将是非常低效的。
  • @MehdiJafarniaJahromi 据我所知 MySQL 支持日期时间格式,您不需要为此定义任何函数。亲爱的杰里米,我说得对吗?
【解决方案3】:

SQLite 将日期存储为字符串。出于这个原因,它们应该被存储为 YYYY-MM-DD,这样可以确保它们被正确排序。老实说,没有理由不这样做。

如果您查看 sqlite 的文档,它甚至无法识别您使用的格式的日期:

https://www.sqlite.org/lang_datefunc.html

所以,我的建议是更新您存储日期的方式。

否则,使用strptime 创建一个执行正确操作的用户定义函数(假设使用playhouse.sqlite_ext.SqliteExtDatabase):

@db.func()
def fix_time(s):
    return datetime.datetime.strptime(s, '%m/%d/%Y').strftime('%Y-%m-%d')

如果你想坚持使用常规的旧SqliteDatabase,你可以调用sqlite3方法connection.create_function

【讨论】:

    【解决方案4】:

    只是一些 SQL 函数的链接看起来很有趣,但可以工作,也许更快。

    from datetime import datetime
    
    import peewee as pw
    
    db = pw.SqliteDatabase('people1.db')
    
    class Person(pw.Model):
        name = pw.CharField()
        birthday = pw.DateField(formats=['%m/%d/%Y'])
        class Meta:
            database = db # This model uses the "people.db" database.
    
    db.create_tables([Person])
    
    
    bob0 = Person(name='Bob0', birthday='4/13/1940')
    bob1 = Person(name='Bob1', birthday='5/13/1950')
    bob2 = Person(name='Bob2', birthday='3/13/1960')
    bob3 = Person(name='Bob3', birthday='3/13/1970')
    bob4 = Person(name='Bob4', birthday='3/13/1980')
    bob5 = Person(name='Bob5', birthday='3/13/1990')
    bob6 = Person(name='Bob6', birthday='12/1/1990')
    
    base = Person(name="base", birthday='3/13/1960')
    
    bob0.save()
    bob1.save()
    bob2.save()
    bob3.save()
    bob4.save()
    bob5.save()
    bob6.save()
    base.save()
    
    month = 'substr(birthday,1,instr(birthday,"/")-1)'
    iso_month = 'case when length({month}) = 1 then "0" || {month} else {month} end'.format(month=month)
    day = 'trim(trim(birthday,"0123456789"),"/")'
    iso_day = 'case when length({day}) = 1 then "0" || {day} else {day} end'.format(day=day)
    year = 'substr(ltrim(ltrim(birthday,"0123456789"),"/"),instr(ltrim(ltrim(birthday,"0123456789"),"/"),"/")+1)'
    iso_date = 'replace(replace(replace("yyyy-mm-dd","yyyy",{year}),"mm",{iso_month}),"dd",{iso_day})'.format(year=year,iso_month=iso_month,iso_day=iso_day)
    iso_base = datetime.strptime(base.birthday,'%m/%d/%Y').date().isoformat()
    
    if __name__ == '__main__':
    
        for item in Person.select().where(pw.SQL(iso_date) > iso_base):
            print item.name , item.birthday
    #output
    #Bob3 1970-03-13
    #Bob4 1980-03-13
    #Bob5 1990-03-13
    #Bob6 1990-12-01
    

    【讨论】:

      猜你喜欢
      • 2012-04-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-01-30
      相关资源
      最近更新 更多