django sql 操作
* db 迁移文件命名
python manage.py showmigrations 查看迁移状态
遇到没有 X 迁移的文件,但实际上已经迁移不需要再操作的,执行:(0053_librarygroup_permission 是具体的迁移文件名)
python manage.py migrate your_app_name 0053_librarygroup_permission --fake【fake忽略,在db的django_migrations表中会记录已迁移,后续不再操作】
尽量一个模型各自迁移各自的文件,避免文件之前产生关联,后续映射db遇到奇怪的问题,比如对表修改字段的新增或者减少,独立迁移自己的文件,不和别的表一起生成迁移文件,减少关联。
----------------------------------------
python manage.py makemigrations your_app_name 把实体模型生成迁移文件
python manage.py migrate 把迁移文件映射到 db
【有时可以使用:python manage.py makemigrations --merge】
【如果遇到执行后 db 没有生成 table,找到 db 的 django_migrations 表,把里面对应要生成的table的生成记录删除掉再执行即可】
=======================================================================================
* 查询 db 数据某个字段,然后在数据库层面累加
Book.objects.filter(acno=acno).update(download_count=F('download_count')+1)
* 截取字符串
使用字符串的切片功能来截取字符串的第一个字符。具体方法如下: string = "Hello, World!" first_char = string[0] print(first_char) 在这个例子中,string[0] 会返回字符串 "Hello, World!" 的第一个字符 "H"。 截取多个字符,例如前几个字符,可以使用类似的方法: # 截取前 5 个字符 first_five_chars = string[:5] print(first_five_chars) # 输出 "Hello" 这里 string[:5] 会返回字符串 "Hello, World!" 的前五个字符 "Hello"。
# 移除末尾指定字符
keyword = '1,2,3,4,5,'
keyword = keyword.rstrip(',')
print(keyword) # 输出 '1,2,3,4,5'
* 非空判断
if param is None: //只判断 null 值 if not param: //判断 null 值同时判断字段是否空值
* bool 类型,取反写法:
flag = False if flag: //正常布尔判断 if not flag: //取反判断
* if - else if - else 写法:
if self.returned_at: return "已還書" elif self.overdue_settled: return "借閱中(已繳部分金額)" else: return "借閱中(已繳部分金額)"
* 异常写法
try: histories = histories.filter(returned_at__date=returned_at) except Exception as e: print("查询异常:",e)
* 时间获取
获取当前时间: from datetime import datetime current_time = datetime.now() //精确到时分秒【对应 db 的 timestamptz 类型】 current_date = datetime.now().date() //精确到天的时间【对应 db 的 date 类型,精确到天或者时分秒的变量,在 filter 中都不可混用】
today = date.today() //直接获取到天的时间【对应 db 的 date 类型,和以上 datetime.now().date() 结果一致,只是效率更高】
自定义指定时间: time = datetime(2024, 4, 14, 0, 0, 0) time = date(2024, 4, 14)
字符串和时间之间的转换,假如有字符串时间为:date_str = %Y-%m-%d %H:%M:%S
字符串转成时间:【strptime 方法,str 字符串,time 时间,p 表示“解析”,解析成时间】 from datetime import datetime date_time = datetime.strptime(date_str, '%Y-%m-%d %H:%M:%S') 如果想提取到天的时间,则:date_time_day = datetime.strptime(date_str, '%Y-%m-%d %H:%M:%S').date() 如果想提取到秒的时间,则:date_time_day = datetime.strptime(date_str, '%Y-%m-%d %H:%M:%S').second() 时间转成字符串:【strftime 方法,str 字符串,time 时间,f 表示“格式化”,格式换成字符串】 str_now = now.strftime('%Y-%m-%d %H:%M:%S')
或者:
date_time = datetime.strptime(date_str, '%Y-%m-%d %H:%M:%S') //str -> time
date_str = datetime.strftime(date_time, '%Y-%m-%d %H:%M:%S') //time -> str
两个时间求之间间隔的,直接相减:
date = dateEnd - dateStart
然后通过 .days 转成相差几天,如:date.days【相差几秒:date.seconds】,如果要求几年,几月等,也是通过转成天,再去计算
* 判断字符串中是否包含某个字符
if 'xx' not in username: //检查 username 是否不包含 xx 片段(如果要检查包含,去掉 not 即可) if not username.startswith('xx'): //检查 username 前缀是否不包含 xx 片段(如果包含,去掉 not 即可)
* 大小写转换:
str.upper() //转成大写 str.lower() //转成小写
* 查询操作
History.objects.get(pk=xx) 查询一条数据【可以使用 pk 来代替表中的主键字段,也可以直接写主键字段名】 History.objects.filter(pk=xx) 查询一个集合
* and 操作【注意:判断 null 是直接根据 = 后面的 False 或者 True 来决定的,__isnull 是固定的,并没有 __isnotnull 的写法】
histories = histories.filter(returned_at__isnull=False,returned_at='2') 这句类型 sql 里面的 wherer returned_at is not null and returned_at = '2'
* or 操作
histories = histories.filter(Q(returned_at__isnull=True) | Q(returned_at='')) 这句类型 sql 里面的 wherer returned_at is null or returned_at = ''
【查询 sql 逻辑“并且”使用,逗号;“或者”使用 Q()|Q();在程序里面,“并且”使用 and,“或者”使用 or 】
并且 if isinstance(result, list) and isinstance(result, set): 或者 if isinstance(result, list) or isinstance(result, set):
* 模糊查询【同时不区分大小写,如果要区分大小写,去掉前面的 i 即可,即:contains,startswith,endswith】
students = User.objects.filter(email__icontains=key) --> sql where email like '%key%' students = User.objects.filter(email__istartswith=key) --> sql where email like 'key%' students = User.objects.filter(email__iendswith=key) --> sql where email like '%key'
* 精准匹配
students = User.objects.filter(email=key) --> sql where email = 'key' (或者:students = User.objects.filter(email__exact=key),exact 表示区分大小写,在英文查询的时候有用,否则和上一句作用完全一样) (所以:students = User.objects.filter(email__iexact=key),不区分大小写的精确匹配)
* django 很多查询操作,都是在字段后面使用两个下划线 __ 来操作的,比如:【包含等于使用 filter ,不包含不等于使用:exclude】
in 操作:User.objects.filter(email__in=[a,b]) --> where email in (a,b) not in 操作:User.objects.exclude(email__in=[a,b]) --> where email not in (a,b) = 操作:User.objects.filter(email=a) != 操作:User.objects.exclude(email=a) >= 操作:User.objects.filter(age__gte=a) <= 操作:User.objects.filter(age__lte=a)
Histroy 关联了 book 表,利用 book 表的 acno 字段不区分大小写查询
history = History.objects.get(book__acno__iexact=acno) //两个 __ 是链表里面的字段,history 表和 book 表链表,同时 book 表的 acno 字段参与判断 Histroy 单表利用自己表的 book_acno 字段不区分大小写查询 history = History.objects.get(book_acno__iexact=acno) //一个 _ 是单表里面的字段,book_acon 是 history 表里面的字段,参与判断
* 正序排列和倒叙排列
history = History.objects.filter(book=acno).order_by('create_ts') 类型 sql 中的 where book = acno order by create_ts history = History.objects.filter(book=acno).order_by('-create_ts') 类型 sql 中的 where book = acno order by create_ts desc
* 多表链表查询
histories = History.objects.select_related('user', 'book').order_by('-created_at') (如果要添加查询条件:History.objects.filter(xx=xx).select_related('user', 'book').order_by('-created_at') ,理论上 filter 放到前面或者后面都可以,但建议放在前面提高查询效率) --> SELECT history.* FROM history INNER JOIN user ON history.user_id = user.id INNER JOIN book ON history.book_id = book.id ORDER BY history.created_at DESC;
* 指定返回字段:
User.objects.filter(age__gt=18).values('name', 'age') 类似: select name,age from user where age > 18
* 事务写法:
from django.db import transaction as db_transaction with db_transaction.atomic(): # 执行数据库操作 # 如果有异常抛出,事务将会回滚,之前的操作都会被撤销 # 如果没有异常抛出,事务会提交,将所有操作保存到数据库中
或者使用注解的方式:@transaction.atomic
* 定义集合赋值操作
# 定义一个空集合 my_set = set() # 给集合放入数据 my_set.add(1) my_set.add(2) my_set.add(3) print(my_set) # 输出: {1, 2, 3}
或者:
my_set = {1, 2, 3}
* 定义一个数组赋值操作:
# 定义一个空列表 my_list = [] # 向列表添加数据 my_list.append(1) my_list.append(2) my_list.append(3) print(my_list) # 输出: [1, 2, 3] 或者: my_list = [1, 2, 3]
* 从一个对象集合中获取某一个字段,并且组成一个数组:
user_ids = [h['user_id'] for h in historys] //获取 historys 对象集合中的每个h对象.user_id 字段,组成 user_ids 数组
或者如下写法:
user_ids = [h.user_id for h in historys] //如果要组成集合,[]改成{}即可
* 从 def xxx 方法中,从 request 获取数据,有两种方式:
第一种: acno = request.data['acno'] //标准的字典对象获取方式,但是不能设置默认值 第二种: acno = request.data.get('acno', None) //可以获取的时候设置默认值
* new 一个对象保存的方式:
menu = Menu() menu.status = 99 menu.remark = menus_json menu.save()
或者直接:
menu = Menu.objects.create(status=99, remark=menus_json)
* 返回对象换个字段名写法:
# 要被换的字段,全程只在 def get_xxx 方法里面被 return 出来,其他地方不再定义它
class GlobalMenuSerializer(serializers.ModelSerializer):
# 序列化器就是要返回给前端的json数据 # remark = serializers.CharField(required=True, allow_null=False,allow_blank=False) menu_json = serializers.SerializerMethodField() //这里使用新字段名,使用 SerializerMethodField
class Meta: model = Menu fields = ['menu_json'] //返回使用新字段 def get_menu_json(self,obj): //固定写法 get_<新字段名> return obj.remark
* 两表外键关联写法:
# User 表
class User(models.Model):
name = models.CharField(max_length=64,null=True,blank=True,verbose_name="资源名称")
# Book 表
class Book(models.Model):
name = models.CharField(max_length=64,null=True,blank=True,verbose_name="资源名称") user = models.ForeignKey( # 这里就是 user 表的主键外键关联,到时候 db 会映射成 user_id User, on_delete=models.CASCADE, # 当用户记录被删除时,删除对应的书籍记录 related_name='books', # 可选:定义反向关系的名称 editable=False, # 如果不允许在 admin 或其他地方编辑这个字段 default=uuid.uuid4, # 可选:默认生成一个新的 UUID )
利用外键的好处,是可以直接使用 django 的写法,站在 User 的角度,在 Book 中定义好了 related_name='books' ,就可以直接使用:
user = User.objects.get(条件)
books = user.books.all() # 通过这种方式直接查到 user 下关联的所有 book
* 将一个对象集合中的某一个属性收集成为一个数组或者集合
# group_ids 允许有重复数据 userGroups = UserLibraryGroup.objects.filter(user_id=user_id) group_ids = [group.group_id for group in userGroups] # group_ids 都是非重复数据 userGroups = UserLibraryGroup.objects.filter(user_id=user_id) group_ids = {group.group_id for group in userGroups}
* 判断一个参数是否为 “列表,元组,集合” 等方法:
if isinstance(result, list): # 如果 result 是列表类型的话,执行对应的逻辑 pass if isinstance(result, tuple): # 如果 result 是元组类型的话,执行对应的逻辑 pass if isinstance(result, set): # 如果 result 是集合类型的话,执行对应的逻辑 pass
* 枚举
class RequestStatus(models.IntegerChoices): WISHLIST = 1, 'Wish List' REQUESTED = 2, 'Requested' ORDERED = 3, 'Ordered' REGISTERED = 4, 'Registered' CANCELLED = 5, 'Cancelled' 要获取中间的数字:print(RequestStatus.ORDERED.value) 要获取后面的单词:print(RequestStatus.ORDERED.label) 直接使用:print(RequestStatus.ORDERED),输出:(3, 'Ordered')
* 将字段(json对象)压缩成关键字传递【print_person_info(**person_info),使用 ** 直接传递对象】
假設有一個字典 person_info 包含了個人的姓名和年齡資訊: person_info = { 'name': 'Alice', 'age': 30 } 現在如果我們有一個函數 print_person_info 接受兩個參數 name 和 age,我們可以使用 ** 將 person_info 的內容解壓縮為函數的參數: def print_person_info(name, age): print(f"Name: {name}, Age: {age}") # 使用 ** 解壓縮字典作為函數的參數 print_person_info(**person_info) 這樣,print_person_info(**person_info) 的效果等同於 print_person_info(name='Alice', age=30)。
* 原生 sql 写法
def execute_custom_sql(book_title_search, page, size): query = """ SELECT a.* FROM library_book a WHERE a.title LIKE %(book_title)s LIMIT %(size)s OFFSET %(offset)s; """ # 计算分页参数 offset = (page - 1) * size # 执行查询 with connection.cursor() as cursor: cursor.execute(query, { 'book_title': f'%{book_title_search}%', 'size': size, 'offset': offset }) results = cursor.fetchall() return results
可以使用 %s 当作占位符,然后在 cursor.execute(query,xxxx) 第二个参数这里使用中括号 [ xxx,xxx,xxx ] 按照顺序传入替换也可以,或者如上使用 %(xxx)s 具体值来替换
* 使用 select_related 和 prefetch_related 链表查询的时候,对于
(1)一对一:select_related 使用这个的时候,里面放置的是外键的字段名 (2)一对多(或者多对多):prefetch_related 使用这个的时候,里面放置的是外键定义的反向关联名:related_name = 'xx'
* 序列化器中传递单个对象参数写法:
在视图层: self.data = BookDetailSerializer(book,context={'user': user}).data 在序列化器层: def get_read_progress(self,obj): # 獲取當前用戶 user = self.context.get('user')
* 序列化器中传递多个对象参数写法:
# 将子评论的层级关系传递给序列化器 context = {'children': children_dict} merged_context = {**context, 'user': request.user} # self.data = CommentBookSerializer(root_comments, many=True, context=context).data # self.data = CommentBookSerializer(commentBooks,context={'user': request.user},many=True).data self.data = CommentBookSerializer(root_comments,context=merged_context,many=True).data
相减取绝对值
def get_overdue_days(self,obj): overdue_days = date.today()-obj.due_date # abs取絕對值 return abs(overdue_days.days)
* 类似 java Map 的操作
def favorite_menu_book_type(books): # 初始化统计字典【单个Map,key 值和 value 值都用】 book_type_counts = { 'BOOk': 0, 'ER': 0, 'EBOOK': 0 } # 遍历所有书籍,统计每种类型的书籍数量 for book in books: book_type_name = book.book_type.name if book_type_name in ['ER', 'EBOOK']: book_type_counts[book_type_name] += 1 else: book_type_counts['BOOk'] += 1 # 构造最终结果【把单个Map转成集合Map,key值和value值分成两个字段使用】 result = [ {'bookType': book_type, 'count': count} for book_type, count in book_type_counts.items() ] return result
* 直接根据“年分”(或者“月份”,“日期”)来查询数据:【create_ts__month=11(月份), create_ts__day=24(日期)】
favoriteBook = FavoriteBook.objects.filter(create_ts__year=2024)