數據工程師面試必備——python與數據庫的那些事

最近小夥伴在準備python數據工程師的面試,趁這個機會整理了Python與數據庫的相關問題,話很少說,直接開始。python

1、關係數據庫與非關係數據庫

SQL(Structured Query Language)數據庫,指關係型數據庫。主要表明:SQL Server、Oracle、MySQL、PostgreSQL。面試

NoSQL(Not Only SQL)泛指非關係型數據庫。主要表明:MongoDB、Redis、CouchDB。redis


關係數據庫是以表的形式存儲數據的數據庫。每一個表都有一個模式,即記錄須要的列和類型。每一個模式必須至少有一個主鍵來惟一標識該記錄。換句話說,數據庫中沒有重複的行。此外,每一個表可使用外鍵與其餘表關聯。sql


關係數據庫的一個重要方面是必須將模式中的更改應用於全部記錄。這有時會在遷移期間形成破壞,所以十分麻煩。非關係數據庫以不一樣的方式處理問題。它們本質上是無模式的,這意味着能夠用不一樣的模式和不一樣的嵌套結構保存記錄。記錄仍然能夠有主鍵,可是模式中的更改是在逐項基礎上進行的。mongodb



一個例子


咱們使用SQLite來舉例,首先,導入所需的Python庫並建立一個新數據庫
數據庫


import sqlite3

db = sqlite3.connect(':memory:')  # 使用內存數據庫
cur = db.cursor()複製代碼

接下來,建立如下三個表:後端

  1. 客戶:此表包含一個主鍵以及客戶的名字和姓氏。
  2. 物品:此表包含主鍵,物品名稱和物品價格。
  3. 購買的項目:此表將包含訂單號,日期和價格。它還將鏈接到「項目」和「客戶」表中的主鍵。
id integer PRIMARY KEY,
                firstname varchar(255),
                lastname varchar(255) )''') cur.execute('''CREATE TABLE IF NOT EXISTS Item (
                id integer PRIMARY KEY,
                title varchar(255),
                price decimal )''') cur.execute('''CREATE TABLE IF NOT EXISTS BoughtItem (
                ordernumber integer PRIMARY KEY,
                customerid integer,
                itemid integer,
                price decimal,
                CONSTRAINT customerid
                    FOREIGN KEY (customerid) REFERENCES Customer(id),
                CONSTRAINT itemid
                    FOREIGN KEY (itemid) REFERENCES Item(id) )''')複製代碼

再往表裏填充一些數據
設計模式


cur.execute('''INSERT INTO Customer(firstname, lastname) VALUES ('Bob', 'Adams'), ('Amy', 'Smith'), ('Rob', 'Bennet');''')
cur.execute('''INSERT INTO Item(title, price) VALUES ('USB', 10.2), ('Mouse', 12.23), ('Monitor', 199.99);''')
cur.execute('''INSERT INTO BoughtItem(customerid, itemid, price) VALUES (1, 1, 10.2), (1, 2, 12.23), (1, 3, 199.99), (2, 3, 180.00), (3, 2, 11.23);''')複製代碼

OK,如今每一個表中都有一些數據,如今咱們用這些數據來回答進行下一步緩存


SQL聚合函數



聚合函數是對結果集執行數學運算的函數。好比AVGCOUNTMINMAX,和SUM。通常來講,還要使用GROUP BYHAVING子句來搭配使用。拿AVG函數來講,能夠用來計算給定結果集的平均值:bash

>>> cur.execute('''SELECT itemid, AVG(price) FROM BoughtItem GROUP BY itemid''')
>>> print(cur.fetchall())
[(1, 10.2), (2, 11.73), (3, 189.995)]複製代碼

上面sql語句就提取出數據庫中購買的每一個商品的平均價格。也能夠顯示項目名稱,而不是itemid⬇️

>>> cur.execute('''SELECT item.title, AVG(boughtitem.price) FROM BoughtItem as boughtitem ... INNER JOIN Item as item on (item.id = boughtitem.itemid) ... GROUP BY boughtitem.itemid''')
...
>>> print(cur.fetchall())
[('USB', 10.2), ('Mouse', 11.73), ('Monitor', 189.995)]複製代碼

另外一個有用的聚合函數是SUM。好比可使用此功能顯示每一個客戶花費的總金額⬇️

>>> cur.execute('''SELECT customer.firstname, SUM(boughtitem.price) FROM BoughtItem as boughtitem ... INNER JOIN Customer as customer on (customer.id = boughtitem.customerid) ... GROUP BY customer.firstname''')
...
>>> print(cur.fetchall())
[('Amy', 180), ('Bob', 222.42000000000002), ('Rob', 11.23)]複製代碼

加速SQL查詢


SQL語句的執行速度取決不少因素,但主要受如下幾種因素的影響:

  • 鏈接
  • 聚合
  • 遍歷
  • 記錄

鏈接數越多,表的複雜度越高,遍歷次數也越多。在涉及多個表的數千條記錄上執行屢次鏈接很是麻煩的,由於數據庫還須要緩存中間結果,因此真的須要的話就要考慮如何增長內存大小。

執行速度還受數據庫中是否存在索引的影響。索引很是重要,它能夠快速搜索表並找到查詢中指定列的匹配項。索引以增長插入時間和一些存儲爲代價對記錄進行排序。能夠組合多個列以建立單個索引。

調試SQL查詢


大多數數據庫都包含一個EXPLAIN QUERY PLAN描述數據庫執行查詢的步驟的。對於SQLite,能夠經過EXPLAIN QUERY PLANSELECT語句前面添加來啓用此功能:

>>> cur.execute('''EXPLAIN QUERY PLAN SELECT customer.firstname, item.title, ... item.price, boughtitem.price FROM BoughtItem as boughtitem ... INNER JOIN Customer as customer on (customer.id = boughtitem.customerid) ... INNER JOIN Item as item on (item.id = boughtitem.itemid)''')
...
>>> print(cur.fetchall())
[(4, 0, 0, 'SCAN TABLE BoughtItem AS boughtitem'),
(6, 0, 0, 'SEARCH TABLE Customer AS customer USING INTEGER PRIMARY KEY (rowid=?)'),
(9, 0, 0, 'SEARCH TABLE Item AS item USING INTEGER PRIMARY KEY (rowid=?)')]複製代碼

該查詢嘗試列出全部購買商品的名字,商品標題,原始價格和購買價格。而該查詢計劃本應這樣寫⬇️

SCAN TABLE BoughtItem AS boughtitem
SEARCH TABLE Customer AS customer USING INTEGER PRIMARY KEY (rowid=?)
SEARCH TABLE Item AS item USING INTEGER PRIMARY KEY (rowid=?)複製代碼

2、有關非關係數據庫的問題

在第一節已經說明了關係數據庫和非關係數據庫之間的差別,並將SQLite與Python結合使用,本節主要講NoSQL。


以MongoDB爲例


首先安裝在python中使用MongoDB相關的庫


$ pip install pymongo複製代碼

再建立數據庫並插入一些數據⬇️


import pymongo

client = pymongo.MongoClient("mongodb://localhost:27017/")

# Note: This database is not created until it is populated by some data
db = client["example_database"]

customers = db["customers"]
items = db["items"]

customers_data = [{ "firstname": "Bob", "lastname": "Adams" },
                  { "firstname": "Amy", "lastname": "Smith" },
                  { "firstname": "Rob", "lastname": "Bennet" },]
items_data = [{ "title": "USB", "price": 10.2 },
              { "title": "Mouse", "price": 12.23 },
              { "title": "Monitor", "price": 199.99 },]

customers.insert_many(customers_data)
items.insert_many(items_data)複製代碼

能夠發現MongoDB將數據記錄存儲在collection中,等價於Python中的字典列表。


使用MongoDB查詢


首先嚐試複製BoughtItem表,就在SQL中所作的同樣。先向客戶追加一個新字段。MongoDB的文檔指定關鍵字操做符集能夠用來更新一條記錄,而沒必要寫全部現有的字段:


bob = customers.update_many(
        {"firstname": "Bob"},
        {
            "$set": {
                "boughtitems": [
                    {
                        "title": "USB",
                        "price": 10.2,
                        "currency": "EUR",
                        "notes": "Customer wants it delivered via FedEx",
                        "original_item_id": 1
                    }
                ]
            },
        }
    )複製代碼

實際上,能夠稍微更改架構來更新另外一個客戶:


amy = customers.update_many(
        {"firstname": "Amy"},
        {
            "$set": {
                "boughtitems":[
                    {
                        "title": "Monitor",
                        "price": 199.99,
                        "original_item_id": 3,
                        "discounted": False
                    }
                ]
            } ,
        }
    )
print(type(amy))  # pymongo.results.UpdateResult複製代碼

能夠像在SQL中同樣執行查詢。首先,能夠建立一個索引


>>> customers.create_index([("name", pymongo.DESCENDING)])複製代碼

而後,就能夠更快的檢索按升序排序的客戶名稱:


>>> items = customers.find().sort("name", pymongo.ASCENDING)複製代碼

還能夠遍歷並打印購買的物品:


>>> for item in items:
...     print(item.get('boughtitems'))    
...
None
[{'title': 'Monitor', 'price': 199.99, 'original_item_id': 3, 'discounted': False}]
[{'title': 'USB', 'price': 10.2, 'currency': 'EUR', 'notes': 'Customer wants it delivered via FedEx', 'original_item_id': 1}]複製代碼

甚至能夠在數據庫中檢索惟一的名字列表:


>>> customers.distinct("firstname")
['Bob', 'Amy', 'Rob']複製代碼

如今咱們已經知道數據庫中客戶的名稱,能夠建立一個查詢檢索有關他們的信息:

>>> for i in customers.find({"$or": [{'firstname':'Bob'}, {'firstname':'Amy'}]}, 
...                                  {'firstname':1, 'boughtitems':1, '_id':0}):
...     print(i)
...
{'firstname': 'Bob', 'boughtitems': [{'title': 'USB', 'price': 10.2, 'currency': 'EUR', 'notes': 'Customer wants it delivered via FedEx', 'original_item_id': 1}]}
{'firstname': 'Amy', 'boughtitems': [{'title': 'Monitor', 'price': 199.99, 'original_item_id': 3, 'discounted': False}]}複製代碼

寫成SQL語句就是

SELECT firstname, boughtitems FROM customers WHERE firstname LIKE ('Bob', 'Amy')複製代碼

NoSQL與SQL


若是架構是不斷變化的(例如財務監管信息),則NoSQL能夠修改記錄並嵌套相關信息。想象一下,若是咱們有八個嵌套順序,那麼在SQL中必須執行的鏈接數須要多少。可是如今,若是須要運行報告,提取有關該財務數據的信息並推斷結論該怎麼辦?在這種狀況下,就須要運行復雜的查詢,而且SQL在這方面每每會更快。

注意: SQL數據庫(尤爲是PostgreSQL)還發布了一項功能,該功能容許將可查詢的JSON數據做爲記錄的一部分插入。雖然這能夠結合兩個方面的優點,但速度可能並無很好。而從NoSQL數據庫查詢非結構化數據比從PostgreSQL中的JSON類型列查詢JSON字段要快。

因爲存在各類各樣的數據庫,每一個數據庫都有其自身的功能,所以,還須要具體分析,以決定使用哪一個數據庫。

3、有關緩存數據庫的問題

緩存數據庫保存常常訪問的數據。它們與主要的SQL和NoSQL數據庫並存。他們的目標是減輕負載並更快地處理請求。

上一節已經爲長期存儲解決方案介紹了SQL和NoSQL數據庫,可是更快,更直接的存儲又如何呢?數據工程師又如何更改從數據庫檢索數據的速度?典型的Web應用程序常常檢索經常使用數據,例如用戶的我的資料或姓名。若是全部數據都包含在一個數據庫中,則數據庫服務器得到的命中次數將超過最高且沒必要要。所以,須要更快,更直接的存儲解決方案。

儘管這減小了服務器負載,但也給數據工程師,後端團隊和DevOps團隊帶來了兩個麻煩。首先,如今須要一個讀取時間比主SQL或NoSQL數據庫更快的數據庫。可是,兩個數據庫的內容必須最終匹配。

因此收到請求時,首先要檢查緩存數據庫,而後是主數據庫。這樣,能夠防止任何沒必要要和重複的請求到達主數據庫的服務器。因爲緩存數據庫的讀取時間較短,所以還能讓性能提高。

以Redis爲例


首先用pip安裝相關的庫


$ pip install redis複製代碼

如今,考慮一個簡單的例子:從ID中獲取用戶名的請求:


import redis
from datetime import timedelta

r = redis.Redis()

def get_name(request, *args, **kwargs):
    id = request.get('id')
    if id in r:
        return r.get(id)  
    else:
        name = 'Bob'
        r.setex(id, timedelta(minutes=60), value=name)
        return name複製代碼

此代碼使用id來檢查名稱是否在Redis中。若是不是,則使用過時時間來設置名稱,如今,若是面試官問這段代碼是否有問題,回答應該是沒有異常處理!數據庫可能有不少問題,例如鏈接斷開,所以永遠要考慮異常捕捉。

4、結束語

有關數據庫相關的問題還有設計模式、ETL概念或者是大數據中的設計模式。這些就留到之後再聊。

相關文章
相關標籤/搜索