使用游标对象的.rowcount 属性来检查结果集中是否有任何行通常更容易。这个属性是specified in the Python Database API:
这个只读属性指定了最后的行数
.execute*() 产生(对于像 SELECT 这样的 DQL 语句)或
受影响(对于像 UPDATE 或 INSERT 这样的 DML 语句)。 [9]
如果没有执行.execute*(),则该属性为-1
上一次操作的游标或行数不能
由接口决定。 [7]
当.rowcount无法使用时
请注意,根据上述规范,当最后一条语句“无法由接口确定”产生或影响的行数时,应将Cursor.rowcount 设置为-1。使用 SSCursor 和 SSDictCursor 游标类时会发生这种情况。
原因是 MySQL C API 有两个不同的函数用于检索结果集:mysql_store_result() 和 mysql_use_result()。不同之处在于mysql_use_result() 在您请求时从结果集中读取行,而不是在执行查询后立即存储整个结果集。对于非常大的结果集,这种“无缓冲”方法可以更快,并且在客户端机器上使用更少的内存;但是,在执行查询时,接口无法确定结果集包含多少行。
SSCursor 和SSDictCursor 都调用mysql_use_result(),因此无论结果集的大小如何,它们的.rowcount 属性都应保持值-1。相比之下,DictCursor 和默认的Cursor 类调用mysql_store_result(),在执行查询后立即读取并统计整个结果集。
更糟糕的是,.rowcount 属性仅在第一次打开光标时才保存值-1;一旦你执行一个查询,它就会收到mysql_affected_rows()的返回值。问题是mysql_affected_rows() 返回一个无符号长整型,它以can be very counterintuitive 的方式表示值-1,并且不会被cursor.rowcount == -1 这样的条件捕获。
为计数而计数
如果您正在做的唯一事情是计算记录,那么.rowcount 就没那么有用了,因为无论记录是否存在,您的COUNT(*) 查询都会返回一行。在这种情况下,测试零值的方式与从查询中获取结果时测试任何值的方式相同。是否可以c.fetchone()[0] == 0 取决于您使用的游标类;它适用于Cursor 或SSCursor,但不适用于DictCursor 或SSDictCursor,它们获取字典而不是元组。
重要的是在你的代码中清楚地知道发生了什么,这就是为什么我不建议使用c.fetchone() == (0,)。当您需要做的只是测试单个值时,它会测试整行;在测试之前将值从行中取出,您的代码会更加清晰。就个人而言,我发现c.fetchone()[0] 是不必要的不透明;我更喜欢:
row = cursor.fetchone()
if row[0] == 0:
do_something()
这清楚地表明,您正在测试行的第一项,而不是太冗长。当我做的事情比简单的COUNT() 或EXISTS() 更复杂时,我更喜欢使用DictCursor,这样我的代码(最多)依赖于显式别名,而从不依赖于隐式列排序。
测试空结果集
另一方面,如果您确实需要获取结果集并且计数纯粹是偶然的,只要您不使用无缓冲游标类之一,您就可以只执行重要查询而不必担心COUNT():
cursor.execute(r"SELECT id, name, email FROM user WHERE date_verified IS NULL;")
if cursor.rowcount == 0:
print 'No results'