WHERE 절의 subquery

item = Item.objects.all()
base = Base.objects.filter(no__in=Subquery(item.values('no')))
== 동일 쿼리
SELECT *
FROM base
WHERE no IN (SELECT no FROM item)

SELECT 절의 subquery

item = Item.objects.all()
base = Base.objects.annotate(no=Subquery(item.values('no')))

== 동일 쿼리
SELECT *, (SELECT no FROM item) AS "no"
FROM base


더 복잡하게

먼저 위에서 그냥 예시를 들었던 모델은 아래와 같이 정의되었다고 가정합니다.

class Base(models.Model):
    no = models.BigAutoField(primary_key=True)
	cnt = models.IntegerField()
    name = models.CharField(max_length=100)


class Items(models.Model):
    no = models.BigAutoField(primary_key=True)
    name = models.CharField(max_length=100)
    base_no = models.ForeignKey(Base, db_column='base_no')


subquery 안에서 외부 테이블의 pk를 비교하여 같을 때만 반환해주는 예시는 아래와 같이 표현할 수 있습니다.

from django.db.models import OuterRef, Subquery


item_qs = Items.objects.filter(
    base_no=OuterRef('pk')
)
qs = Base.objects.annotate(
    item_name=Subquery(
        item_qs.values('name')[:1]
    )
)


위의 결과를 쿼리로 변환하면 아래와 같이 의도한대로 나옵니다.

SELECT "item_base"."no",
       "item_base"."name",

  (SELECT U0."name"
   FROM "item_items" U0
   WHERE U0."base_no" = ("item_base"."no")
   LIMIT 1) AS "item_name"
FROM "item_base"


만약 비교하고 싶은 컬럼이 pk가 아닌 일반 컬럼일 경우, 일반 컬럼을 작성하여 비교할 수 있습니다.

from item.models.base import Items, Base
from django.db.models import OuterRef, Subquery


item_qs = Items.objects.filter(
    base_no=OuterRef('cnt')
)
qs = Base.objects.annotate(
    item_name=Subquery(
        item_qs.values('name')[:1]
    )
)


-- 동일 쿼리
SELECT "item_base"."no",
       "item_base"."cnt",
       "item_base"."name",

  (SELECT U0."name"
   FROM "item_items" U0
   WHERE U0."base_no" = ("item_base"."cnt")
   LIMIT 1) AS "item_name"
FROM "item_base"


파이썬에서 문자열 포맷팅 방식은 다양합니다. 아래에서 다양한 방법과 사용법을 설명하겠습니다.

% operator (오래된 방식)

C에서 prinf 스타일로 사용한 적이 있으면 익숙한 방식입니다. python3 이전의 방식으로 편리하지만 타입을 정확하게 알고 작성해야 한다는 단점이 있습니다.

test = 'Hello %s' % 'Bob'
print(test)
# Hello Bob


만약 데이터 타입이 integer일 경우 아래와 같이 %s로 추가합니다.

test = 'age: %i' % 111
print(test)

# age: 111


만약 포맷팅하고자 하는 데이터 타입이 다를 경우, 아래와 같이 에러를 뱉게 됩니다.

test = 'age: %i' % '111'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: %i format: a number is required, not str

왜 좋지 않다고 할까?

바로 위에서 설명한 것과 같이 타입을 정확하게 알고 사용해야 한다는 단점도 있지만 포맷팅할 문자열이 길어지면 곧바로 더러워지는 것을 볼 수 있습니다.

first_name = 'Eric'
last_name = 'Idle'
age = 74
profession = 'comedian'
affiliation = 'Monty Python'
'Hello, %s %s. You are %s. You are a %s. You were a member of %s.' % (first_name, last_name, age, profession, affiliation)
# Hello, Eric Idle. You are 74. You are a comedian. You were a member of Monty Python.

str.format

파이썬3 이후부터 새로운 포맷팅을 제시합니다.

test = 'Hello {}'.format('Bob')
print(test)
# Hello Bob


파이썬3 에서도 % operator를 지원하지만 공식문서에서는 권장하지 않는다고 나와 있습니다. (새로나온 str.format이 너무 좋아서..)

format 메소드는 아래와 같이 여러 형태로 지원됩니다.

test = 'Hello {name}. count: {count}'
test.format(name='Bob', count=5)
# 'Hello Bob. count: 5'


test = 'Hello {1}. count: {0}'
test.format(10, 'Jim')

# 'Hello Jim. count: 10'

왜 좋지 않을까?

% operator보다는 읽기 좋지만 여러 매개변수와 긴 문자열을 처리할 때 장황하다는 것을 확인할 수 있습니다.

first_name = 'Eric'
last_name = 'Idle'
age = 74
profession = 'comedian'
affiliation = 'Monty Python'
print(('Hello, {first_name} {last_name}. You are {age}. ' + 
       'You are a {profession}. You were a member of {affiliation}.') \
       .format(first_name=first_name, last_name=last_name, age=age, \
               profession=profession, affiliation=affiliation))

# Hello, Eric Idle. You are 74. You are a comedian. You were a member of Monty Python.


.format()에 전달할 변수가 길다면 dictionary 형태로 담아서 .format(**some_dict)로 압축해서 전달할 수 있습니다. 파이썬 3.6이상에서는 이러한 문제를 해결하기 위해 좀 더 직관적인 방법을 제시합니다.

f-string

f-string은 파이썬 3.6 이상 버전에서만 지원하는 문법입니다. str.format이 % operator에 비해 강력하고 사용하기 쉽지만 f-string은 더욱더 간편해졌습니다.

name = 'Bob'
test = f'Hello {name}'
print(test)


# Hello Bob


str.format과 다르게 정수끼리의 산술 연산도 지원합니다.

a = 2
b = 3
test = f'sum: {a+b}'
print(test)

# sum: 5


f-string 선언을 먼저 한 후, 변수를 나중에 선언하는 형식 또한 가능합니다.

test = f'Hi {name}'
name = 'Bob'
print(test)


# Hi Bob

속도

아래 예시와 같이 f-string이 가장 빠른 것으로 확인할 수 있습니다. f-striing은 상수 값이 아닌 런타임에서 계산이 되는 표현식입니다. https://www.python.org/dev/peps/pep-0498/#abstract에서 f-string에 대한 자세한 내용을 확인할 수 있습니다.

% operator

>>> import timeit
>>> timeit.timeit("""name = "Eric"
... age = 74
... '%s is %s.' % (name, age)""", number = 10000)

0.003324444866599663

str.format

>>> timeit.timeit("""name = "Eric"
... age = 74
... '{} is {}.'.format(name, age)""", number = 10000)
0.004242089427570761

f-string

>>> timeit.timeit("""name = "Eric"
... age = 74
... f'{name} is {age}.'""", number = 10000)
0.0024820892040722242



test 테이블의 데이터

test1
abcd
1111
1234


array로 변경

select array_agg(test1) from test {abcd,1111,1234}

string으로 변경

select string_agg(test1, ',') from test abcd,1111,1234


array 데이터를 각 로우로 변경

unnest(array타입)


-- 예시
select unnest(ARRAY[1,2])


1
2
(2 rows)

array 타입에 데이터 추가

array_append(array, 값)


-- 예시
array_append(ARRAY[1,2], 3)


{1,2,3}

array 타입끼리 협차기

array_cat(array, array}


-- 예시
array_cat(ARRAY[1,2], ARRAY[3,4])


{1,2,3,4}

array에서 string(단일컬럼)으로 변경

array_to_string(array, 구분자, [NULL 값 대체 구분자])


-- 예시
array_to_string(ARRAY[1, 2, 3, NULL, 5], ',', '*')


1,2,3,*,5

string을 array로 변경

string_to_array(string, 구분자, [null로 변경할 값])


-- 예시
string_to_array('xx~^~yy~^~zz', '~^~', 'yy')
{xx,,zz}


Django 에서는 crontab 기능을 지원하여 손쉽게 배치를 추가/실행/삭제를 할 수 있습니다.

설치

$ pip3 install django-crontab

crontab 등록

스크립트 등록을 위한 함수를 생성합니다.

# app/cron.py


from my.app import run


def scheduler():
	run()
	pass


이후 settings.py에서 아래와 같은 형식으로 등록합니다.

CRONJOBS = [
	('* * * * *', 'app.cron.scheduler')
]


CRONJOBS 내에 들어가는 데이터 형식은 아래와 같습니다.

(' 분 시 일 월 요일 ', '앱이름.파일명.함수명')

스케쥴링에 대한 자세한 내용은 http://brownbears.tistory.com/15 에서 확인할 수 있습니다.


위 작업까지 완료했으면 이제 crontab 명령어를 사용해 처리하면 됩니다.

crontab 명령어

목록

$ python3 manage.py crontab show

추가

$ python3 manage.py crontab add

삭제

$ python3 manage.py crontab remove


위 명령어를 통해 등록했을 경우 실제 서버의 crontab에도 등록된 것을 확인할 수 있습니다.

$ crontab -l


* * * * * /bin/python3 /manage.py crontab run 49fb9b8a9b695f26de0c796631105b3e # django-cronjobs for base


백업과 아카이브는 엄연히 다른 기능입니다.

백업이란?

백업(Backup)은 데이터가 손상되거나 손실될 경우를 대비해 저장하는 데이터의 사본입니다. 원본 데이터는 백업을 생성한 후에도 지우지 않습니다. 백업의 예로 휴대폰의 사진을 클라우드에 복사하는 것부터 중요한 파일을 USB에 복사하는 등 다양합니다. 또한 파일 서버(비정형 데이터)와 데이터베이스(구조화 데이터)도 백업합니다. 이처럼 백업은 데이터를 그대로 복사하는 것에에 중점을 둡니다.

백업은 사고가 났을 때 데이터를 복원(Restore)하는 것입니다. 만약 휴대폰의 모든 사진을 클라우드로 백업했는데 휴대폰이 고장났으면 클라우드에 백업한 파일을 그대로 내려받아 사용할 수 있으며 서버 내 랜섬웨어가 걸렸다면 랜섬웨어 해결 후, 백업한 데이터를 받아 정상적으로 운영할 수 있습니다.

아카이브란?

아카이브(Archive)는 참고용으로 생성한 데이터 사본입니다. 종종 아카이브를 만든 후에는 원본 데이터를 지우기도 합니다. 백업의 목적이 어떤 것을 정상적인 상태로 되돌리는 것이라면, 아카이브는 여러 가지 목적이 있는데 보편적으로는 이전 데이터에서 일부 데이터를 찾는 것입니다. 예를 들어, 몇년 전에 고객이 서명한 계약서처럼 아주 중요한 내용을 담은 파일 하나를 찾는 것입니다.

또 하나의 데이터는 특정 시점을 증명할 수 있는 이메일이나 파일 같은 것입니다. 예를 들어, 3년간 홍길동이란 이름이 들어간 모든 메일을 찾는 행위입니다. 보통 데이터를 실시간 상태로 유지하고 싶어 하지만 데이터는 아카이브로 보존하며, 아카이브는 인덱스를 제공해 사용자는 오래된 콘텐츠에서 과거의 데이터를 되찾을 수 있도록 합니다. 일부 서버에서의 아카이브 시스템은 아카이브의 크기나 액세스 기간으로 아카이브를 삭제할 수 있습니다. 이러한 행동으로 시스템을 최적화하고 저장공간을 절약할 수 있습니다.

복원과 회수의 차이점

백업 시스템은 데이터를 복원(Restore)하고 아카이브 시스템은 데이터를 회수(Retrieval)합니다. 복원은 단일 파일이나 서버, 데이터베이스에 해당하는 경우가 대부분입니다. 하지만 뭔가를 회수한다고 하면, 보통은 관련 데이터 모음입니다. 이러한 데이터는 동일한 서버나 형식으로 저장되어 있지 않을 가능성이 큽니다.

복원은 특정 시점을 기준으로 이루어집니다. 예를 들어, 데이터베이스를 어제와 같은 상태로 복원하는 것입니다. 반면에 회수는 일정 시간대를 사용합니다. 예를 들어, 지난 3년 간의 모든 이메일과 같은 방식입니다. 복원을 하기 위해서는 데이터가 언제 어디에 저장되었는지 알아야 합니다. 1개라도 모를 경우 진행을 할 수 없습니다. 데이터가 있던 서버 이름부터 데이터베이스나 디렉토리를 알아야 하고, 복원하려는 파일 이름이나 테이블, 마지막으로 본 날짜와 시간도 알아야 합니다. 회수의 경우는 아카이브가 되어 있는 서버에서 찾고자 하는 특정 파라미터만 있으면, 파라미터와 일치하는 모든 데이터를 가져오면 끝납니다.

+ Random Posts