使用 CDate() 的查询过滤器产生不一致的结果

Query filter using CDate() produces inconsistent results

我尝试获取此查询 运行ning 但它没有像我预期的那样工作。

table 中有一个格式化为文本的日期字段(无法更改),我需要过滤此列。

日期看起来像

11/03/2022 (d/m/Y)

我的查询看起来像

session.query(DBGEUK)\
        .filter(DBGEUK.VALIDATOR == '58')\
        .filter(func.CDate(DBGEUK.DATE) <= datetime.now())\
        .all()

共有 24 个条目。当我 运行 上面的查询并打印出 DBGEUK.DATE + datetime.now 时,这 9 个条目是我的结果。

27/03/2022 2022-03-28 19:06:49.465406
27/03/2022 2022-03-28 19:06:49.480988
27/03/2022 2022-03-28 19:06:49.480988
27/03/2022 2022-03-28 19:06:49.480988
28/03/2022 2022-03-28 19:06:49.480988
28/03/2022 2022-03-28 19:06:49.480988
28/03/2022 2022-03-28 19:06:49.480988
28/03/2022 2022-03-28 19:06:49.481612
28/03/2022 2022-03-28 19:06:49.481727

如果我将查询更改为大于 >= 我得到了其他 15 个条目

04/03/2022 2022-03-28 19:09:09.030659
04/03/2022 2022-03-28 19:09:09.031659
04/03/2022 2022-03-28 19:09:09.031659
04/03/2022 2022-03-28 19:09:09.031659
04/03/2022 2022-03-28 19:09:09.031659
05/03/2022 2022-03-28 19:09:09.031659
05/03/2022 2022-03-28 19:09:09.031659
05/03/2022 2022-03-28 19:09:09.031659
05/03/2022 2022-03-28 19:09:09.032657
11/03/2022 2022-03-28 19:09:09.032657
12/03/2022 2022-03-28 19:09:09.032657
11/03/2022 2022-03-28 19:09:09.032657
11/03/2022 2022-03-28 19:09:09.032657
09/03/2022 2022-03-28 19:09:09.033654
09/03/2022 2022-03-28 19:09:09.033654

感谢您的帮助。

CDate() 函数尝试根据 Windows 控制面板中的日期格式设置来解释日期字符串文字。使用 MM/dd/yyyy 的“短日期”设置,CDate("03/07/2022") 计算为 2022 年 3 月 7 日。使用 dd/MM/yyyy 的“短日期”设置,CDate("03/07/2022") 计算为 7 月 3 日, 2022.

但是,如果日期字符串表示无效日期,则 CDate() 将“有帮助”,并且 return 使用其他格式的有效日期。在上述两种情况下,CDate("14/03/2022") 将评估为 2022 年 3 月 14 日。

不幸的是,这意味着

  1. 如何解释日期取决于 Windows 日期格式,这可能因机器而异,甚至在同一台机器上因用户而异。
  2. 日期的解释在不明确和不明确的日期字符串之间可能不一致。

因此在这种情况下,我们需要避免使用 CDate() 并自行解析日期字符串:

from datetime import datetime

from sqlalchemy import create_engine, Column, String, select, func
from sqlalchemy.engine import URL
from sqlalchemy.orm import declarative_base, Session

accdb_path = r"C:\Users\Public\test\sqlalchemy-access\gord_test.accdb"
connection_string = (
    "DRIVER={Microsoft Access Driver (*.mdb, *.accdb)};"
    f"DBQ={accdb_path};"
    "ExtendedAnsiSQL=1;"
)
connection_url = URL.create(
    "access+pyodbc", query={"odbc_connect": connection_string}
)
engine = create_engine(connection_url)

Base = declarative_base()


class DBGEUK(Base):
    __tablename__ = "so71651145"
    DATE = Column(String(10), primary_key=True)

    def __repr__(self):
        return f"<DBGEUK(DATE='{self.DATE}')>"


# create test environment
Base.metadata.drop_all(engine)
Base.metadata.create_all(engine)

with Session(engine) as session:
    # example data
    session.add_all(
        [
            #                           How CDate() interprets the string
            #                           based on Windows' short date format:
            #
            #                             dd/MM/yyyy    MM/dd/yyyy
            #                             ------------  ------------
            DBGEUK(DATE="07/03/2022"),  # Mar 7, 2022   Jul 3, 2022
            DBGEUK(DATE="14/03/2022"),  # Mar 14, 2022  Mar 14, 2022
            DBGEUK(DATE="03/07/2022"),  # Jul 3, 2022   Mar 7, 2022
            DBGEUK(DATE="31/12/2022"),  # Dec 31, 2022  Dec 31, 2022
        ]
    )
    session.commit()

    # (for future readers of this answer)
    print(datetime.now())  # 2022-03-29 08:44:00.512366

    # original query
    qry = select(DBGEUK).filter(func.CDate(DBGEUK.DATE) <= datetime.now())
    results = session.scalars(qry).all()
    print(results)
    # [<DBGEUK(DATE='14/03/2022')>, <DBGEUK(DATE='03/07/2022')>]
    #
    # with Windows' short date format set to MM/dd/yyyy, CDate() interprets
    # the second value as March 7, not July 3

    # corrected query
    qry = select(DBGEUK).filter(
        func.DateSerial(
            func.CInt(func.Mid(DBGEUK.DATE, 7, 4)),  # year
            func.CInt(func.Mid(DBGEUK.DATE, 4, 2)),  # month
            func.CInt(func.Mid(DBGEUK.DATE, 1, 2)),  # day
        )
        <= datetime.now()
    )
    results = session.scalars(qry).all()
    print(results)
    # [<DBGEUK(DATE='07/03/2022')>, <DBGEUK(DATE='14/03/2022')>]
    #
    # all good