数据工程师面试必备——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概念或者是大数据中的设计模式。这些就留到之后再聊。

相关文章
相关标签/搜索