Menjalankan Raw Query pada Odoo

Odoo sudah memiliki ORM (object relational mapping) yang cukup mudah digunakan. Dengan menggunakan ORM kita bisa memanipulasi data pada database dengan sangat mudah. Sayangnya ORM pada semua framework, tidak terkecuali odoo memiliki kelemahan, yaitu lambat.

Menggunakan ORM untuk mengolah data yang besar dalam satu kali proses, misal saat membuat report, kadang membawa kita pada suatu dilema. Jika kita menggunakan ORM pekerjaan bisa cepat selesai dari sisi programmer, tapi ada kemungkinan pada sisi user prosesnya bisa memakan waktu cukup lama. Tentu saja hal ini bakal mempengaruhi pengalaman pengguna.

Untuk itu tidak ada salahnya jika kita mencoba menggunakan Raw Query pada proses yang kira-kira bakal lambat jika kita menggunakan ORM, seperti saat membuat report tadi.

Raw Query yang saya maksud disini adalah perintah SQL, seperti perintah insert, update, delete dan select yang biasa digunakan untuk manipulasi database. Raw Query mengabaikan hak akses odoo. Misal pada suatu proses insert jika menggunakan ORM odoo akan mengecek user tersebut punya hak akses create atau tidak. Jika tidak punya hak akses maka akan muncul peringatan atau pesan error, sedangkan jika menggunakan Raw Query hak akses tersebut akan diabaikan, jadi gunakan dengan bijak. Berikut ini adalah tutorial bagaimana membuat raw query pada odoo.

Untuk menggunakan raw query kita cukup memanggil method execute yang ada pada class Cursor. Class atau object Cursor sendiri bisa di akses dari semua model odoo dengan kode seperti dibawah ini

self.env.cr

Atau jika ingin diakses dari controller bisa menggunakan kode seperti dibawah ini

from odoo.http import request

request.env.cr

Berikut ini adalah deklarasi method execute, yang ditulis pada baris ke-228 pada source code odoo.

@check
def execute(self, query, params=None, log_exceptions=None):

Terlihat method execute tersebut memiliki 3 parameter. Parameter pertama adalah SQL Query yang ingin kita jalankan, parameter ini wajib kita isi.

Parameter kedua bersifat opsional, tidak wajib diisi. Parameter ini harus berupa tuple, list atau dictionary. Parameter ini digunakan untuk memasukkan data yang bersifat dinamis pada parameter pertama untuk mencegah sql injection.

Parameter ketiga juga bersifat opsional. Digunakan sebagai penanda bahwa kita ingin mencatat pesan error pada log atau tidak jika query yang kita lakukan menyebabkan error.

Berikut ini adalah contoh memasukkan data ke model res.partner menggunakan raw query. Ingat tanda titik (.) pada nama model pada file python akan diterjemahkan menjadi tanda underscore (_) pada table database.

query = """
    INSERT INTO res_partner (name, email)
    VALUES ('Ngasturi', 'ngasturi@gmail.com')
"""

self.env.cr.execute(query)

Saat menggunakan data yang bersifat dinamis, kita tidak disarankan untuk menggabungkan antara query dan value menggunakan operator plus (+) seperti dibawah ini

def insert_new_partner(self, name, email):
    query = """
        INSERT INTO res_partner (name, email)
        VALUES ('""" + name + """', '""" + email + """')"""

    self.env.cr.execute(query)

Atau menggunakan string formating seperti dibawah ini

def insert_new_partner(self, name, email):
    query = """
        INSERT INTO res_partner (name, email)
        VALUES ('%s', '%s')
    """ % (name, email)

    self.env.cr.execute(query)

Kedua kode diatas tetap jalan, tetapi tidak disarankan karena rawan sql injection. Cara yang disarankan jika menggunakan data dinamis adalah memasukkan data tersebut sebagai parameter kedua pada method execute. Tetapi harus dibungkus sebagai tuple, list atau dictionary, seperti pada kode dibawah ini.

def insert_new_partner(self, name, email):
    query = """
        INSERT INTO res_partner (name, email)
        VALUES (%s, %s)
    """
    params = (name, email)

    self.env.cr.execute(query, params)

Saat kita menggunakan parameter kedua untuk mengisi value / data yang dinamis, kita tidak perlu menggunakan single quote atau double quote untuk membungkus text, jadi lebih mudah. Pada kode diatas kita menggunakan tuple sebagai parameter kedua, tidak ada perbedaan saat kita menggunakan tuple atau list sebagai parameter kedua, berikut ini adalah contoh jika menggunakan list.

def insert_new_partner(self, name, email):
    query = """
        INSERT INTO res_partner (name, email)
        VALUES (%s, %s)
    """
    # bedanya hanya disini
    params = [name, email]

    self.env.cr.execute(query, params)

Yang berbeda adalah saat kita menggunakan dictionary

def insert_new_partner(self, data):
    """
        anggap saja nilai parameter data adalah seperti ini

        data = {'name': 'Ngasturi', 'email': 'ngasturi@gmail.com'}
    """

    query = """
        INSERT INTO res_partner (name, email)
        VALUES (%(name)s, %(email)s)
    """

    self.env.cr.execute(query, data)

Selanjutnya bagaimana jika kita ingin mengambil data dari database menggunakan perintah select ?

Kabar baiknya untuk menjalankan raw query baik perintah insert, delete, update dan select kita hanya perlu satu method, yaitu method execute diatas. Jika raw query yang kita jalankan return suatu nilai, baru kita membutuhkan method lain dari class Cursor untuk menangkap nilai tersebut. Berikut ini adalah contoh perintah insert yang return id dan bagaimana cara menangkap nilai id tersebut.

def insert_new_partner(self, data):
    """
        anggap saja nilai parameter data adalah seperti ini

        data = {'name': 'Ngasturi', 'email': 'ngasturi@gmail.com'}
    """

    query = """
        INSERT INTO res_partner (name, email)
        VALUES (%(name)s, %(email)s) returning id
    """

    self.env.cr.execute(query, data)
    result = self.env.cr.dictfetchall()
    print(result)

Hasilnya akan terlihat seperti ini

[{'id': 51}]

Ada 3 method dari class Cursor yang bisa kita gunakan untuk menangkap nilai dari raw query. Yang pertama adalah dictfetchone. Berikut ini adalah contohnya

query = """
    SELECT 
        partner_id, 
        sum(so_count) AS so_count, 
        sum(po_count) AS po_count
    FROM (
        SELECT 
            partner_id, 
            count(id) AS so_count,
            0 AS po_count
        FROM sale_order
        GROUP BY partner_id
        UNION ALL
        SELECT 
            partner_id, 
            0 AS so_count,
            count(id) AS po_count
        FROM purchase_order
        GROUP BY partner_id
    ) AS sub_query
    GROUP BY partner_id
"""

self.env.cr.execute(query)
result = self.env.cr.dictfetchone()
print(result)

Berapapun hasil dari query yang dijalankan hanya akan menampilkan satu baris data saja. Hasilnya akan seperti ini

{'partner_id': 11, 'so_count': 13.0, 'po_count': 1.0}

Selanjutnya adalah dictfetchmany, dengan method ini kita bisa mengatur berapa baris data yang akan ditampilkan, misal 3 baris seperti pada kode dibawah ini

query = """
    SELECT 
        partner_id, 
        sum(so_count) AS so_count, 
        sum(po_count) AS po_count
    FROM (
        SELECT 
            partner_id, 
            count(id) AS so_count,
            0 AS po_count
        FROM sale_order
        GROUP BY partner_id
        UNION ALL
        SELECT 
            partner_id, 
            0 AS so_count,
            count(id) AS po_count
        FROM purchase_order
        GROUP BY partner_id
    ) AS sub_query
    GROUP BY partner_id
"""

self.env.cr.execute(query)
result = self.env.cr.dictfetchmany(3)
print(result)

berbeda dengan dictfetchone hasilnya akan dibungkus dalam sebuah list, seperti ini

[
    {'partner_id': 11, 'so_count': 13.0, 'po_count': 1.0}, 
    {'partner_id': 9, 'so_count': 0.0, 'po_count': 2.0}, 
    {'partner_id': 15, 'so_count': 1.0, 'po_count': 0.0}
]

Terakhir adalah method dictfetchall. Sesuai namanya odoo akan menampilkan semua hasil query.

query = """
    SELECT 
        partner_id, 
        sum(so_count) AS so_count, 
        sum(po_count) AS po_count
    FROM (
        SELECT 
            partner_id, 
            count(id) AS so_count,
            0 AS po_count
        FROM sale_order
        GROUP BY partner_id
        UNION ALL
        SELECT 
            partner_id, 
            0 AS so_count,
            count(id) AS po_count
        FROM purchase_order
        GROUP BY partner_id
    ) AS sub_query
    GROUP BY partner_id
"""

self.env.cr.execute(query)
result = self.env.cr.dictfetchall()
print(result)
Tulisan Serupa

Leave a Reply

Your email address will not be published. Required fields are marked *