django 1.8 官方文檔翻譯: 2-5-2 進(jìn)行原始的sql查詢

Django 文檔協(xié)作翻譯小組人手緊缺,有興趣的朋友可以加入我們宛徊,完全公益性質(zhì)。

交流群:467338606

網(wǎng)站:http://python.usyiyi.cn/django/index.html

進(jìn)行原始的sql查詢

模型查詢API不夠用的情況下砰碴,你可以使用原始的sql語句扮授。django提供兩種方法使用原始sql進(jìn)行查詢:一種是使用Manager.raw()方法芳室,進(jìn)行原始查詢并返回模型實(shí)例;另一種是完全避開模型層刹勃,直接執(zhí)行自定義的sql語句堪侯。

警告

編寫原始的sql語句時(shí),應(yīng)該格外小心荔仁。每次使用的時(shí)候伍宦,都要確保轉(zhuǎn)義了參數(shù)中的任何控制字符,以防受到sql注入攻擊乏梁。更多信息請(qǐng)參閱防止sql注入次洼。

進(jìn)行原始查詢

raw()方法用于原始的sql查詢,并返回模型的實(shí)例:

Manager.raw(raw_query, params=None, translations=None)

這個(gè)方法執(zhí)行原始的sql查詢之后遇骑,返回django.db.models.query.RawQuerySet的實(shí)例卖毁。RawQuerySet實(shí)例可以像一般的QuerySet那樣,通過迭代來提供對(duì)象的實(shí)例落萎。

這里最好通過例子展示一下亥啦,假設(shè)存在以下模型:

class Person(models.Model):
    first_name = models.CharField(...)
    last_name = models.CharField(...)
    birth_date = models.DateField(...)

你可以像這樣執(zhí)行自定義的sql語句:

>>> for p in Person.objects.raw('SELECT * FROM myapp_person'):
...     print(p)
John Smith
Jane Jones

當(dāng)然,這個(gè)例子不是特別有趣练链,和直接使用Person.objects.all()的結(jié)果一模一樣翔脱。但是,raw()擁有其它更強(qiáng)大的使用方法媒鼓。

模型表的名稱

在上面的例子中届吁,Person表的名稱是從哪里得到的?

通常绿鸣,Django通過將模型的名稱和模型的“應(yīng)用標(biāo)簽”(你在manage.py startapp中使用的名稱)進(jìn)行關(guān)聯(lián)瓷产,用一條下劃線連接他們,來組合表的名稱枚驻。在這里我們假定Person模型存在于一個(gè)叫做myapp的應(yīng)用中濒旦,所以表就應(yīng)該叫做myapp_person

更多細(xì)節(jié)請(qǐng)查看db_table選項(xiàng)的文檔再登,它也可以讓你自定義表的名稱尔邓。

警告

傳遞給raw()方法的sql語句并沒有任何檢查。django默認(rèn)它會(huì)返回一個(gè)數(shù)據(jù)集锉矢,但這不是強(qiáng)制性的梯嗽。如果查詢的結(jié)果不是數(shù)據(jù)集,則會(huì)產(chǎn)生一個(gè)錯(cuò)誤沽损。

警告

如果你在mysql上執(zhí)行查詢灯节,注意在類型不一致的時(shí)候,mysql的靜默類型強(qiáng)制可能導(dǎo)致意想不到的結(jié)果發(fā)生。如果你在一個(gè)字符串類型的列上查詢一個(gè)整數(shù)類型的值炎疆,mysql會(huì)在比較前強(qiáng)制把每個(gè)值的類型轉(zhuǎn)成整數(shù)卡骂。例如,如果你的表中包含值'abc''def'形入,你查詢'where mycolumn=0'全跨,那么兩行都會(huì)匹配。要防止這種情況亿遂,在查詢中使用值之前浓若,要做好正確的類型轉(zhuǎn)換。

警告

雖然RawQuerySet可以像普通的QuerySet一樣迭代蛇数,RawQuerySet并沒有實(shí)現(xiàn)可以在QuerySet上使用的所有方法挪钓。例如,__bool__()__len__()RawQuerySet中沒有被定義耳舅,所以所有RawQuerySet轉(zhuǎn)化為布爾值的結(jié)果都是True诵原。RawQuerySet中沒有實(shí)現(xiàn)他們的原因是,在沒有內(nèi)部緩存的情況下會(huì)導(dǎo)致性能下降挽放,而且增加內(nèi)部緩存不向后兼容绍赛。

將查詢字段映射到模型字段

raw()方法自動(dòng)將查詢字段映射到模型字段。

字段的順序并不重要辑畦。換句話說吗蚌,下面兩種查詢的作用相同:

>>> Person.objects.raw('SELECT id, first_name, last_name, birth_date FROM myapp_person')
...
>>> Person.objects.raw('SELECT last_name, birth_date, first_name, id FROM myapp_person')
...

Django會(huì)根據(jù)名字進(jìn)行匹配。這意味著你可以使用sql的as子句來映射二者纯出。所以如果在其他的表中有一些Person數(shù)據(jù)蚯妇,你可以很容易地把它們映射成Person實(shí)例。

>>> Person.objects.raw('''SELECT first AS first_name,
...                              last AS last_name,
...                              bd AS birth_date,
...                              pk AS id,
...                       FROM some_other_table''')

只要名字能對(duì)應(yīng)上暂筝,模型的實(shí)例就會(huì)被正確創(chuàng)建箩言。
又或者,你可以在raw()方法中使用翻譯參數(shù)焕襟。翻譯參數(shù)是一個(gè)字典陨收,將表中的字段名稱映射為模型中的字段名稱、例如鸵赖,上面的查詢可以寫成這樣:

>>> name_map = {'first': 'first_name', 'last': 'last_name', 'bd': 'birth_date', 'pk': 'id'}
>>> Person.objects.raw('SELECT * FROM some_other_table', translations=name_map)

索引訪問

raw()方法支持索引訪問务漩,所以如果只需要第一條記錄,可以這樣寫:

>>> first_person = Person.objects.raw('SELECT * FROM myapp_person')[0]

然而它褪,索引和切片并不在數(shù)據(jù)庫層面上進(jìn)行操作饵骨。如果數(shù)據(jù)庫中有很多的Person對(duì)象,更加高效的方法是在sql層面限制查詢中結(jié)果的數(shù)量:

>>> first_person = Person.objects.raw('SELECT * FROM myapp_person LIMIT 1')[0]

延遲加載模型字段

字段也可以被省略:

>>> people = Person.objects.raw('SELECT id, first_name FROM myapp_person')

查詢返回的Person對(duì)象是一個(gè)延遲的模型實(shí)例(請(qǐng)見 defer())茫打。這意味著被省略的字段居触,在訪問時(shí)才被加載妖混。例如:

>>> for p in Person.objects.raw('SELECT id, first_name FROM myapp_person'):
...     print(p.first_name, # This will be retrieved by the original query
...           p.last_name) # This will be retrieved on demand
...
John Smith
Jane Jones

從表面上來看,看起來這個(gè)查詢獲取了first_namelast_name轮洋。然而制市,這個(gè)例子實(shí)際上執(zhí)行了3次查詢。只有first_name字段在raw()查詢中獲取砖瞧,last_name字符按在執(zhí)行打印命令時(shí)才被獲取。

只有一種字段不可以被省略嚷狞,就是主鍵块促。Django 使用主鍵來識(shí)別模型的實(shí)例,所以它在每次原始查詢中都必須包含床未。如果你忘記包含主鍵的話竭翠,會(huì)拋出一個(gè)InvalidQuery異常。

增加注解

你也可以在查詢中包含模型中沒有定義的字段薇搁。例如斋扰,我們可以使用PostgreSQL的age()函數(shù)來獲得一群人的列表,帶有數(shù)據(jù)庫計(jì)算出的年齡啃洋。

>>> people = Person.objects.raw('SELECT *, age(birth_date) AS age FROM myapp_person')
>>> for p in people:
...     print("%s is %s." % (p.first_name, p.age))
John is 37.
Jane is 42.
...

raw() 方法中傳遞參數(shù)

如果你需要參數(shù)化的查詢传货,可以向raw() 方法傳遞params參數(shù)。

>>> lname = 'Doe'
>>> Person.objects.raw('SELECT * FROM myapp_person WHERE last_name = %s', [lname])

params是存放參數(shù)的列表或字典宏娄。你可以在查詢語句中使用%s占位符问裕,或者對(duì)于字典使用%(key)占位符(key會(huì)被替換成字典中鍵為key的值),無論你的數(shù)據(jù)庫引擎是什么孵坚。這樣的占位符會(huì)被替換成參數(shù)表中正確的參數(shù)粮宛。

注意

SQLite后端不支持字典,你必須以列表的形式傳遞參數(shù)卖宠。

警告

不要在原始查詢中使用字符串格式化巍杈!

它類似于這種樣子:

>>> query = 'SELECT * FROM myapp_person WHERE last_name = %s' % lname
>>> Person.objects.raw(query)

使用參數(shù)化查詢可以完全防止sql注入,一種普遍的漏洞使攻擊者可以向你的數(shù)據(jù)庫中注入任何sql語句扛伍。如果你使用字符串格式化筷畦,早晚會(huì)受到sql輸入的攻擊。只要你記住默認(rèn)使用參數(shù)化查詢刺洒,就可以免于攻擊汁咏。

直接執(zhí)行自定義sql

有時(shí)Manager.raw()方法并不十分好用,你不需要將查詢結(jié)果映射成模型作媚,或者你需要執(zhí)行UPDATE攘滩、INSERT以及DELETE查詢。

在這些情況下纸泡,你可以直接訪問數(shù)據(jù)庫漂问,完全避開模型層赖瞒。

django.db.connection對(duì)象提供了常規(guī)數(shù)據(jù)庫連接的方式。為了使用數(shù)據(jù)庫連接蚤假,調(diào)用connection.cursor()方法來獲取一個(gè)游標(biāo)對(duì)象之后栏饮,調(diào)用cursor.execute(sql, [params])來執(zhí)行sql語句,調(diào)用cursor.fetchone()或者curser.fetchall()來返回結(jié)果行磷仰。

例如:

from django.db import connection

def my_custom_sql(self):
    cursor = connection.cursor()

    cursor.execute("UPDATE bar SET foo = 1 WHERE baz = %s", [self.baz])

    cursor.execute("SELECT foo FROM bar WHERE baz = %s", [self.baz])
    row = cursor.fetchone()

    return row

注意如果你的查詢中包含百分號(hào)字符袍嬉,你需要寫成兩個(gè)百分號(hào)字符,以便能正確傳遞參數(shù):

cursor.execute("SELECT foo FROM bar WHERE baz = '30%'")
cursor.execute("SELECT foo FROM bar WHERE baz = '30%%' AND id = %s", [self.id])

如果你使用了不止一個(gè)數(shù)據(jù)庫灶平,你可以使用django.db.connections來獲取針對(duì)特定數(shù)據(jù)庫的連接(以及游標(biāo))對(duì)象伺通。django.db.connections是一個(gè)類似于字典的對(duì)象,允許你通過它的別名獲取特定的連接

from django.db import connections
cursor = connections['my_db_alias'].cursor()
# Your code here...

通常逢享,Python DB API會(huì)返回不帶字段的結(jié)果罐监,這意味著你需要以一個(gè)列表結(jié)束,而不是一個(gè)字典瞒爬」花費(fèi)一點(diǎn)性能之后,你可以返回一個(gè)字典形式的結(jié)果侧但,像這樣:

def dictfetchall(cursor):
    "Returns all rows from a cursor as a dict"
    desc = cursor.description
    return [
        dict(zip([col[0] for col in desc], row))
        for row in cursor.fetchall()
    ]

下面是一個(gè)體現(xiàn)二者區(qū)別的例子:

>>> cursor.execute("SELECT id, parent_id FROM test LIMIT 2");
>>> cursor.fetchall()
((54360982L, None), (54360880L, None))

>>> cursor.execute("SELECT id, parent_id FROM test LIMIT 2");
>>> dictfetchall(cursor)
[{'parent_id': None, 'id': 54360982L}, {'parent_id': None, 'id': 54360880L}]

連接和游標(biāo)

連接和游標(biāo)主要實(shí)現(xiàn)PEP 249中描述的Python DB API標(biāo)準(zhǔn)矢空,除非它涉及到事務(wù)處理。

如果你不熟悉Python DB-API禀横,注意cursor.execute()中的sql語句使用占位符"%s"妇多,而不是直接在sql中添加參數(shù)。如果你使用它燕侠,下面的數(shù)據(jù)庫會(huì)在必要時(shí)自動(dòng)轉(zhuǎn)義你的參數(shù)者祖。

也要注意Django使用"%s"占位符,而不是SQLite Python綁定的"?"占位符绢彤。這是一致性和可用性的緣故七问。

Django 1.7中的改變。

PEP 249并沒有說明游標(biāo)是否可以作為上下文管理器使用茫舶。在python2.7之前械巡,游標(biāo)可以用作上下文管理器,由于魔術(shù)方法lookups中意想不到的行為(Python ticket #9220)饶氏。Django 1.7 顯式添加了對(duì)允許游標(biāo)作為上下文管理器使用的支持讥耗。

將游標(biāo)作為上下文管理器使用:

with connection.cursor() as c:
    c.execute(...)

等價(jià)于:

c = connection.cursor()
try:
    c.execute(...)
finally:
    c.close()
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市疹启,隨后出現(xiàn)的幾起案子古程,更是在濱河造成了極大的恐慌,老刑警劉巖喊崖,帶你破解...
    沈念sama閱讀 212,542評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件挣磨,死亡現(xiàn)場離奇詭異雇逞,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)茁裙,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,596評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門塘砸,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人晤锥,你說我怎么就攤上這事掉蔬。” “怎么了矾瘾?”我有些...
    開封第一講書人閱讀 158,021評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵女轿,是天一觀的道長。 經(jīng)常有香客問我霜威,道長谈喳,這世上最難降的妖魔是什么册烈? 我笑而不...
    開封第一講書人閱讀 56,682評(píng)論 1 284
  • 正文 為了忘掉前任戈泼,我火速辦了婚禮,結(jié)果婚禮上赏僧,老公的妹妹穿的比我還像新娘大猛。我一直安慰自己,他們只是感情好淀零,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,792評(píng)論 6 386
  • 文/花漫 我一把揭開白布挽绩。 她就那樣靜靜地躺著,像睡著了一般驾中。 火紅的嫁衣襯著肌膚如雪唉堪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,985評(píng)論 1 291
  • 那天肩民,我揣著相機(jī)與錄音唠亚,去河邊找鬼。 笑死持痰,一個(gè)胖子當(dāng)著我的面吹牛灶搜,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播工窍,決...
    沈念sama閱讀 39,107評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼割卖,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了患雏?” 一聲冷哼從身側(cè)響起鹏溯,我...
    開封第一講書人閱讀 37,845評(píng)論 0 268
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎淹仑,沒想到半個(gè)月后剿涮,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體言津,經(jīng)...
    沈念sama閱讀 44,299評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,612評(píng)論 2 327
  • 正文 我和宋清朗相戀三年取试,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了悬槽。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,747評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡瞬浓,死狀恐怖初婆,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情猿棉,我是刑警寧澤磅叛,帶...
    沈念sama閱讀 34,441評(píng)論 4 333
  • 正文 年R本政府宣布,位于F島的核電站萨赁,受9級(jí)特大地震影響弊琴,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜杖爽,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,072評(píng)論 3 317
  • 文/蒙蒙 一敲董、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧慰安,春花似錦腋寨、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,828評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至撒桨,卻和暖如春查刻,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背凤类。 一陣腳步聲響...
    開封第一講書人閱讀 32,069評(píng)論 1 267
  • 我被黑心中介騙來泰國打工穗泵, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人踱蠢。 一個(gè)月前我還...
    沈念sama閱讀 46,545評(píng)論 2 362
  • 正文 我出身青樓火欧,卻偏偏與公主長得像,于是被迫代替她去往敵國和親茎截。 傳聞我的和親對(duì)象是個(gè)殘疾皇子苇侵,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,658評(píng)論 2 350

推薦閱讀更多精彩內(nèi)容