我为Django编写了一个函数,它允许用户输入一个单词或短语,并获取指定模型中的所有实例,其中该实例的所有单词在一系列指定字段中以任何顺序出现。我选择使用objects.raw方法并为此编写自定义SQL,因为使用Django Q对象构建正确的查询时出现问题。
def fuzzy_search(objmodel,columns,q='',limit=None,offset=0):
"""
TEMPORARY PATCH version for fuzzy_search, gets around a native Django bug.
"""
if len(q)<3:
return [] #No results until you reach 3 chars
words = q.strip().split(" ")
#Get model table name:
print "All results: %s" % objmodel.objects.all()
db_table = objmodel._meta.db_table
print("DB_table = %s" % db_table)
#Construct fields into list of kwarguments!
sql = "SELECT * FROM %s" % (db_table,)
userparams = []
whereands = []
#Construct the SQL as
for word in words:
if len(word)<2:
continue #Ignore computationally expensive single char strings
whereors = []
for col in columns:
whereors.append('`%s`.`%s` LIKE "%s##*##"' % (db_table,col,"##P##")) #STARTSWITH word... The third param will be converted via injection proof means
whereors.append('`%s`.`%s` LIKE "(%s##*##"' % (db_table,col,"##P##")) #STARTSWITH (word... The third param will be converted via injection proof means
whereors.append('`%s`.`%s` LIKE "##*## %s##*##"' % (db_table,col,"##P##")) #CONTAINS word... The third param will be converted via injection proof means
whereors.append('`%s`.`%s` LIKE "##*## (%s##*##"' % (db_table,col,"##P##")) #CONTAINS (word... The third param will be converted via injection proof means
if whereors not in boolfalse:
whereorstr= "(" + " OR ".join(whereors) + ")"
for i in range(0,len(whereors)):
userparams.append(word) #Need to match the number of supplied params to the number of clauses
whereands.append(whereorstr) #Build into an SQL string
else:
continue
#Build the final sql:
results = []
if whereands not in boolfalse:
sql+= " WHERE " + " AND ".join(whereands)
sql = sql.replace("##P##","%s") #Necessary to get around %s persistence restriction
sql = sql.replace("##*##","%%") #Makes everything a bit clearer!
#For big datasets, it is more efficient to apply LIMITS and OFFSETS at SQL level:
if limit:
sql+= " LIMIT %s" % int(limit) #This is injection proof as only ints are accepted
if offset:
sql+= " OFFSET %s" % int(offset) #This is injection proof as only ints are accepted
#Perform the raw query, but with params carefully passed in via SQLi escaped method:
### objects.raw method ###
resultsqset = objmodel.objects.raw(sql,userparams)
print("Fuzzy SQL: \n%s\n" % resultsqset.query.__str__()) #View SQL
results = list(resultsqset)
print("Results: %s" % results)
### direct cursor method ###
#cursor = connection.cursor()
#cursor.execute(sql,userparams)
#results = dictfetchall(cursor) #Ensures the results are fetched as a dict of fieldname => value
return results
return results
这个函数的调用方式如下:
from modules.documents.models import Data_icd10_en
results = fuzzy_search(Data_icd10_en,["code","long_label"],"diab mel",30)
模型是:
class Data_icd10_en(models.Model):
code = models.CharField(max_length=10)
short_label = models.CharField(max_length=100)
long_label = models.CharField(max_length=100)
当我调用该函数时,我可以在控制台中看到实际的SQL转储:
print("Fuzzy SQL: \n%s\n" % resultsqset.query.__str__()) #View SQL
Fuzzy SQL:
<RawQuery: u'SELECT * FROM documents_data_icd10_en WHERE (`documents_data_icd10_en`.`code` LIKE "diabetes%" OR `documents_data_icd10_en`.`code` LIKE "(diabetes%" OR `documents_data_icd10_en`.`code` LIKE "% diabetes%" OR `documents_data_icd10_en`.`code` LIKE "% (diabetes%" OR `documents_data_icd10_en`.`long_label` LIKE "diabetes%" OR `documents_data_icd10_en`.`long_label` LIKE "(diabetes%" OR `documents_data_icd10_en`.`long_label` LIKE "% diabetes%" OR `documents_data_icd10_en`.`long_label` LIKE "% (diabetes%") AND (`documents_data_icd10_en`.`code` LIKE "mell%" OR `documents_data_icd10_en`.`code` LIKE "(mell%" OR `documents_data_icd10_en`.`code` LIKE "% mell%" OR `documents_data_icd10_en`.`code` LIKE "% (mell%" OR `documents_data_icd10_en`.`long_label` LIKE "mell%" OR `documents_data_icd10_en`.`long_label` LIKE "(mell%" OR `documents_data_icd10_en`.`long_label` LIKE "% mell%" OR `documents_data_icd10_en`.`long_label` LIKE "% (mell%") LIMIT 30'>
如果我将此SQL直接复制并粘贴到数据库后端(MySQL)中,则会返回正确的结果(30行诊断变体“Diabetes Mellitus”)。但是,python函数本身无法返回任何内容(结果只是一个空列表)。我尝试过print(resultsqset),这只是揭示了这个RawQuerySet:
Results: <RawQuerySet: u'SELECT * FROM documents_data_icd10_en WHERE (`documents_data_icd10_en`.`code` LIKE "diab%" OR `documents_data_icd10_en`.`code` LIKE "(diab%" OR `documents_data_icd10_en`.`code` LIKE "% diab%" OR `documents_data_icd10_en`.`code` LIKE "% (diab%" OR `documents_data_icd10_en`.`long_label` LIKE "diab%" OR `documents_data_icd10_en`.`long_label` LIKE "(diab%" OR `documents_data_icd10_en`.`long_label` LIKE "% diab%" OR `documents_data_icd10_en`.`long_label` LIKE "% (diab%") AND (`documents_data_icd10_en`.`code` LIKE "mel%" OR `documents_data_icd10_en`.`code` LIKE "(mel%" OR `documents_data_icd10_en`.`code` LIKE "% mel%" OR `documents_data_icd10_en`.`code` LIKE "% (mel%" OR `documents_data_icd10_en`.`long_label` LIKE "mel%" OR `documents_data_icd10_en`.`long_label` LIKE "(mel%" OR `documents_data_icd10_en`.`long_label` LIKE "% mel%" OR `documents_data_icd10_en`.`long_label` LIKE "% (mel%") LIMIT 30'>
我还尝试将rawqueryset转换为列表,并手动迭代并打印行。两者都没有产生任何结果。
最后,要检查模型对象实际上是我认为的那样,尝试print "All results: %s" % objmodel.objects.all()
会给我一个大约40个<Data_icd10_en: Data_icd10_en object>
的列表,这正是我所期望的。
那么,这里发生了什么?为什么我的代码在通过modelname.objects.raw()运行时没有产生任何东西,但是当在数据库shell中运行完全相同的SQL时获取结果,并且当相同的modelname在其中获取所有行时也正确地获取结果那个功能?
----编辑---- 测试证实是的,我确实通过Django应用程序和shell访问了同一个数据库。此外,一行中的简单原始查询也能正常工作。
答案 0 :(得分:1)
经过进一步调查,打开MySQL日志记录并向Django开发人员发送电子邮件后,发现我的代码没有问题。
而QuerySet.query.__str__()
中存在本机和次要错误:当它将实际的SQL内容输出到控制台时,它无法打印封装用户提供的参数的引号。
所以当控制台说明时:
<RawQuery: u'SELECT * FROM documents_data_icd10_en WHERE (`documents_data_icd10_en`.`code` LIKE "(diabetes%"...
实际执行的是:
"<RawQuery: u'SELECT * FROM documents_data_icd10_en WHERE (`documents_data_icd10_en`.`code` LIKE "("diabetes%""...
......这是无效的。
故事的故事:不要相信QuerySet.query.__str__()
告诉你的内容,也不要将用户提供的字符串封装在Model.objects.raw(sql,PARAMS)
的引号中,因为这将为你完成。
答案 1 :(得分:0)
#this 将执行任何类型的原始查询,它可以是子查询等..\
query ="""this will be the raw query"""\
X=cursor.execute(query) \
answers = cursor.fetchall()\
print ("answers---",answers)\