1. 변수와 자료형

타 언어와 달리 어떠한 선언자나 자료형 없이 자유롭게 선언한다.

a = "Hello World"
b = 128
c = 3.141592
d = 10/2.6
e = [1,2,3]
f = 0b1010

순서대로 문자열, 정수, 소수, 소수(식으로 작성), 리스트(배열), 정수(이진수) 이다.

 

print(type(   ))을 사용하여 해당 변수의 자료형이 무엇인지 확인할 수 있다.

val = 'Hello World!'
print(type(val))

# 출력 값
# <class 'str'>

 

모든 변수에는 id값이 부여되어 있다.

이 값은 실행할 때마다 바뀌는데 값을 print(id(   ))를 사용해서 확인할 수 있다.

val = 'Hello World!'
print(id(val))

# 출력 값
# 2753358083712

출력값은 매번 바뀌기 때문에 알아봤지 큰 의미는 없다.

 

2. 리스트와 튜플 (배열)

C 계열 언어는 리스트와 배열이 따로 있지만 파이썬에서는 같은 개념이며 배열이라는 말을 안 쓰고 리스트라고 한다.

 

리스트와 튜플은 거의 비슷하나 상수와 변수와 같은 차이가 있다.

둘 다 배열과 같은 느낌이지만 리스트는 얼마든지 안의 값이 바뀔 수 있지만 튜플은 한 번 정해진 값이 변하지 않는다.

arr1 = [1,2,3]
arr2 = [1.1, 2.2, 3.3]
arr3 = ['Hello', 13, 'World', True]

어떤 값이든 자유롭게 넣는다.

 

arr4 = []
arr5 = list()

빈 리스트를 선언할 때는 위 처럼 작성한다.

 

arr4.append(4)
print(arr4)

# 출력결과
# [4]

append를 사용해 새로운 값을 넣어줄 수 있다.

 

tuple1 = (1,2,3,4)
print(tuple1)

# 출력결과
# (1, 2, 3, 4)

튜플은 소괄호를 사용해서 선언한다.

 

 

3. 딕셔너리

여타 언어에도 있는 사전형 자료형이다.

spiderman = {'name' : 'Peter Parker',
              'age' : 18,
           'weapon' : 'Web Shooter'}

spiderman이라는 변수는 각각 name, age, weapon이라는 키를 가지고 있고

각각의 키는 Peter Parker, 18, Web Shooter라는 값을 가지고 있다.

키가 있으면 값도 있으며, 그 수에는 제한이 없다.

 

참고로 아래처럼 한 줄로 작성해도 된다. 그러나 보기 안 좋기 때문에 위처럼 작성하는 것이다.

spiderman = {'name' : 'Peter Parker', 'age' : 18, 'weapon' : 'Web Shooter'}

 

이렇게 만들어진 딕셔너리는 다양하게 출력할 수 있다.

print(spiderman)
print(spiderman['weapon'])
print(spiderman['age'])

# 출력결과
# {'name': 'Peter Parker', 'age': 18, 'weapon': 'Web Shooter'}
# Web Shooter
# 18

전체 값을 출력할 수도 있고 특정 키 값만 출력할 수도 있다.

 

참고로 키 값을 입력할 때, ctrl + spacebar 를 입력하면 해당 변수가 어떤 키를 가지고 있는지 보여준다.

 

4. 집합

수학의 집합을 처리하기 위해 만든 자료형으로 set이라는 키워드를 사용한다.

set1 = set([1,2,3,4])
set2 = set("Hello World")
print(set1)
print(set2)

# 출력결과
# {1, 2, 3, 4}
# {'d', 'H', 'e', 'W', 'o', ' ', 'r', 'l'}

출력결과가 다소 특이하다.

집합의 특징은 중복을 허용하지 않는다는 것이다.

때문에 Hello World에서 중복되는 값들이 사라졌으며 값이 이리저리 얽힌 것을 볼 수 있다.

실제로 출력값은 매번 시도할 때마다 위치가 바뀐다.

 

 

리스트, 튜플, 딕셔너리, 셋은 각각 다른 용도로 사용하나 값을 출력하면

튜플은 소괄호, 딕셔너리와 셋은 중괄호, 리스트는 대괄호를 사용한다는 차이점이 있다.

파이썬의 가장 큰 특징이라면 쉬운 난이도에 있다.

그때문인지 파이썬은 지금 C언어나 Java를 넘어 세상에서 가장 많이 사용되고 있는 언어중 하나다.

(물론 조사기관마다 약간의 차이는 있지만)

이는 파이썬 특유의 낮은 난이도와 과학, 공학 친화적인 특징 덕분에 프로그래머가 아닌 타 분야 전공자들이 프로그래밍이 필요할 때 사용하기 위한 언어로 적합해서 그런게 아닐까 싶다.

 

어쨌든 현재 가장 많이 사용되는 언어인 것은 확실하므로 내용을 정리해보자

 

1. 파이썬의 특징

엄격하지 않은 언어로 자바스크립트 처럼 변수를 선언할 때 자료형을 정의해줄 필요가 없다.

심지어 자바스크립트와는 달리 선언자도 사용할 필요가 없다.

배열(리스트)에 아무런 값을 자유롭게 넣을 수 있다.

새미콜론을 사용하지 않고 들여쓰기에 큰 영향을 받는다.

실행속도가 타 언어에 비해 느리다는 단점이 있다.

 

좀더 전문적으로 파고들자면

플랫폼에 독립적이며 인터프리터식, 객체지향적, 동적 타이핑 대화형 언어이다.

 

2. 개발환경

보통 파이썬 유저들은 파이참을 주로 사용하지만

파이참은 유료이기 때문에 필자는 파이썬을 공식 홈페이지에서

파이썬을 설치하고 VS-CODE를 사용해서 개발을 할 것이다.

 

3. 시작하기전 기본적인 내용

가장 간단한 콘솔 출력을 해보자

print('Hello World!')

C# 이나 그 외 언어들과 달리 자바스크립트 처럼 짧다.

 

 

주석처리는 #으로 한다.

print('Hello World!')		# 주석은 #을 사용한다.

주석 단축키는 ctrl + / 로 할 수 있지만 ctrl + k + c 로도 할 수 있다.

 

 

 

가장 중요한 부분중 하나인 조인이다.

 

조인이란 두 테이블의 정보를 엮어서 추출하는 것을 말한다.

 

 

내부조인

보통 조인이라고 하면 내부조인을 말한다.

두 테이블의 조인을 위해선 테이블이 일대다(one to many) 관계로 연결되어 있어야 한다.

 

일대다란 무엇인가?

쉽게말해 Tarel이라는 유저는 콜라도 살 수 있고 사이다도 살 수 있고 코코아도 살 수 있다.

즉, Tarel이라는 user 하나가, 여러개의 상품 데이터에 접근한다는 것이다.

 

내부조인은 다음과 같이 한다.

 

SELECT 열 목록
FROM <첫 번째 테이블>
	INNER JOIN <두 번째 테이블>
	ON <조인 조건>
[WHERE 검색조건]

참고로 INNER JOIN 대신 그냥 JOIN 이라고 써도 된다.

 

조인하기 앞서, 우선 user테이블과 buy 테이블을 다시한번 확인해보자

user 테이블
buy 테이블

 

위 두 테이블을 조인해보자

 

조인 예시

WHERE은 조건을 추가해주는 용도로 사용된다.

즉, buy 테이블에서, identifier가 tarel인 것만 가져온 것이다.

WHERE을 지우면 아래처럼 나온다.

 

 

지금보니까 쓸데없이 데이터가 너무 많다.

테이블을 좀 더 간결하게 해보자

 

SELECT 를 수정하여 간결하게 줄인 모습

이렇게 내부조인을 통해서, 2개의 테이블에서 원하는 데이터를 가져올 수 있다.

 

테이블 이름에 별칭을 붙여서, SQL을 더 간결하게 사용할 수 있다.

별칭은 FROM과 JOIN에 들어간 데이터 베이스에 적는다.

즉, 2번 줄과 3번줄에서 별칭을 지정해주고, 그걸 1번줄과 4번줄에서 쓰고 있는 것이다.

 

많은 프로그램 언어에 자료형이 있는 것 처럼 SQL에도 자료형이 있다.

 

정수형

데이터 형식 바이트 범위
TINYINT 1 -128 ~ 127
SMALLINT 2 -32,728 ~ 32,767
INT 4 약 -21억 ~ 약 21억
BIGINT 8 약 -900경 ~ 약 900경

만약 범위를 벗어나는 값을 입력할 경우 Out of range 오류가 발생한다.

오버플로우가 일어나는 다른 언어들과는 다른 부분

 

이 형태로 테이블을 만들 때는 아래 예시처럼 만든다.

CREATE TABLE tarelExample (
	int_1 TINYINT,
	int_2 SMALLINT,
	int_3 INT,
	int_4 BIGINT
);

 

만들어진 모습, 사진 속 SQL이 약간 다른데, db를 선택해주는 부분이 추가되었다.

 

 

문자형

문자형은 두 가지가 있다.

데이터 형식 바이트
CHAR(원하는 숫자) 1~255
VARCHAR(원하는 숫자) 1~16383

 

괄호안의 원하는 숫자는 문자의 최대 길이라고 보면 된다.

즉 CHAR(10)은 최대 10글자라는 뜻이다.

VARCHAR는 가변길이 문자열로 VARCHAR(10)에 3글자를 저장하면 3자리만 사용하고 남은 7자리는 차지하지 않는다.

반면 CHAR(10)에 3글자를 저장하면 7자리의 공간낭비가 발생한다.

 

이렇게 보면 VARCHAR이 더 좋아보이지만 공간 효율은 VARCHAR가 더 좋으나 시간 효율은 CHAR이 더 좋다.

 

긴 문자열

만약 블로그에 장문의 소설을 작성한다면 그 많은 글자를 어떻게 저장해야 할까?

이 때 사용하는 것이 TEXT, BLOB형식으로 자주 사용하는건 총 4가지가 있다.

데이터 형식 바이트 수
TEXT 형식 TEXT 1~65535
LONGTEXT 1~4294967295
BLOB 형식 BLOB 1~65535
LONGBLOB 1~4294967295

이 외에도 TINYTEXT, MEDIUMTEXT, TINYBLOB, MEDIUMBLOB도 있지만 잘 안쓴다.

 

BLOB는 글자가 아닌 이미지나 동영상 등의 데이터로 이러한 것을 이진 데이터라고 한다.

즉, 사진이나 영상등을 저장하고 싶다면 BLOB형식으로 저장해야 한다.

 

실수형

소숫점으로 총 2가지가 있다.

데이터 형식 바이트 수 설명
FLOAT 4 소숫점 7자리까지
DOUBLE 8 소숫점 15자리까지

특별한 경우가 아닌 이상 FLOAT를 쓰면 된다.

 

날짜형

날짜와 시간으로 총 3가지가 있다.

데이터 형식 바이트 수 설명
DATE 3 날짜만 저장, YYYY-MM-DD 형식
TIME 3 시간만 저장, HH:MM:SS 형식
DATETIME 8 날짜와 시간을 저장, YYYY-MM-DD HH:MM:SS 형식

 

변수

일반적인 프로그래밍 언어처럼 변수를 선언하고 사용하기 위한 용도

이름 역할
SET 변수를 선언 및 값을 대입
SELECT 변수의 값을 출력

아래와 같이 사용한다.

SET @newVar1 = 5;
SET @newVar2 = 10;
SET @Name = "Tarel 베이스";
SELECT @newVar1 + newVar2;
SELECT @Name;

 

이런식으로 출력된다.

 

최하단의 SELECT @Name 이 없으면 이렇게 출력
변수를 이런식으로 사용하는 것도 가능

형변환

문자를 정수로 바꾸거나 반대로 정수를 문자로 바꾸는 것으로 직접 함수를 사용하는 명시적 변환과 그런거 없이 자련스럽게 바뀌는 암시적인 변환이 있다.

 

명시적인 변환

함수는 두 종류가 있고 아래와 같이 사용한다.

CAST(값 AS 데이터 형식 [길이])
CONVERT(값 또는 데이터 형식 [길이])

 

직접 써보자

문자열 2022$12$12를 DATE로 변환한 모습, 잘 변환 되었다.

 

 

암시적인 변환

CAST()나 CONVERT() 없이 자연스럽게 형변환이 일어나는 것이다.

SELECT '100' + '200';

 

문자열 100과 문자열 200을 더했다.

이때, 문자열은 자연스럽게 숫자형으로 변환되며 300을 출력한다.

그렇다면 100과 200을 이어붙이는, 100200 을 만들고 싶다면 어떨까?

SELECT CONCAT('100','200');

CONCAT은 파라미터 내의 요소를 이어붙여주는 역할을 한다.

100200이 되었다.

 

 

프로그램을 실행하다보면 여러가지 이유로 에러가 발생할 수 있다.

이럴 경우, 보통 프로그램은 멈추거나 꺼지거나 하게되는데 이런 일을 막기위한 것이 바로 예외처리다.

 

예외처리에는 여러가지 방법이 있는데 다음과 같다.

 

try-catch

try - catch문은 대강 다음과 같이 사용한다.

 

try{
	//원하는 내용 입력
}
catch (FormatException e){
	//원하는 내용 입력
}

try의 {} 내에는 시험할 내용을 입력한다.

catch는 try의 {} 안에서 에러가 발생할 경우, catch로 넘어오게 한다.

 

예를 들어서

try{
	1번코드
	2번코드
	3번코드
	4번코드
}
catch (FormatException e){
	5번코드
}

이 코드는 1번코드 -> 2번코드 -> 3번코드... 순서대로 실행된다.

그런데 이 코드에서 만약 2번 코드를 실행하던 중 에러가 발생할 경우 바로 catch문인 5번코드로 넘어가게 된다.

 

만약 어떤 코드에서도 에러가 발생하지 않았다면? 4번코드까지 실행하고 5번코드는 실행되지 않는다.

 

여기서 catch의 FormatException e 는 발생한 에러를 말한다.

예를 들어, 에러 발생시, 에러 메시지를 출력한다면

try{
}
catch (Exception e){
	Console.WriteLine(e.Message);
}

이런 식으로 입력하면 된다.

 

Exception

에러는 많은 종류를 가지고 있으며 그 종류와 용도는 다음과 같다.

ArgumentException
메서드에 전달 되는 null이 아닌 인수가 잘못되었다.

ArgumentNullException
메서드에 전달 되는 인수가 null이다.
ArgumentOutOfRangeException
인수가 유효한 값 범위를 벗어났다.
DirectoryNotFoundException
디렉터리 경로 일부가 잘못되었다.
DivideByZeroException
0으로 나누었다.
DriveNotFoundException
드라이브가 없거나 사용할 수 없다.
Exception
모든 종류의 예외를 처리할 수 있다.
FileNotFoundException
파일이 없다.
FormatException
문자열에서 변환할 수 있는 적절 한 형식이 아니다.
IndexOutOfRangeException
인덱스가 배열 또는 컬렉션의 범위를 벗어났다.
InvalidOperationException
개체의 현재 상태에서 메서드 호출을 사용할 수 없다.
KeyNotFoundException
컬렉션의 멤버에 액세스 하는 데 지정 된 키를 찾을 수 없다.
NotImplementedException
메서드 또는 작업이 구현 되지 않았다.
NotSupportedException
메서드 또는 작업이 지원 되지 않는다.
ObjectDisposedException
삭제 된 개체에 대한 작업을 수행했다.
OverflowException
산술, 캐스팅 또는 변환 작업을 수행 하면 오버플로가 발생한다.
OverflowException
작업 결과가 대상 데이터 형식의 범위를 벗어났다.
PathTooLongException
경로 또는 파일 이름이 시스템에서 정의한 최대 길이를 초과한다.
PlatformNotSupportedException
현재 플랫폼에서 작업이 지원 되지 않는다.
RankException
차원 수가 잘못되었다.
TimeoutException
작업에 할당 된 시간 간격이 만료 되었다.
UriFormatException
잘못 된 URI (Uniform Resource Identifier)가 사용 되었다.

 

여기서 가장 많이, 대중적으로 사용하는 키워드가 바로 Exception 이다.

 

또한 이 exception의 하위 키워드들은 모두 exception의 상속을 받은 서브 클래스다.

때문에 아래처럼 사용은 불가능하다.

try {
}
catch (Exception e)
{
}
catch (OverflowException e)
{
}

이미 가장 큰 개념인 Excepton에서 모든 에러를 검출했기 때문에 두번째 catch문은 에러가 난다.

때문에 아래와 같이 사용해야 한다.

try {
}
catch (OverflowException e)
{
}
catch (Exception e)
{
}

작은 부분을 먼저 검출하고, 그 다음 나머지를 검출한다.

 

사실 Exception 만 알아도 왠만한건 다 커버가 된다.

 

try-catch-finally

finally는 try구문을 실행하던 중 에러가 발생하든 하지 않든 실행하는 부분이다.

즉 에러가 발생하지 않아도 실행하고 에러가 발생해도 실행하는 부분이다.

try를 쓸 때는 catch가 필수이지만 finally는 필수가 아니다.

static void ThrowFunc(int data) {
    if(data > 0) {
        Console.WriteLine("ThrowFunc data: " + data);
    }
    else {
        throw new Exception("data에 0이 입력되었습니다.");
    }
}

static void Main(string[] args) {
    try {
        ThrowFunc(10);
        ThrowFunc(100);
    }
    catch(Exception e) {
        Console.WriteLine(e.Message);
    }
    finally {
        Console.WriteLine("  finally 무조건 실행  \n");
    }
}

이 코드를 실행할 경우, try에서 에러가 발생하든 하지 않든 상관없이 finally는 무조건 실행된다.

에러를 일으키고 싶다면 ThrowFunc에서 파라미터 값을 10이 아닌 0이나 그 이하값을 주면 된다.

 

throw

try-catch가 실행 중 에러 검출이라면 throw는 원하는 타이밍에 에러를 낸다고 볼 수 있다.

 

if(data > 0) {
    Console.WriteLine("정상실행");
}
else {
    throw new Exception("에러발생");
}

위 구문에서, data가 0보다 크면 정상실행되지만 아닐 경우, throw를 통해서 에러를 반환한다.

참고로 이때 키워드 또한 Exception 말고도 위에서 표로 작성했던 다른 하위 Exception을 사용할 수도 있다.

 

직접만드는 예외 클래스

앞서 말했듯이 exception은 많은 하위 클래스를 가지고 있다.

마찬가지로, 임의로 클래스를 가져와 상속을 해주는 형태로 새로운 예외클래스를 만들 수 있다.

 

class MyException : ApplicationException
{
    public int Num { get; set; }
    public MyException():base() {
    }
    public MyException(int a) {
        Num = a;
    }

    public override string ToString() {
        return "Num: " + Num;
    }
}

 

ApplicationException을 상속받아 나만의 애러를 만들었다.
ApplicationException의 base를 상속 받고 정수를 받는데, MyException이 호출되면  Num값이 에러메시지로 출력된다.

 

 

 

일반화

일반화의 특징과 장점

클래스, 함수 일반화 가능

<T> 키워드 사용

박싱과 언박싱을 줄일 수 있음

불필요한 오버로딩을 줄일 수 있다

 

<T>키워드는 굳이 T일 필요는 없다. (U로 하든 AAA로 하든 자유다.)

 

아래 예시를 보자

static void GenericPrint<T>(T data) {
    Console.WriteLine("data: {0}", data);
}

static void GenericPrint<T>(T[] arrData) {
    for(int i = 0; i < arrData.Length; i++) {
        Console.WriteLine("arrData: {0}", arrData[i]);
    }
}

함수는 GenericPrint고 파라미터 하나를 받는다.

그리고 Console을 통해, 입력받은 파라미터를 출력한다.

 

두번째 함수는 같은 이름이지만 배열을 받는다.

 

static void Main(string[] args) {
    int a = 10;
    float b = 10.3f;
    string c = "Hello";

    int[] arrA = { 0, 1, 2, 3 };
    string[] arrB = { "A", "B", "C", "D" };

    GenericPrint(a);
    GenericPrint(b);
    GenericPrint(c);

    Console.WriteLine();
    GenericPrint(arrA);
    Console.WriteLine();
    GenericPrint(arrB);
}

메인함수의 모습, 첫번째 함수에 정수를 넣든, 소수를 넣든 문자열을 넣든 잘 작동한다.

 

이를 통해 알 수 있는 것은

일반화를 하면 데이터에 종속되지 않고 나름 확장성을 지니게 되는 것이다.

 

일반화 - 클래스

클래스에서는 일반화 키워드를 클래스 이름과 함께 적는다.

class GenericAA<T>
{
    private T num;
    public T GetNum() {
        return num;
    }

    public void SetNum(T data) {
        num = data;
    }
}

이걸 메인함수로 가져와서 사용하면 아래와 같다.

class Program
{
    static void Main(string[] args) {
        GenericAA<int> AA = new GenericAA<int>();
        AA.SetNum(100);
        Console.WriteLine("AA: " + AA.GetNum());

        GenericAA<float> BB = new GenericAA<float>();
        BB.SetNum(100.30f);
        Console.WriteLine("BB: " + BB.GetNum());
    }
}

메인함수에서는 new를 통해 새로 생성할 때, 키워드에 자료형을 넣어준다.

이로써 클래스를 실행할 때, 넣어줄 값을 정해줄 수 있다.

 

일반화를 통해서, 클래스는 다양한 종류의 값을 받을 수 있게 된다.

 

 

dynamic

dynamic은 var타입이 가지고 있는 문제를 해결하는 용도로 사용할 수 있다.

또한 var와 object와 비슷한 느낌이 있다.

 

static T AddArray<T>(T[] arrDatas) {
    //T temp = 0; 에러 발생
    //object aaa = 0; //박싱, 언박싱 발생
    dynamic temp = default(T); 
    for(int i = 0; i < arrDatas.Length; i++) {
        temp += arrDatas[i];
    }
    return temp;
}

 

위 코드에서, 첫 번째 주석에서, T는 0으로 지정할 수 없다.

왜냐하면 T를 입력받을 때, 그 자료형이 무엇인지 알 수 없기 때문에 정수형으로 넣어줄 수 없는 것이다.

하지만 dynamic의 경우, var나 object처럼 다양하게 값을 받을 수 있으므로 에러가 발생하지 않는다.

 

dynamic과 var와 object는 어떻게 다를까?

var와 dynamic은 선언된 원본 형식을 유지한다.

그러나 이 둘은 타입이 확정되는 시점이 다르다.

var는 컴파일 시점에 이미 타입을 추정하여 확정하지만

dynamic은 런타임 시점에 타입이 확정된다.

때문에 dynamic은 올바르지 않은 타입이 지정되더라도 컴파일 시점에서는 모두 pass되며 런타임 시점에 에러가 발생한다.

 

일반화 컬렉션

 

C# 코드 최상단에 다음을 추가하여 사용할 수 있다.

using System.Collections.Generic;

 

이것은 컬렉션의 박싱과 언박싱의 단점을 해결하며 4가지 방식이 있다.

List<T>
Queue<T>
Stack<T>
Dictionary<T>

이것은 자료를 보관하는 통과 같으나

어떻게 보관하는지, 또 꺼낼때는 어떻게 꺼내는지에 따라 차이가 있다.

 

List부터 시작해보자

List

list는 말 그대로 데이터 리스트다.

후술할 다른 것들과는 다르게 딱히 데이터를 꺼낸다던가 하는 개념은 없고 인덱스를 통해 접근할 수 있다.

내부요소를 지울때는 clear()를 사용한다.

static void Main(string[] args) {
    List<int> ListAA = new List<int>();
    ListAA.Add(1);
    ListAA.Add(2);

    for(int i = 0; i < 10; i++) {
        ListAA.Add(i);
    }

    foreach(var data in ListAA) {
        Console.WriteLine("data: " + data);
    }

    Console.WriteLine("\n배열데이터로 초기화");
    string[] arrData = { "AA", "BB", "CC" };
    List<string> listArr = new List<string>(arrData);

    for(int i = 0; i < arrData.Length; i++) {
        Console.WriteLine("arrData: " + arrData[i]);
    }
}

ListAA는  정수형만 받을 수 있게 되어있다.

그렇게 정수를 받고 foreach로 값을 출력한다.

이후 문자열만 받는 새로운 List를 만들고 거기에arrData를 넣은 다음 그걸 다시 출력한다.

 

출력결과

 

Queue

static void Main(string[] args) {
    Queue<int> queueAA = new Queue<int>();
    queueAA.Enqueue(1);
    queueAA.Enqueue(2);

    for(int i = 0; i < 10; i++) {
        queueAA.Enqueue(i);
    }

    Console.WriteLine("queue data: {0}", queueAA.Peek());

    while(queueAA.Count > 0) {
        Console.WriteLine("queue data: {0}, count: {1}", queueAA.Dequeue(), queueAA.Count);
    }

    Console.WriteLine("\n배열데이터로 초기화");
    string[] arrData = { "AA", "BB", "CC"};
    Queue<string> queueArr = new Queue<string>(arrData);

    foreach(var data in queueArr) {
        Console.WriteLine("queueArr data: " + data);
    }
}

큐는 파이프와 같다.

값을 꺼낼 때, 가장 오래된 데이터가 나온다.

지금 for을 통해서, 1에서 9까지 차례대로 데이터를 넣고 있는데

Dequeue를 통해서 꺼낸 값은 가장 먼저 들어간 값부터 차례대로 나오고 있다.

출력결과

 

 

Stack

 

static void Main(string[] args) {
    Stack<string> stack = new Stack<string>();
    stack.Push("a");
    stack.Push("b");
    stack.Push("c");

    Console.WriteLine("stack data: {0}", stack.Peek());

    while(stack.Count > 0) {
        Console.WriteLine("stack data: {0}, count: {1}", stack.Pop(), stack.Count);
    }

    //배열데이터로 초기화
    Console.WriteLine("\n배열데이터로 초기화");
    int[] arrData = { 100, 200, 300 };
    Stack<int> stackCopy = new Stack<int>(arrData);

    foreach(object data in stackCopy) {
        Console.WriteLine("stackCopy data: " + data);
    }
}

stack에선 Push로 값을 넣고 Peek로 그 값을 살펴보고

Count로 값을 세고 Pop으로 값을 꺼낸다.

 

stack은 통 안에 쌓는 느낌이다.

이는 주로 실행취소(undo) 같은 기능에 사용하는데

Pop을 통해 데이터를 꺼내면 가장 최근에 입력된 데이터가 출력된다.

Peek는 지금 당장 꺼낼 수 있는 값이 무엇인지, 즉, stack의 최상단에 위치한 값을 출력(확인)한다.

 

출력결과

 

 

 

 

 

Dictionary

딕셔너리는 자바스크립트나 웹개발에서 자주 사용되는 그것과 비슷하다.

여러가지 데이터를 제목과 내용 같은 느낌으로 저장하는데 사실 제한이 없다.

여기서 제목에 속하는것이 key, 내용에 해당하는 것이 value다.

예를들어, AA 딕셔너리의 1번키는 a_a 1번벨류는 a_b, 2번키는 b_a, 2번 벨류는 b_b... 이런 형태로 저장되는 것이다.

즉 AA의 1번 키의 벨류값을 가져오라고 한다면 a_b가 나올 것이다.

 

static void Main(string[] args) {
    Dictionary<string, int> dictionaryAA = new Dictionary<string, int>();
    dictionaryAA.Add("10", 10);
    dictionaryAA.Add("20", 20);
    dictionaryAA["30"] = 30;

    foreach(var key in dictionaryAA.Keys) {
        Console.WriteLine("key: {0}, data: {1}", key, dictionaryAA[key]);
    }

    Console.WriteLine("");

    Dictionary<int, string> dictionaryInit = new Dictionary<int, string>() { 
        [1] = "Hello",
        [100] = "Dictionary",
        [1000] = "Good!!",
    };

    foreach(var key in dictionaryInit.Keys) {
        Console.WriteLine("key: {0}, data: {1}", key, dictionaryInit[key]);
    }

    string getValue = string.Empty;
    dictionaryInit.TryGetValue(1, out getValue);
    Console.WriteLine("\nTryGetValue: " + getValue);

    dictionaryInit.TryGetValue(11, out getValue);
    Console.WriteLine("\nTryGetValue: " + getValue);

 

딕셔너리를 처음 선언하는 곳을 보면 string과 int로 선언되었는데, 사실 2개 말고도 더 많이 넣을 수도 있다.

 

foreach를 보면 dictionaryAA.keys 라고 되어 있는데, 이는 dictionaryAA가 가진 key값들을 말한다.

 

출력결과

key가 key값이고 data가 그 key가 가진 value값이다

 

 

 

 

 

 

 

 

 

 

 

 

 

지금까지 살펴본 SELECT는 데이터를 조회하는데 사용했다.

 

이번엔 데이터를 만들고 수정하고 지우는 것에 대해서 알아보자

 

INSERT

테이블에 데이터를 입력하는 기본적인 SQL 문이다.

형태는 다음과 같다.

INSERT INTO 테이블 [(열1, 열2......)] VALUES (값1, 값2......)

 

이걸 이용해서 새로운 데이터를 넣어보자

지난번에도 사용한 이 product 테이블을 사용해보자

 

여기에 id는 6, 이름은 제로환타, 가격은 2500인 데이터를 넣어보자

 

 

2번 줄에 SELECT문이 보이는데, 이건 조회를 위해서 넣은 것이다.

즉, 새로운 데이터를 추가하고, 그 결과를 조회하는 것이므로 무시해도 된다.

 

아무튼 제로환타가 추가된 것을 볼 수 있다.

 

그런데, product_id와 같은 id 값에는 AUTO_INCREMENT를 적용한다.

이건 자동으로 숫자가 1 증가한 값이 들어가는 옵션이다.

 

 

auto increment가 적용되어 있는 id 값

 

아이디 값은 아무것도 입력받지 않아도 알아서 숫자가 1 증가한 값이 적용되는 것을 알 수 있다.

이번엔 코코아를 추가해보자

 

id 값을 null로 입력하자 자동으로 1 증가한 값인 7이 들어간 모습

 

자, 그런데 증가가 시작되는 값을 인위적으로 조정해 줄 수 있다.

예를 들어서, 다시 위 테이블에 새로운 데이터를 넣으면 id는 8이 될것이다.

그러나 100부터 시작할 수도 있다.

100부터 시작하는 카푸치노 데이터를 넣어보자

 

위 사진을 보면 ALTER라는 키워드가 보이는데, 이는 테이블의 속성을 바꿀 때 사용한다.

이 키워드는 나중에 더 자세히 알아보자

결과값을 보면 알겠지만 새로들어간 데이터인 카푸치노는 100부터 시작하는 것을 볼 수 있다.

 

UPDATE

 

자, 이번엔 데이터를 수정해보자

카푸치노를 럼주로, 가격은 45000으로 바꿔보자

 

는 세이프 모드를 쓰고 있어서 안 된단다.

이걸 풀어주자

저걸 체크 해제해주어야 한다.

 

그런 다음 워크벤치를 재시작 해주어야 한다.

 

그리고 실행한 모습

 

으악! 파일이 전부 덮어 쓰워졌다!

 

...생각해보니 where이 빠졌다.

 

조건을 빼먹어서 발생한 불상사를 빨리 수습하고 이번엔 where을 넣어서 제대로 실행했다.

보시다시피 카푸치노가 정상적으로 럼주로 바뀐 것을 볼 수 있다.

 

앞으로 주의하자, update를 할 때 where을 빼먹으면 모든 데이터가 덮어씌워지니 조심하도록 하자

 

다만 한 번에 전체를 바꿔야할 때가 있지 않을까?

모든 데이터에서, 가격을 100으로 나누어 보자

 

가격이 1/100로 줄어든 모습이다.

 

다시 원상복구를 해준 모습

 

DELETE

데이터를 삭제하는 DELETE는 다음과 같이 사용한다.

DELETE FROM 테이블 WHERE 조건;

만원보다 가격이 높은 데이터를 삭제했다.

 

 

 

전에 사용한 데이터베이스를 그대로 사용한다.

 

ORDER BY

특정 열을 기준으로 정렬한다.

user_grade를 기준으로 정렬

역순으로 정렬한다면 끝에 DESC를 붙여주면 된다.

 

user_grade를 기준으로 역순으로 정렬

LIMIT

출력에 제한을 둔다.

예를 들어 3개만 출력한다고 하면 LIMIT 3 라고 적는다.

user_grade를 기준으로 역순으로 정렬해서 3개 까지만 출력

 

DISTINCT

중복된 결과를 제거한다.

단, 이 부분은 문법이 조금 이질적이다.

 

user 테이블에서 user_address를 가져온다.

자세히 보면 SELECT 다음에 사용되었으며 user_address의 데이터만, 중복되지 않게 가져왔다.

 

 

GROUP BY

이건 조금 어렵다.

이걸 설명하기 전에, 여기에 적합한 테이블을 하나 더 만들자

 

새로 만드는 테이블의 쿼리문, 이 명령으로 테이블을 만들었다.
새로만든 테이블에 넣은 데이터, 사용자들이 무엇을 얼마나 구매했는지를 보여준다.

 

GROUP BY는 단독으로 쓰기 보단 집계함수와 같이 사용되는데 그 집계함수로는 다음이 있다.

함수명 설명
SUM() 합계
AVG() 평균
MIN() 최소값
MAX() 최대값
COUNT() 행의 개수를 센다.
COUNT(DISTINCT) 행의 개수를 센다(단, 중복없이)

위 함수와 같이 사용하여 데이터를 원하는 부분만 가져올 수 있다.

GROUP BY는 위 함수와 같이 사용해보면서 살펴보자

 

GROUP BY - SUM

먼저 SUM 부터 알아보자

buy 테이블에는 어떤 사용자가 어떤 제품을 몇 개 구매했는지에 대한 정보가 들어있다.

위 사진은 사용자별로 총 몇 개의 물건을 구매했는지를 나타낸 것이다.

buy테이블을 보면 tarel 이라는 사용자는 제로콜라 5개, 콜라 3개, 사이다 2개, 사이다 2개로 총 12개를 구매한 것을 알 수 있다.

그리고 SUM 함수를 사용해서 buy_count를 정리하자, tarel은 12가 나온 것을 볼 수 있다.

 

SELECT 다음에 오는 값은 결국 내가 보고자 하는 데이터목록인 것을 알 수 있다.

여기서, SUM 함수를 통해서 buy_count의 값을 더한 것을 알 수 있다.

GROUP BY는 더한 것의 기준을 잡아주었다고 볼 수 있다.

 

GROUP BY - AVG

위의 SUM을 잘 이해했다면 여기서부턴 어렵지 않을 것이다.

AVG는 눈치 챈 사람도 있겠지만 평균 값이다.

이번엔 구매자들이 구매한 물품의 평균 값을 구해보자

SELECT에서 함수만 SUM에서 AVG로 바꾸어주고 AVG의 파라미터만 buy_price로 바꾸어 주었다.

그런데 이건 구매시, 구매했던 물건의 가격의 평균이다.

즉, 갯수와는 상관이 없는 것이다.

 

만약 매 쇼핑마다 쓴 금액의 평균을 구하고자 한다면 어떨까?

예를 들어, tarel은 제로콜라 5개, 콜라 3개, 사이다 2개, 사이다 2개를 구매했는데

이때, 순서대로 15000원, 3000원, 4000원, 4000원을 사용했다.

그렇다면 매 쇼핑시 평균값은 6500원인데 이런 값을 구하고자 한다면 어떨까?

보시다시피 파라미터에 식을 넣어서 해결한 것을 알 수 있다.

다른 프로그래밍 언어처럼 파라미터에 식을 넣는 것도 가능한 것을 알 수 있다.

 

 

GROUP BY - MIN, MAX

min과 max는 말 그대로 최대, 최소값이다.

각 고객이 가장 비싼 물건을 샀을 때, 그 가격을 보여준다.

 

역시 파라미터에 식을 넣어서 해당 고객이 가장 많이 돈을 쓴 시점을 뽑아낼 수도 있다.

여담인데 명령어 소문자로 적어도 무방하다....

어딜가나 대문자로 적혀있길래 꼭 대문자여야 했지만 굳이 그런건 아닌듯

 

GROUP BY - COUNT()

행의 갯수를 센다.

이때, 파라미터에 DISTINCT를 넣어서 중복을 없앨 수 있다.

 

이렇게 사용하면 어느 고객이 구매를 총 몇번 했는지를 알아낼 수 있다.

 

HAVING

Having은 where과 비슷하게 조건을 제한하지만 여기서 제한하는 조건은 집계함수에 대한 것이라고 볼 수 있다.

예를 들어, 각 사용자별 사용 금액을 가져와보자

보시다시피 사용자별 총 구매 금액이 나왔다.

여기서 만원을 초과해서 쓴 사람들만 가져와보자

이럴 때 이렇게 having을 써서 조건을 걸어줄 수 있다.

 

 

+ Recent posts