기억의 실마리
2023. 2. 22. 01:20

JWT

JWT는  .  을 기준으로 좌측부터 Header.Payload.Signature의 의미를 가진

세가지 문자열의 조합이다.

 

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOjE1LCJpYXQiOjE2NzQ0NjQwNzIsImV4cCI6MTY3NTY3MzY3Mn0.trE7PemoIJZ1y7u3F6hrURW3iHUCN1KBcPTsdVAueEw

 

Header에는 type과 hash알고리즘의 종류가 내포되어있고, Payload 서버에서 응답한 response(권한 정보와 데이터 등) 내포되어있다. Signature에는 HeaderPayload Base64 URL-safe Encode를 한 이후 Header에 명시된 해시함수를 적용하고, Private Key로 만들어진 전자서명이 내포되어 있다.

 

JWT에 대한 견해

나는 인증이 필요한 프로젝트를 만들었을때 JWT를 선택했다. 인증방식에 대한 많은 방법들이 존재하지만 주관적인 생각으로는 JWT의 데이터 변조에 대한 안정성은 타 인증방식에 비하면 검증된 방법이 생각했기 때문이었다. 토큰을 대조하여 인증하기 때문에 해당 데이터 위변조를 방지할 수 있다는 점 이 가장 컸고 또 다른 이점으로 해당 서비스가 아닌 다른 로그인 시스템에 접근, 권한공유가 가능한 구조이기 때문에 쿠키와는 차별화된 인증방식으로서 큰 이점을 가질 수 있다고 생각했기 때문이다.

 

Header

토큰의 유형과 서명 암호화 알고리즘을 내포하고 있다.

{
  "alg": "HS256",  // 서명화 알고리즘(HMAC SHA256, RSA 등)
  "typ": "JWT"  // 토큰의 유형
}

 

 

Payload

서버와 클라이언트가 실질적으로 주고받는 데이터를 담고 있는 섹션이다.

지정된 데이터 타입은 없지만 보편적으로 사용하는 데이터 타입으로

Registered claims, Public claims, Private claims 이 있다.

{
  /* 위 예시와 동일코드 */
  "sub": 15,            // Registed Claim
  "iat": 1674464072,    // Registed Claim
  "exp": 1675673672,    // Registed Claim
  
  /* 예시코드 */
  "jti": 4200,                          // Registed Claim
  "https://zeriong.tistory.com": true,  // Public Claim
  "useId": "zeriong@zeriong.com"        // Private claim
}

1. Registed claims : predefined claims

  • iss : issuer (발행자)
  • exp : expiration time (만료시간)
  • sub : subject (제목)
  • iat : issued At (발행시간)
  • jti : JWT ID

 

2. Public claims

  • 사용자가 정의 가능한 클레임의 공개용 data를 전달할때 사용한다.

 

3. Private claims

  • 인증이 완료된 당사자들 간에 정보를 공유하기 위해 만들어진 사용자 지정 클레임이다.
    공개의 여부는 무관하지만 해당 유저를 특정할 수 있는 정보들을 내포한다.

 

 

Signature

시그니처는 헤더에서 정의한 알고리즘 방식인 alg를 활용한다.

(Header + Payload) + (Primary-Key in Server) 이렇게 둘을 합친 후

헤더에서 정의했던 알고리즘으로 암호화한다.

 

Signature = Base64Url( Header ) + . + Base64Url( Payload ) + server's Key\

 

 

JWT.IO에 접속해서 인코딩과 디코딩을 할 수 있다.

https://jwt.io/

 

JWT.IO

JSON Web Tokens are an open, industry standard RFC 7519 method for representing claims securely between two parties.

jwt.io

 

 

 

Access-Token, Refresh-Token을 통한 인증과정

  1. 클라이언트에서 ID,PW를 입력한다.

  2. 클라이언트에서 보낸 ID,PW가 DB에 정보와 매치되면 DBrefresh-token을 저장하고
    헤더를 통해서 refresh-token과 동일한 값을 가진 access-token을 보내준다.

  3. 클라이언트에서 api를 요청할때 access-token을 같이 헤더에 실어 보내준다.

  4. 요청받은 access-token을 대조한 후 문제가 없을 경우에만 응답한다.

  5. 클라이언트에서 api를 요청 했는데 access-token이 만료된 경우에는
    DBrefresh-token를 이용해서 access-token을 재발급 해준다.

 

 

JWT는 보안?

결론부터 말하자면 JWT의 목적은 보안이 아닌 데이터 위조방지이다.

Base64를 통해서 암호화 하기 때문에 디버거를 사용하면 바로 복호화 할 수 있다.

그러므로 페이로드에 담기는 데이터는 모두 쉽게 노출될 수 있기 때문에 비밀번호나

민감한 정보는 담지 말아야 한다. 그리고 가장 처음 "암호화된 문자열" 이 아닌

"문자열의 조합" 이라 서술한 것도 이러한 특성 때문이었다.

 

 

JWT 장점

  1. Header와 Payload로 Signature를 생성하기 때문에 데이터 위변조를 방지할 수 있다.
  2. 인증 정보 저장에 대한 자유도가 높은 편에 속한다.
  3. data와 token이 검증 됨을 증명하는 서명과 필요한 모든 data를 자체적으로 지니고 있다.
  4. 클라이언트 인증 정보를 저장하는 세션과 다르게, 서버는 무상태(StateLess)가 되어 서버 확장성이 우수해질 수 있다.
  5. 쿠키와 다른 토큰 기반으로 다른 로그인 시스템에 접근 및 권한 공유가 가능하다.
  6. OAuth의 경우 Facebook, Google 등 소셜 계정을 이용하여 다른 웹서비스에서도 로그인을 할 수 있다.
  7. 모바일 어플리케이션 환경에서도 잘 동작한다. (모바일은 세션 사용 불가능)

 

JWT단점

  1. Self-contained의 형식으로 토큰 자체에 정보를 담고 있어, 양날의 검이 될 수 있다.
  2. 토큰의 Payload에 3종류의 클레임을 저장하기 때문에, 정보가 많아질수록 토큰의 길이가 늘어남에 따라 네트워크에 부하를 줄 수 있다.
  3. payload 자체는 암호화 된 것이 아니라 BASE64로 인코딩 된 것이기 때문에 중간에 Payload를 탈취하여 디코딩하면 데이터를 볼 수 있으므로 payload에 중요 데이터를 넣지 말자.
  4. access-token하나만 가지고 운영을 할 경우 stateless 특징을 가진다. 즉 클라이언트 측에서 단독관리 를 하기 때문에 토큰 자체를 탈취당하면 대처하기가 어렵게 된다. 해결법으로 DB에서 refresh-token을 저장하여 access-token을 재발급 해주며 이중으로 보안을 강화할 수 있다.

 

 

마치며...

프론트개발자를 지향하며 공부하는 나로서 백엔드스택의 경험은 꽤나 복잡하고 어려운 구조였던 것 같다. 그래도 하나하나 풀어가며 이해하려 공을 들이다보니 어느새 많은 구조를 이해하게 된 것 같아 뿌듯 했다. 앞으로 어떤 인증방식이 유행을하고 더 보안이 강화가 된 인증방식이 나올지는 모르겠지만 아마 JWT방식은 앞으로도 오랜 사랑을 받을 것 같다.

2023. 2. 18. 17:22

TypeORM

TypeORM에서 ORM은 Object-Relational Mapping(객체- 관계 맵핑)의 약자이다.

객체와 관계형 데이터베이스의 데이터(이하 SQL)를 자동으로 맵핑해준다.

객체지향 프로그래밍은 클래스를 사용한 객체모델이며

SQL은 테이블을 사용한 모델이다. 서로 다른 형식의 모델이지만

ORM을 통해서 객체 간의 관계를 기반으로 SQL을 자동 생성한다.

 

 

 

TypeORM에 대한 견해

fastify와 express를 SQL를 맵핑할때 자주 사용되는 ORM으로 Sequelize, Knex, TypeORM 등이 있다.

요즘 Typescript가 인기를 얻어 급부상하면서 타입을 지정하여 모델을 정의하는

장점을 최대한 끌어낼 수 있기 때문에  TypeORM의 선택이 적합하다는 생각이 들었다.

특히나 서버와 데이터는 보안이 아주 중요하기 때문에 클라이언트로부터의 모든 요청이

거짓말일 수도 있다는 가정으로부터 데이터를 걸러서 받아야한다. 때문에 Typescript와 TypeORM의

엄격하고 규격화된 타입만을 요청할 수 있도록 만들어 메리트를 극대화시킬 수 있을 것이라 생각했다.

 

 

 

TypeORM Install

1. npm install  &  start

//기본설치
$ npm install typeorm --save

//nest.js에서 사용시
$ npm install --save @nestjs/typeorm typeorm mysql2

//프로젝트 시작
$ npm start

 

 

 

사용예시

※ 모두 공식문서의 예제 코드이다.

 

Active Record 패턴

모델 자체에 쿼리메소드를 정의하여 메소드를 사용하여 객체를 저장, 제거, 불러오는 패턴(방식)이다.

BaseEntity클래스를 사용하여 Use클래스에 상속한 후 사용할 수 있으며 이를 통하여

BaseEntity가 가진 메소드를 만들 수 있고 static을 통해서도 추가적인 커스텀메소드를 만들어 사용할 수 있다.

import { BaseEntity, Entity, PrimaryGeneratedColumn, Column } from "typeorm"

@Entity()
export class User extends BaseEntity {
    @PrimaryGeneratedColumn()
    id: number

    @Column()
    firstName: string

    @Column()
    lastName: string

    @Column()
    isActive: boolean

    static findByName(firstName: string, lastName: string) {
        return this.createQueryBuilder("user")
            .where("user.firstName = :firstName", { firstName })
            .andWhere("user.lastName = :lastName", { lastName })
            .getMany()
    }
}

 

생성자함수 new를 사용하면 새로운 인스턴스를 만들어 DB스키마를 직접 다룰 수 있다.

const userRepository = dataSource.getRepository(User)

const user = new User()
user.firstName = "Timber"
user.lastName = "Saw"
user.isActive = true
await userRepository.save(user)

await userRepository.remove(user)

const users = await userRepository.find({ skip: 2, take: 5 })
const newUsers = await userRepository.findBy({ isActive: true })
const timber = await userRepository.findOneBy({
    firstName: "Timber",
    lastName: "Saw",
})

 

 

Data Mapper 패턴

분리된 클래스에 쿼리 메소드를 정의한다. Repository를 이용하여 객체를 저장하거나 제거, 불러올 수 있다.

모델에 접근하는 Active Record 패턴과 다르게 Repository를 통해서 데이터에 접근이 가능하다.

import { Entity, PrimaryGeneratedColumn, Column } from "typeorm"

@Entity()
export class User {
    @PrimaryGeneratedColumn()
    id: number

    @Column()
    firstName: string

    @Column()
    lastName: string

    @Column()
    isActive: boolean
}

 

getRepository()를 사용해서 접근할 수 있다.

const userRepository = dataSource.getRepository(User)

const user = new User()
user.firstName = "Timber"
user.lastName = "Saw"
user.isActive = true
await userRepository.save(user)

await userRepository.remove(user)

const users = await userRepository.find({ skip: 2, take: 5 })
const newUsers = await userRepository.findBy({ isActive: true })
const timber = await userRepository.findOneBy({
    firstName: "Timber",
    lastName: "Saw",
})

 

 

Active Record  VS  Data Mapper

소프트웨어 개발과 관련하여 항상 염두에 해야 할 것은 애플리케이션을

어떻게 유지 관리할 것인지 생각하는 것이다. 이를 관점으로 보았을때

 

Data Mapper: 대규모 애플리케이션에서 더 효과적인 유지보수성을 지원한다.

 

Active Record: 소규모 앱에서 단순성을 유지하는 데 도움이 된다.

소규모앱의 경우 단순성이 유지됨으로서 유지보수성이 높아진다.

 

 

 

마치며...

전에 로그인기능 구현을 위해서 Nest.js서버와 MySQL을 다룬적이 있다. 이때 typeORM을 통해서 스키마를 만들고 클라이언트로부터 요청받은 정상적인 데이터를 저장하며 사용한 경험이 있다. 실제로 엄청나게 편리함을 느꼈고 앞으로도 객체지향형 서버를 다루게 되면 typeORM을 채택할 의향이 있다. 그리고 사용하게 된다면 반드시 Data Mapper방식으로 접근할지, Active Record방식으로 접근할지도 잘 선택해서 사용해야할 것 같다.

 

출처: https://typeorm.io/

2023. 2. 15. 22:11

Typescript

Javascript를 기반으로 하는 오픈소스이며 Type을 추가하여 올바른 입출력값의 Type을

미리 가이드할 수 있고 해당 Type이 아닌경우 ide가 에러를 일으키기 때문에

문제를 원천적으로 방지할 수 있다.

 

 

Typescript에 대한 견해

분명 대부분의 개발자들이 코딩을 입문한지 얼마 안됐을때 나와 비슷한 생각을 했을 것이다. "그냥 코드간결하고 실행 잘되고, 가독성 좋으면 그만이지 코드 난잡해지게 굳이...?"하지만 이 생각은 단 하나의 Toy프로젝트를 만들고 달라지게 되었다.

 

가장큰 이유는 뜻밖의 에러, 의도치 않은 결과가 실행되는 경우가 적지 않기 때문이었다.

이러한 경우 나는 코드의 Type 문제가 90%였고 내가 혼자서 만드는 Toy프로젝트만 해도 이런데

만약 현업을 하게된다면...? 이건 엄청난 기여를 하겠구나 하고 생각하게 되었다.

특히나 코드의 양이 방대해진다면 나라면 고민할 것도 없이 채택해야겠다! 라고 생각했다.

 

타입스크립트를 사용하지 않는 가정하에 현업을 하여 방대한 코드를 여럿이서 공유하며

개발을 한다고 가정해보면 내가 직접 의도하여 만든 함수나 변수가 아니기 때문에

하나하나 추론을 해 나아가며 return되는 값의 Type이나 함수에 매개변수로는

어떤 Type이 들어가야하는지 등을 알아내기 위한 시간비용은 무시할 수 없다.

 

이런 문제는 Typescript의 사용만으로 크게 개선될 수 있고 엄청난 시간을 절약할 수 있게 된다.

나에게 이것 만으로도 Typescript를 사용해야하는 이유로 충분해졌다.

 

 

 

Typescript Install

 

1. npm install

npm install -g typescript

기본적인 타입스크립트의 인스톨 방식

 

 

2. tsconfig.json

//tsconfig.json 파일을 만들고 아래 코드입력

{
  "compilerOptions" : {
    "target": "es5",
    "module": "commonjs", 
  }
}

Typescript를 사용하기 위해서는 반드시 tsconfig.json파일이 존재해야한다.

 

 

3. ts -> js 변환

//터미널에 입력 (basic javascript의 경우만)
tsc -w

 

 

4. React Typescript 생성

//react와 마찬가지로 node 버전이 14, 혹은 그 이상인 경우만 가능.
npx create-react-app@latest my-app --template typescript

 

 

5. Next Typescript 생성

npx create-next-app --ts
//이 후 "What is your project named?" 메시지가 뜨면 프로젝트 이름을 입력하면 된다.

 

 

 

사용예시

let name :string = "문자만"
//string, boolean, number, null, undefined, bigint, [], {} 등 있다

let name :string[] = ["kim", "park"]
//name변수엔 array에 string인 값만 할당 할 수 있다.

let NAME :{ name : string } = { name : 'kim' }
//NAME변수에 할당된 key(name)속성에 값은 string만 올 수 있다.

let 이름 :{ name? : string } = { name : 'kim' }
//이름변수에 name이란 속성이 올지말지 확실하지 않을땐 물음표를 사용해서
//있을수도 있고 없을 수도 있는 옵션인 타입으로 지정해줄 수 있다.

let name :string | number = 'kim';
let name :string[] | number = [ 'kim', 123 ]

//이런식으로 or기호를 넣어주면 타입 중 하나만 들어가도
//에러가 뜨지 않는다.
//Union type 이라고도 한다.


type myType = string | number;
let name :myType = 123;
//이렇게 타입변수를 만들어줘서 사용도 가능하다.


function FUC (x :number) :number {
    return  x * 2
}
FUC( "123" );
//이런 경우 매개변수타입은 number이고 리턴값도 number여야 하기때문에
//에러가 뜬다. 반드시 number Type으로 적어줘야 제대로 작동한다.

type Member = [number, boolean];
let john:Member = [ 123, false ];
//array타입을 만들때 예시이다.
//tuple타입이라고 한다.


type Member = { name : string };
let john :Member = { name : 'kim' };
//이렇게 name이라는 key엔 스트링이 들어가야한다.

//만약 name속성 외에도 age, address 등등 여러 속성이 필요할 경우
//아래와 같이 대표적으로 지정이 가능하다.
type Member = { [key :string] : string };
let john :Member = { name : 'kim', age : '123', address };


class User {
    name :string;
    constructor(name :string){
        this.name = name;
    }
}
class 타입지정도 할수 있다.

 

 

마치며...

사용예시의 경우 간단하고 대표적인 예만 명시해 두었지만 실제로 프로젝트를 진행하면서 더욱 다양하고 많은 Typescript를 사용하고 있다. 다행이 document에서 서술이 잘 되어있고 여러 사람들이 사용하는 만큼 타 기술블로그에서도 이해하기 쉽게 서술하고 있어 러닝커브가 크지 않았던 것 같다. 앞으로도 잊지말고 typescript 해야겠다.

2023. 2. 5. 22:23

자바스크립트 연산자의 우선순위

연산자의 우선순위란?

간단하게 사칙연산을 예로 우선순위를 생각해보면 10 + 10 + 5 * 5 = 이 경우

우리는 곱하기를 우선적으로 계산하고 나머지 계산식을 차례대로 더하게 될 것이다.

만약 위의 식을 (10 + 10) + 5 * 5 = 이렇게 바꾸게 된다면 5 * 5와 10 + 10의 우선 계산순위가

동일하게 바뀌듯이 자바스크립트 연산자에서도 연산자 간에 우선순위라는 것이 있다는 것이다.

 

 

※ 연산자 타입, 연산자 순으로 정렬해두었다.

  • 멤버
    .[ ]

  • 객체생성과 호출
    ()new

  • 거짓(부정),증가와 감소
    !,  ~,  -,  +,  ++,  --,  typeof,  void,  delete

  • 곱셉, 나눗셈, 나머지
    *,  /,  %

  • 덧셈,뺄셈
    +, -

  • 비트 시프트
    <<, >>, >>>

  • 관계
    <, <=, >, >=, in, instanceof

  • 등호
    ==, ===, !=, !==

  • 비트논리곱
    &

  • 비트 배타적 논리합
    ^

  • 논리 곱
    &&

  • 논리 합
    ||

  • 조건
    ?:

  • 할당
    =, +=, -=, *=, /=, %=, <<=, >>=, >>>=, <<<=, &=, ^=, |=

  • 콤마
    ,

 

대입 연산자 :  =

우변의 계산 값을 좌변에 대입할때 사용한다.

const a = 1, b = 5;
a = b; //a에 b값인 5를 대입
console.log(a); // 5

 

(연산기호) 대입 연산자 :  +=, -=, *=, /=, %=, **=

이해하기가 난해할 수 있지만 쉽게 생각하면 a += b 는  a = a + b 와 같은 식이다.

조금더 간단하게 만든 단축식(short-cut)이라고 생각하면 된다.

// += 더하기 대입연산자
const a = 4, b = 5;
a += b; //a에 a + b의 값을 대입한다.
console.log(a); // 9

// -= 빼기 대입연산자
const a = 4, b = 5;
a -= b; //a에 a - b의 값을 대입한다.
console.log(a); // -1

// *= 곱하기 대입연산자
const a = 4, b = 5;
a *= b; //a에 a * b의 값을 대입한다.
console.log(a); // 20

// /= 나누기 대입연산자
const a = 4, b = 5;
a /= b; //a에 a / b의 값을 대입한다.
console.log(a); // 0.8

// %= 나머지 대입 연산자
const a = 4, b = 5;
a %= b; //a에 a / b의 나머지를 대입한다.
console.log(a); // 4

// **= 지수 대입연산자
//지수승을 말하며 현재값으로는 4의 5승 = 4 ** 5 이다.
let a = 4, b = 5;
a **= b; //a에 a의 b승의 값을 대입한다.
console.log(a); // 1024

//모든 비트연산자도 마찬가지로 작동한다.

 

 

비트연산자 - Bitwise Operator

 

1. 시프트 연산자

시프트 연산자란?

비트를 이동시키는 연산자이다. 즉 10진수를 2진수로 변환하고 2진수로서 연산을 하는 것이다.

시프트연산자는 피연산자가 2개인 2항연산자이며 기호는 <<, >>, <<<, >>> 이 있다.

 

  • ※ 8bit 기준 예시입니다.
0 0 0 0 1 0 1 0

10진수의 정수 10을 2진수로 변환 해줬다.

 

 

<<, >> 설명

우선 >><<에 대해서 설명하자면 방향대로 이진수를 이동시켜준다고 생각하면

이해하기 수월하다.

 

10 >> 2의 경우 1010을 오른쪽으로 두칸 움직이는 것이다. 그렇게된다면 10이 되는 것이다.

오른쪽으로 움직이면서 버려진 2진수의 값은 버려진다.(데이터손실)

0 0 0 0 0 0 1 0

그리고 이를 다시 10진수로 바꿔주면 값은 2가 된다.

10 >> 2 = 2

 

그리고 반대로  10 << 2 의 경우는

0 0 1 0 1 0 0 0

이를 10진수로 바꾸어주면 값은 40이 된다.

10 << 2 = 40

 

이를 통해서 10 * 2의 제곱과 같다는 것을 유추할 수 있다. 즉  a << b = a * 2 ^ b인 것이다.

 

 

 

<<<, >>> 설명

이 연산자는 <<, >>과 다른 것은 음수의 표현이 없다는 것.

즉 시프트연산된 2진수의 값을 절대값으로만 반환한다는 것이다.

 

  • 절대값 시프트연산 예시
let a = -100, b = 2;

console.log(a >> b); //  -25

console.log(a >>> b); //  1073741799

 

2. 그 외 비트연산자

  •  &
    대응되는 비트가 모두 1이면 1을 반환 (비트 AND 연산)

  •  |
    대응되는 비트중에 하나라도 1이면 1을 반환 (비트 OR 연산)

  •  ^
    대응되는 비트가 서로 다르면 1을 반환 (비트 XOR 연산)

  •  ~
    비트가 1이면 0, 0이면 1로 반전시킴 (비트 NOT 연산)

 

&  AND 비트연산 예시

  • 31 & 41  ( 비교대상이 모두 1이면 1 )
0 0 0 1 1 1 1 1
& & & & & & & &
0 0 1 0 1 0 0 1
  • = 9
0 0 0 0 1 0 0 1

 

|  OR 비트연산 예시

  • 31 | 41  ( 비교대상이 하나라도 1이면 1 )
0 0 0 1 1 1 1 1
| | | | | | | |
0 0 1 0 1 0 0 1
  • = 63
0 0 1 1 1 1 1 1

 

^  XOR 비트연산 예시

  • 31 ^ 41  ( 비교대상이 다르면 1 )
0 0 0 1 1 1 1 1
| | | | | | | |
0 0 1 0 1 0 0 1
  • = 54
0 0 1 1 0 1 1 0

 

~  NOT 비트연산 예시

  • ~31  ( 비트를 반전시킴 )
0 0 0 1 1 1 1 1
  • = 224
1 1 1 0 0 0 0 0

 

 

 

논리 연산자

논리연산자란?

연산자에 '논리’라는 수식어가 붙는다. 논리 연산자는 피연산자로 boolean type뿐 아니라

모든 타입의 값을 받을 수 있으며. 연산 결과도 어떠한 타입의 값을 받을 수 있다.

 

|| OR 연산자

  • 피연산자를 불린형으로 변환했을때 피연산자중 하나라도 true인 경우 true를 반환한다.
console.log( true || true );  // true
console.log( true || false );  // true
console.log( false || false );  // false
console.log( false || true || false );  // true

console.log( 1 || 0 );  // true
console.log( 1 || 1 || 0 );  // true
console.log( 0 || 1 );  // true
console.log( 0 || 0 );  // false

피연산자가 불린형이 아닌경우 평가를 불린형으로 변환된다.

예시에서 0과 1같은 경우

 

&&  AND 연산자

  • 피연산자를 불린형으로 변환했을때 피연산자 모두 true인 경우에만 true를 반환한다.
console.log( true && true );  // true
console.log( true && false );  // false
console.log( true && true && true );  // true
console.log( true && false && true );  // false

console.log( 1 && 0 );  // false
console.log( 1 && 1 );  // true
console.log( 1 && 0 && 1 );  // false
console.log( 1 && 1 && 1 );  // true

 

!  NOT 연산자

  • 피연산자를 불린형으로 변환했을때 피연산자를 반전시킨다.
console.log( !true )  // false
console.log( !false )  // true

console.log( !0 )  // true
console.log( !1 )  // false

 

 

증감연산자

증감연산자란?

변수의 값을 1씩 증가시키거나 1씩 감소시키는 연산자이다. 증감연산자는

1씩 증가시키는 Increment연산자1씩 감소시키는 Decrement 연산자가 있다.

 

Increment ++

let num = 0;
num++;
console.log(num);  // 1

/*
num++ 를 풀어쓴다면
num = num + 1;  이다.
*/

Decrement --

let num = 1;
num--;
console.log(num);  // 0

/*
num-- 를 풀어쓴다면
num = num - 1;  이다.
*/

 

 

 

전위 연산자, 후위 연산자

연산자 위치에 따라서 전위(prefix), 후위(postfix)연산자로 나뉜다.

 

전위연산자

let a = 3;
const b = ++a

console.log(a, b);  // 4, 4

연산자가 앞에 붙어있기 때문에 전위 연산자이다.

전위연산자의 특징을 위에 예시로 설명하면 ++a가 b에 할당되기 전에 a가 증가연산이된 후

a자신에게 먼저 할당이 된 이후에  b에 할당된다.

 

전위연산자 풀이

let a = 3;
const b;

a = a + 1;
b = a

console.log(a, b);  // 4, 4

 

 

후위연산자

let a = 3;
const b = a++;

console.log(a, b);  // 4, 3

연산자가 뒤에 붙어있어 후위연산자이다.

후위연산자의 특징을 위에 예시로 설명하면 우선적으로 b에 a가 할당된다.

그 이후에 a = a + 1연산이 이루어지게된다.

 

후위연산자 풀이

let a = 3;
const b;

b = a;  // 먼저 할당되어 b = 3; 인 상태
a = a + 1;  // a = 4

console.log(a, b);  // 4, 3

 

 

증감연산자 대신 복합 대입연산자?

eslint에서는 증감연산자는 자동으로 세미콜론이 추가되는 대상이기 때문에

예상치 못하게 코드의 흐름을 변경시켜 오류를 발생시킬 가능성이 있다고 한다.

그렇기 때문에 eslint에서는 복합대입연산자 사용을 권하고 있는 모양이다.

 

 

 

마치며...

이번엔 자바스크립트 연산자에 대해서 쭉 포스팅을 해보았다. 처음 접해보는 비트연산식이나 전위, 후위 연산자에 대해서 작동형태만 알고 있을 뿐 정확한 작동방식은 모르고 있었는데 이번 포스팅을 통해 더 자세한 로직의 형태를 이해할 수 있게 되었다. 연산자는 코딩을 함에 있어 아주 중요한 내용이니 반드시 index를 기억해두어야겠다.

2023. 2. 4. 18:00

Array - 배열

  • 정의
    배열은 index를 가지며 여러 자료를 저장할 수 있는 자료구조이다.
    중복도 가능하며 저장된 데이터는 인덱스를 통해 접근이 가능하다.

  • 속성
    객체와 같이 여러자료를 저장할 수 있는 자료구조
    이다.
    index / value 를 Pair로 저장되는 구조이다.
    index는 배열의 length -1 이며 0부터 시작한다. ( [1,2,3] => index : 0,1,2 )

 

배열 만들기

  • 배열도 객체와 마찬가지로 빈 배열을 변수에 할당해주어 사용한다.
let arr = [];

 

  • 예시 데이터
let arr = [1,"two",3,{name:"bob smith"},[1,2,3]];

 

배열의 인덱스값(index value)에 접근하기

// arr = [1,"two",3,{name:"bob smith"},[1,2,3]];

console.log(arr[0]);  // 1
console.log(arr[1]);  // two
console.log(arr[3]);  // {name: 'bob smith'}
console.log(arr[4]);  // [1, 2, 3]

//배열 내부의 오브젝트 키값에 접근하기
console.log(arr[3].name); // bob smith

//배열 내부의 배열 인덱스값에 접근하기
console.log(arr[4][0]);  // 1
console.log(arr[4][1]);  // 2

배열 내부 오브젝트(객체)에 접근하는 방식은 프로퍼티 접근연산자   .   (dot notation)를 사용하면 된다.

배열 내부에 배열로 접근하기 위해선  arr[4][0]  대괄호(bracket)를 연속으로 사용하여 접근 할 수 있다.

 

 

 

배열에 새로운 요소 추가

  • push()와 arr.length를 이용한 추가방식
// arr = [1,"two",3,{name:"bob smith"},[1,2,3]];

//push()를 이용한 추가방식
console.log(arr.push(1995));  // 6
console.log(arr);
// [1,"two",3,{name:"bob smith"},[1,2,3],1995]


//length를 이용한 추가방식
arr[arr.length] = "last"
console.log(arr);
// [1,"two",3,{name:"bob smith"},[1,2,3],1995,"last"]

push()는 추가된 이 후의 길이인 arr.length를 반환한다.

 

 

  • unshift()로 첫번째 위치에 요소 추가하기
let newArr = [2,3];

console.log(newArr.unshift(1));  // 3

console.log(newArr);  // [1, 2, 3]

unshift()는 추가된 이 후의 길이인 arr.length를 반환한다.

 

 

배열 요소 삭제

  • delete로 삭제하기
// arr = [1,"two",3,{name:"bob smith"},[1,2,3],1995,"last"]

delete arr[6];

console.log(arr);
// [1,"two",3,{name:"bob smith"},[1,2,3],1995, 비어있음 ]

console.log(arr[6]);
// undefined

console.log(arr.length);
// 7

delete로 삭제를 하게되면 해당 요소전체가 삭제되는 것이 아니라 해당 값만 undefined가 된다.

따라서 보는 것 과 같이 index는 남아있기 때문에 배열의 길이는 그대로이다.

무작정 delete를 사용해서 배열을 삭제하기보다 용도에 따라 사용하는 것이 적합할 것 같다.

 

 

  • pop()으로 삭제
// arr = [1,"two",3,{name:"bob smith"},[1,2,3],1995, 비어있음 ]

console.log(arr.pop());
// undefined

console.log(arr);
// [1,"two",3,{name:"bob smith"},[1,2,3],1995]

pop()은 접근한 배열의 마지막 요소를 삭제하고 삭제된 요소의 값을 반환한다.

delete와는 다르게 index/value를 pair로 삭제하기 때문에 length도 줄어들게된다.

 

 

  • splice()로 선택삭제, 교체

    ex) splice(삭제시작index: 0, 지울갯수(옵션): 1, 교체할요소(옵션): "Hello");
         첫번쨰 인자만 넣어 줄 경우 입력한 시작인덱스 뒤로 모두 삭제한다.
         첫번째 인덱스에서 한개 = 첫번째 인덱스이다.  삭제 후 "Hello"요소를 넣어준다.
// arr = [1,"two",3,{name:"bob smith"},[1,2,3],1995]

console.log( arr.splice(0,1) );  // [1]
console.log(arr);  // ["two",3,{name:"bob smith"},[1,2,3],1995]


console.log( arr.splice(2,1,{name: "bori-bob black-smith"}) );
// {name: "bob smith"}
console.log(arr);
// ["two",3,{name:"bori-bob black-smith"},[1,2,3],1995]

splice()는 pop()과 마찬가지로 삭제한 요소를 반환한다.

splice()는 삭제뿐 아니라 새로운 요소를 넣어 줄 수도 있기 때문에 잘 기억해두면 좋을 것 같다.

 

 

배열 연결하기

const arr1 = ["one", "two", "three"];
const arr2 = [4, 5, 6];
const arr3 = ["칠", "팔", "구", "십"];

const linkedArr = arr1.concat(arr2, arr3);
console.log(linkedArr);
// ['one', 'two', 'three', 4, 5, 6, '칠', '팔', '구', '십']

const linkedArr2 = arr3.concat(arr2,arr1);
console.log(linkedArr2);
// ['칠', '팔', '구', '십', 4, 5, 6, 'one', 'two', 'three']

 

 

  • concat()을 사용하여 배열 복제하기
const arr1 = ["a", "b", "c", "d", "e", "f"];

const arr2 = [].concat(arr1);

console.log(arr1)  // ["a", "b", "c", "d", "e", "f"]
console.log(arr2)  // ["a", "b", "c", "d", "e", "f"]

빈배열에 .concat(복제할배열) 을 하게되면 해당 변수에 복사되어 할당된다.

 

 

 

특정 조건을 만족하는 요소만 필터링하기

  • filter()로 필터링하기

    ex) filter(function(배열의요소: value, 인덱스(옵션): index, 함수를호출한배열(옵션): array));
const mergeArr = ["one", "two", "three", 1, 2, 3];

const filterArr = mergeArr.filter(value => typeof value === "number");

console.log(filterArr);  // [1, 2, 3]



/* 모든 옵션을 사용해서 적용 */
const mergeArr2 = ["one", "two", "three", 1, 2, 3];

const filterArr2 = mergeArr2.filter( (value, index, array) => {
console.log(`${index} index의 값: ` + value);
/* console.log
0 index의 값: one
1 index의 값: two
2 index의 값: three
...
*/

return (typeof value === "string") && (typeof array[index] === "string");
} );

console.log(filterArr2);  // ['one', 'two', 'three']

filter()를 사용하면 console.log()를 통해서배열 모두를 순회하는 것을 확인할 수 있다.

그리고 filter함수는 내부 함수를 조건으로서 반환되는 값을 true / false 여부에 따라서 추가할지 버릴지 판단하게 된다.

필터링 된 배열을 저장할 변수가 필요하니 반드시 변수에 할당시켜주자.

 

 

 

배열내의 요소를 가공하기

  • map()을 사용하여 요소를 변경한 새로운 배열을 만들기

    ex) map(function(배열의요소: value, 인덱스(옵션): index, 함수를호출한배열(옵션): array));
const numArr = [1, 3, 15, 22];
const mapArr = numArr.map((value,index) => {
    console.log(`${index+1}번째: ` + value);
    /* console.log
    1번째: 1
    2번째: 3
    3번째: 15
    4번째: 22
    */
    return `${index+1}번째: ` + value * 100
})
console.log(mapArr);
// ['1번째: 100', '2번째: 300', '3번째: 1500', '4번째: 2200']

map()을 사용하면 filter()와 마찬가지로 배열 모두를 순회하는 것을 확인할 수 있다.

return하는 값을 순회하면서 모두 적용시켜 주는 배열을 반환한다.

 

 

 

요소를 순회하며 연산하기

  •  reduce()를 사용하여 요소의 값을 순회하며 연산

  • ex ①  reduce (샐행값을 누적하는 콜백함수: callback, 초기값: initiaValue);

  • ex ②  callback (
                    callback함수의 반환값을 누적: accumulator,
                                         
    배열의 현재요소: currentValue,
                                      배열의 현재인덱스: index,
                                                호출한 배열:
    array
    ) { ... return accumulator  }

 

 

reduce의 작동방식

1. 각 요소의 전체를 순회연산할때의 initialValue

  • 초기값을 설정하면 연산을 시작할때 초기값으로 설정된다. 
    또한 순회하는 각 요소의 전체가 대입되어 연산해야 하는 경우에 초기값을 생략가능하다.
    ex) [1,2,3,4,5] 요소를 순회하며 더할 경우

 

2.  요소가 배열, 객체이며 특정 index || key의 값으로 순회연산할때의 initialValue

  • 초기값은 똑같이 초기값으로 적용되나, 특정 index나 key값으로 순회연산할때는
    initialValue 설정하지 않으면 원치않는 결과를 마주하게 될 수도 있다.

  • reduce()함수는 최초 호출 시
    callback => initialValue => crrentValue => currentIndex 순으로 동작하는데

    initialValue의 자리가 비어 있는 경우그 자리를 crrentValue가 대체 해버린다.

    callback => crrentValue => crrentValue => currentIndex 순서가 이렇게 변동 되어서
    crrentValue순서가 + 1이 되어버린다.

    그러므로 순서대로 작동하게 만드려면 반드시 initialValue에 0을 설정해두자

  • initialValue를 설정하지 않는경우 강제적으로 currentIndex가 1이되어
    가장 첫번째 요소인 index 0 은 건너뛰게 되는데 이 경우 callback호출없이
    요소 자체를 반환하게 되고 이후 연산은 강제로 문자열연산으로 변환된다.
    ex) 
            [ { name: "a", age: 30 }, { name: "a", age: 20 }, { name: "a", age: 30 }, { name: "a", age: 20 }, ]
            / /  Object.age를 모두 더하는 reduce()

            O  =>   100
            X   =>   [object Object]203020

 

3. initialValue에 의한 순서 변화

  • callback =>   initialValue =>   crrentValue =>   currentIndex
        콜백               초기값               첫번째요소               0

  • callback =>   crrentValue =>   crrentValue =>   currentIndex 
        콜백              첫번째요소        두번째요소                1

 

reduce 예시

  • 요소 모두 더하기
const numArr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

const sumAll = (accumulator, currentNum) => {
  return accumulator + currentNum;
}

const result = numArr.reduce(sumAll); 
// reduce내부에서 직접적으로 함수를 넣어줘도 무관하다.
// 요소자체를 순회연산 하고 특정 key값이나 객체가 아니므로 initialValue 생략가능.

console.log('result =', result); // result = 55

 

  • 객체 요소에서 특정 key의 값만 모두 더하기
const objArr = [
	{
		name: 'Danaka',
		age: 39,
	},
	{
		name: 'B',
		age: 40,
	},
	{
		name: '민식이',
		age: 30,
	},
	{
		name: '준식이',
		age: 30,
	},
];

const result = objArr.reduce((accumulator, currentObj) => {
  return accumulator + currentObj.age;
}, 0);

console.log('result =', result); // result = 139

 

 

 

배열정렬하기

  • sort()를 사용하여 정렬

  • sort(정렬순서를 정의하는 함수:compareFunction);
    compareFunction는 두개의 배열 요소를 매개변수로 입력받는다.

  • 매개변수 a, b에 의한 정렬기준
    생략  =  요소가 문자열로 취급되며 유니코드 값의 순서로 정렬된다.
    반환되는 값이 음수  =  a가 b보다 앞에 오도록 정렬한다.
    반환되는 값이 양수  =  b가 a보다 앞에 오도록 정렬한다.
    반환되는 값이 0이면  =  순서를 변경하지 않는다.

 

1.문자열 정렬

const abcArr = ['B', 'C', 'A', 'E', 'D'];
const abcArr2 = abcArr;
const abcArr3 = abcArr;
const abcArr4 = abcArr;

//오름차순 (Default)
abcArr.sort();
console.log(abcArr); // ['A', 'B', 'C', 'D', 'E']


//문자열 내림차순은 조건문을 잘 달아줘야 한다.
abcArr2.sort(function (a, b) {
    if (a > b) return -1;
    if (b > a) return 1;
    return 0;
});
console.log(abcArr2); // ['E', 'D', 'C', 'B', 'A']


//두번째 방법으론 reverse()함수를 사용하는 것이다. 로직상 비효율적이지만 코드는 짧아진다.
abcArr3.sort();
abcArr3.reverse();
console.log(abcArr3); // ['E', 'D', 'C', 'B', 'A']




//가장 간결하고 효율적인 삼항연산자 (가독성은 좋지 않다)
abcArr4.sort((a, b) => a>b ? -1 : b>a ? 1 : 0);
console.log(abcArr4); // ['E', 'D', 'C', 'B', 'A']

 

 

 

2.숫자 정렬

// 숫자의 경우 반드시 비교조건문을 넣어줘야한다.
const numArr = [200, 101, 2, 9, 1, 5];
const numArr2 = numArr;
const numArr3 = numArr;

// 조건문을 넣지 않았을때
numArr.sort();
console.log(numArr);  // [1, 101, 2, 200, 5, 9]


// 오름차순
numArr2.sort((a, b) => a - b);
console.log(numArr2);  // [1, 2, 5, 9, 101, 200]


// 내림차순
numArr3.sort((a, b) => b - a);
console.log(numArr3);  // [200, 101, 9, 5, 2, 1]

 

배열 초기화하기

  • 직접 값을 할당하여 초기화하기

  • new 생성자함수를 사용하여 초기화 하기
// 직접 값을 할당시켜주어 초기화 시킨다.
let arr = [1, 2, 3, 4, 5];

// new 생성자 함수를 사용하여 배열을 형성하고 fill함수를 사용하여 모든 요소에 값을 넣어준다.
let arr = new Array(5).fill(0);

/*
   길이가 5이면서 모든 원소의 값이 0인 배열로 초기화된다.
   arr = [0, 0, 0, 0, 0]
*/

 

 

마치며...

생각했던 것 보다 포스팅하는 시간이 길었던 것 같다. 전반적으로 객체보다도 더 데이터 가공성이 뛰어난게 배열인 것 같다고 생각을 했다. 물론 상황에 따라 다르겠지만... :) 그리고 개발을 하면서 데이터를 가공할 일이 점점 많아지기 때문에 배열과 객체를 많이 다루게 되는 것 같다.

2023. 2. 2. 00:59

객체(Object)

  • 정의
    객체는 관련된 데이터와 함수의 집합이다. 여러 데이터와 함수로 이루어져 있으며
    객체 내부에 존재할때는 주로 "프로퍼티" 또는 "메소드"라 부른다.

  • 속성
    여러 속성을 하나의 변수에 저장할 수 있도록 해주는 데이터 타입으로
    Key / Value 를 Pair로 저장할 수 있는 구조이다.
    추가내용: Javascript는 객체기반의 스크립트 언어이다.

 

객체 만들기

객체를 만들때는 변수를 선언해주고 {} 를 할당해주면 된다.

let obj = {};

 

표기법

  1. 마침표 표기법 (dot notation)
    프로퍼티 접근 연산자   .   을 사용한다.

  2. 대괄호 표기법 (bracket notation)
    프로퍼티 접근연산자   [  ]   를 사용한다.
    프로퍼티를 반드시 문자열로 표기해야 동작한다.
let obj = {
  name: 'bob smith'
};

// dot notation
console.log(obj.name);  // bob smith

// bracket notation
console.log(obj['name']); // bob smith

// bracket notation은 프로퍼티를 반드시 문자열로 표기해주어야 한다.

 

 

  • 우선 여러 기능을 사용해보기 위해서 예시데이터를 넣어 줬다.
let obj = {
        name: [
            "bob", "smith",
            {bob: {smith: {bobSmith: "is name"}}},
        ],
        age: 32,
        gender: 'male',
        interests: ['music', 'skiing'],
        bio: function() {
            alert(this.name[0] + ' ' + this.name[1] + ' is ' + this.age + ' years old. He likes ' + this.interests[0] + ' and ' + this.interests[1] + '.');
        },
        greeting: function() {
            alert('Hi! I\'m ' + this.name[0] + '.');
        }
    };

 

1. obj객체에 프로퍼티 추가

/* 객체변수.새로만들어줄key = value; */
obj.tall = 179;


/* 함수형 프로퍼티 추가도 쉽게 가능하다 */
const sayY = obj.sayYeah = () => {     alert( 'Yeah~~~!!!' ) }; //새로운 객체멤버 생성

obj.sayYeah(); // Yeah~~~!!!

 

2. 객체 ↔ 스트링 변환

    const changeString = JSON.stringify(obj.name); // 객체를 스트링으로 변환.
    console.log("객체 => 스트링",changeString);
    // JSON.객체 => 스트링 ["bob","smith",{"bob":{"smith":{"bobSmith":"is name"}}}]

    const stringToJsonObject = JSON.parse(`{ "name": "John Doe", "age":30 }`); //스트링을 객체로 변환.
    console.log("스트링 => 객체",stringToJsonObject);
    // {name: 'John Doe', age: 30}
    //    age: 30
    //    name: "John Doe"
    // ▶ [[Prototype]] : Object

 

3. 객체 프로퍼티 삭제하기

예제 1

    console.log("name 삭제전, name:",obj.name);
    // name 삭제전, name:  ["bob", "smith", {...}]
    
    delete obj.name[0]; //프로퍼티 삭제
    
    console.log("name 삭제후, name:",obj.name);
    // name 삭제후, name:  ["비어있음", "smith", {...}]

예제2

    console.log("name 삭제전, name:",obj.name);
    /*
     name 삭제전, name:
     0: "bob"
     1: "smith"
     2:
       bob:
        smith: 
    */
    
    delete obj.name[2].bob.smith.bobSmith; //해당프로퍼티 삭제
    
    
    console.log("name 삭제후, name:",obj.name);
    /*
     name 삭제후, name:
     0: "bob"
     1: "smith"
     2:
       bob:
        smith: 
    */

 

실습 해보면서 특이한 점을 발견했다. 분명 삭제를 하기전과 삭제 후의 콘솔로그가 달라야할텐데...?

이상하게도 둘은 같은 로그를 나타내고 있었다. 이를 통해서 name 프로퍼티에 겹객체를 넣어 둔 것이

이러한 작동을 일으켰다는 것을 유추할 수 있었다.

 

  • 이를 통해 유추할 수 있는 내용...
    가장 먼저 선언된 일반적인 키를 삭제하는 경우엔 콘솔로그와 동기적으로 작동하지만
    객체 내부의 객체키(겹객체)를 삭제하는경우. 내부의 키가 모두 삭제된 이후에 콘솔로그가
    작동하는 것을 알 수 있었다. (콘솔로그와 비동기적으로 작동)
    이 현상에 대한 원인은 추 후 학습하게 된다면 따로 내용을 추가해야겠다.

혹시 이 게시글을 보고 계신분들중 원인을 알고 계신분은 댓글로 남겨주시면 감사드리겠습니다!

 

 

4. 구조분해할당을 통한 프로퍼티 분류

    const { name, ...others } = obj;
    
    console.log("name array :",name); // name array : (3) ['bob', 'smith', {…}]
    console.log("name :",name[0] + " " + name[1]); // name : bob smith
    
    console.log("others :",others);
    // others : {age: 32, gender: 'male', interests: Array(2), bio: ƒ, greeting: ƒ, …}

구조분해할당을 사용하여 name을 받고 spread연산자를 사용하여 name을 제외한

obj내의 모든 프로퍼티의 집합을 others라는 새로운 변수로서 할당시켜준 것이다.

 

5. 객체의 모든 key와 value

    const objKeys = Object.keys(obj);
    const objValues = Object.values(obj);
    
    console.log( objKeys );
    // (7) ['name', 'age', 'gender', 'interests', 'bio', 'greeting', 'sayYeah']
    
    console.log( objValues );
    /*
    (7) [Array(3), 32, 'male', Array(2), ƒ, ƒ, ƒ]
    0: (3) ['bob', 'smith', {…}]
    1: 32
    2: "male"
    3: (2) ['music', 'skiing']
    4: ƒ ()
    5: ƒ ()
    6: () => { alert( 'Yeah~~~!!!' ) }
    length: 7
    */

 

 

6. 프로퍼티의 존재여부 확인하기

    // includes를 통한 존재여부 확인
    const isExist_name = Object.keys(obj).includes('name');
    const isExist_hobby = Object.keys(obj).includes('hobby');

    console.log("------------------------------------");
    console.log("includes를 통한 존재여부 확인!");
    console.log("name의 존재여부 :",isExist_name,"\nhobby의 존재여부: ",isExist_hobby);
    console.log("------------------------------------");
    /*
    ------------------------------------
    includes를 통한 존재여부 확인!
    name의 존재여부 : true 
    hobby의 존재여부:  false
    ------------------------------------
    */
    
    
    // key in obj를 통한 존재여부 확인
    obj.hobby = undefined; //undefined값을 가진 프로퍼티 추가
    const isExist_name_in = 'name' in obj;
    const isExist_hobby_in = 'hobby' in obj;
    console.log("key in obj를 통한 존재여부 확인!");
    console.log("name의 존재여부 :",isExist_name_in,"\nhobby의 존재여부: ",isExist_hobby_in);
    console.log("------------------------------------");
    /*
    ------------------------------------
    key in obj를 통한 존재여부 확인!
    name의 존재여부 : true 
    hobby의 존재여부:  true
    ------------------------------------
    */
    //undefined 값을 가진다고 프로퍼티가 존재하지 않는 것이 아니다. 그러므로 true를 반환한다
    
    
    // 가장 정확한 존재여부 확인 방법은 hasOwnProperty함수를 사용하는 것이다.
    // 해당 객체의 직접적인 속성만을 검사하기 때문이다.
    const hasOwnName = obj.hasOwnProperty('name');
    const hasOwnHobby = obj.hasOwnProperty('hobby');
    console.log("hasOwnProperty를 통한 존재여부 확인!");
    console.log("name의 존재여부 :",hasOwnName,"\nhobby의 존재여부: ",hasOwnHobby);
    console.log("------------------------------------");
    /*
    ------------------------------------
    hasOwnProperty를 통한 존재여부 확인!
    name의 존재여부 : true 
    hobby의 존재여부:  true
    ------------------------------------
    */

위 내용에서 key in obj는 과거Object의 prototype 마저도 체크하기 때문에

프로퍼티의 존재여부를 크게 착각할 수도 있었다. 하지만 학습을 하는 과정에서는

적용 되지 않았고, Javascript Document에서 찾아보니 현재는 프로토타입체크를

지원하지 않게 되어 더 이상 상관없게 되었다.

 

마치며...

객체에 대해서 더 자세하게 학습을 진행하면서 웹개발이 주목적인 자바스크립트에선 학습우선순위 0순위이지 않을까? 라는 생각이 들었다. 그리고 데이터를 다루는 데에 있어 아주 유용한 기능들이 매우 편리하게 사용할 수 있도록 구현되어 있어 더욱 마음에 들었다. 다음포스팅은 객체와 마찬가지로 데이터다루기에 적합한 배열에 대해서 포스팅을 해봐야겠다.

2023. 2. 2. 00:12

이 게시글에 모든 예시는 아래 링크를 통해서 사용해 볼 수 있다.스키마 이름이나 칼럼이름 역시 같아 결과값이 그대로 적용되어 보여진다.

https://www.w3schools.com/mysql/trymysql.asp?filename=trysql_select_all

 

MySQL Tryit Editor v1.0

WebSQL stores a Database locally, on the user's computer. Each user gets their own Database object. WebSQL is supported in Chrome, Safari, and Opera. If you use another browser you will still be able to use our Try SQL Editor, but a different version, usin

www.w3schools.com

 

비상관 서브쿼리

연계되지않고 독립적으로 반환되는 서브쿼리를 뜻한다.

 

예제

SELECT
  CategoryID, CategoryName, Description,
  (SELECT ProductName FROM Products WHERE ProductID = 1)
FROM Categories;
-- 위의 Category ID, NAME, DESCRIPTION 모두 Categories 테이블에
-- 속하는 데이터이지만 괄호안에 서브쿼리는 Products 테이블에서 가져온 데이터이다.


SELECT * FROM Products
WHERE Price < (
  SELECT AVG(Price) FROM Products
);
-- Price 가 Products테이블에서 평균 Price보다 낮은 경우만 보여준다.


SELECT
  CategoryID, CategoryName, Description
FROM Categories
WHERE
  CategoryID =
  (SELECT CategoryID FROM Products
  WHERE ProductName = 'Chais');
-- Products 테이블에서 CategoryID기준으로 ProductName = 'Chais'인 데이터를
-- 가져와서 Categories테이블에서 CategoryID와 같은 CategoryID, CategoryName, Description의 데이터를 보여준다.


SELECT
  CategoryID, CategoryName, Description
FROM Categories
WHERE
  CategoryID IN
  (SELECT CategoryID FROM Products
  WHERE Price > 50);
-- 위와 마찬가지로 역순으로 괄호부터 해석해나가면 좀 더 쉽게 접근이 가능하다.

 

ALL, ANY

  • ~ ALL(서브쿼리) = 서브쿼리의 모든 결과에 대해 ~하다
  • ~ ANY(서브쿼리) = 서브쿼리의 하나 이상의 결과에 대해 ~하다

 

연산자 + ANY(SUBQUERY)

> ANY >= ANY < ANY <= ANY = ANY != ANY
최소값보다 크면 최소값보다 크거나 같으면 최대값보다 작으면 최대값보다 작거나 같으면 IN과 같은 기능 NOT IN과 같은
기능

 

연산자 + ALL(SUBQUERY)

> ALL >= ALL < ALL <= ALL = ALL != ALL
최소값보다 크면 최소값보다 크거나 같으면 최대값보다 작으면 최대값보다 작거나 같으면 서브 쿼리의
결과가 1건이면
괜찮지만 여러 건이면 오류가 발생
서브 쿼리의
결과가 1건이면
괜찮지만 여러 건이면 오류가 발생

 

예제

SELECT * FROM Products
WHERE Price > ALL (
  SELECT Price FROM Products
  WHERE CategoryID = 2
);
-- 서브쿼리 = CategoryID가 2인 Price들을 모두 가져온다. 그리고
-- 그 모든 데이터 들중 어떤 것보다(ALL의 의미이며 현재는 수의 비교이기 때문에
-- 데이터들 중에서 가장 큰 값이 기준이 된다.) Price가 큰 것을 가져온다.


SELECT
  CategoryID, CategoryName, Description
FROM Categories
WHERE
  CategoryID = ANY
  (SELECT CategoryID FROM Products
  WHERE Price > 50);
-- 위와 같은 조건인 경우 in을 쓰는게 더 적합하다.
-- ANY 의 경우는 IN으로 사용하면 같은 결과를 반환한다.

 

 

상관 서브쿼리

반환되는 쿼리로부터 연계되는 서브쿼리를 뜻한다.

 

예제

SELECT
  ProductID, ProductName,
  (
    SELECT CategoryName FROM Categories C
    WHERE C.CategoryID = P.CategoryID
  ) AS CategoryName
FROM Products P;
-- Categories C 이것은 AS를 생략한 문구로 C로 별명을 지어준 것이다.
-- 마찬가지로 Products P를 통해서 P로 별명을 만들어준 후 P. 으로 CategoryID에
-- 접근하고 C. 을 통해 CategoryID에 접근한 것이다. AS를 생략할 경우에는 절대로
-- 문자열을 사용하면 안된다. 공백 또한 안된다. 공백을 넣고 싶다면 백틱으로 별명을
-- 지어주고 접근할때역시 백틱을 사용해줘야 한다.

 

EXISTS / NOT EXISTS 연산자

예제

SELECT
  CategoryID, CategoryName
  -- ,(SELECT MAX(P.Price) FROM Products P
  -- WHERE P.CategoryID = C.CategoryID
  -- ) AS MaxPrice
FROM Categories C
WHERE EXISTS (
  SELECT * FROM Products P
  WHERE P.CategoryID = C.CategoryID
  AND P.Price > 80
);
-- EXISTS의 서브쿼리에 맞게 존재하는 결과들만 반환한다.

 

JOIN(INNER JOIN) 내부조인

 

예제

SELECT * FROM Categories C
JOIN Products P 
  ON C.CategoryID = P.CategoryID; 
-- Categories 와 Products 두 테이블을 합쳐준다(JOIN) 어떤 기준으로?(ON)
-- 각 테이블의 CategoryID를 기준으로 합쳐준다.

SELECT C.CategoryID, C.CategoryName, P.ProductName
FROM Categories C
JOIN Products P 
  ON C.CategoryID = P.CategoryID; 
-- ambiguous 주의!(같은 이름의 column이 있는경우 뜨는 에러)
-- 조인으로 Products에서 Categories테이블의 CategoryID와 Products의
-- CategoryID가 같은경우만 반환하고 그 후 P. 으로 접근하여 ProductName를 반환한다.

 

여러 테이블 JOIN

SELECT 
  C.CategoryID, C.CategoryName, 
  P.ProductName, 
  O.OrderDate,
  D.Quantity
FROM Categories C
JOIN Products P 
  ON C.CategoryID = P.CategoryID
JOIN OrderDetails D
  ON P.ProductID = D.ProductID
JOIN Orders O
  ON O.OrderID = D.OrderID;
-- 다른 여러테이블의 데이터가 필요할 경우 여러테이블을 JOIN하여 사용할 수 있다.

 

JOIN 테이블 GROUP하기

SELECT 
  C.CategoryName,
  MIN(O.OrderDate) AS FirstOrder,
  MAX(O.OrderDate) AS LastOrder,
  SUM(D.Quantity) AS TotalQuantity
FROM Categories C
JOIN Products P 
  ON C.CategoryID = P.CategoryID
JOIN OrderDetails D
  ON P.ProductID = D.ProductID
JOIN Orders O
  ON O.OrderID = D.OrderID
GROUP BY C.CategoryID;
-- C.CategoryID의 기준으로 GROUPING 해줬다. C.CategoryID는 1~8까지
-- 중복되는 숫자가 없고 종류별로 있기 때문에 종류별로 퍼스트,라스트오더와 팔린갯수를
-- 모두 볼 수 있다.


SELECT 
  C.CategoryName, P.ProductName,
  MIN(O.OrderDate) AS FirstOrder,
  MAX(O.OrderDate) AS LastOrder,
  SUM(D.Quantity) AS TotalQuantity
FROM Categories C
JOIN Products P 
  ON C.CategoryID = P.CategoryID
JOIN OrderDetails D
  ON P.ProductID = D.ProductID
JOIN Orders O
  ON O.OrderID = D.OrderID
GROUP BY C.CategoryID, P.ProductID;
-- C.CategoryID를 첫 기준으로 오름차순으로 정렬 된 후 P.ProductID도
-- 오름차순으로 정렬한다.

 

SELF JOIN - 같은 테이블끼리 JOIN

SELECT
  E1.EmployeeID, CONCAT_WS(' ', E1.FirstName, E1.LastName) AS Employee,
  E2.EmployeeID, CONCAT_WS(' ', E2.FirstName, E2.LastName) AS NextEmployee
FROM Employees E1 JOIN Employees E2
ON E1.EmployeeID + 1 = E2.EmployeeID;

-- 1번의 전, 마지막 번호의 다음이 없다.(외부 조인으로 해결가능)
-- 위와같이 같은 테이블이지만 별명을 다르게 만들어줘서 셀프조인이 가능하다.

 

LEFT/RIGHT OUTER JOIN - 외부 조인

SELECT
  E1.EmployeeID, CONCAT_WS(' ', E1.FirstName, E1.LastName) AS Employee,
  E2.EmployeeID, CONCAT_WS(' ', E2.FirstName, E2.LastName) AS NextEmployee
FROM Employees E1
LEFT JOIN Employees E2
ON E1.EmployeeID + 1 = E2.EmployeeID
ORDER BY E1.EmployeeID;

-- LEFT를 RIGHT로 바꿔서도 실행해 볼 것
-- 반환된 양쪽 데이터에서 오른쪽이나 왼쪽데이터가 NULL이라도 반환한다.


SELECT
  C.CustomerName, S.SupplierName,
  C.City, C.Country
FROM Customers C
LEFT JOIN Suppliers S
ON C.City = S.City AND C.Country = S.Country;

-- LEFT는 SupplierName을 가지지 않은 CustomerName도 반환한다. RIGHT는 반대이다.


SELECT
  IFNULL(C.CustomerName, '-- NO CUSTOMER --'),
  IFNULL(S.SupplierName, '-- NO SUPPLIER --'),
  IFNULL(C.City, S.City),
  IFNULL(C.Country, S.Country)
FROM Customers C
LEFT JOIN Suppliers S
ON C.City = S.City AND C.Country = S.Country;

-- LEFT를 RIGHT로 바꿔서도 실행해 볼 것
-- IFNULL을 사용해서 NULL일 경우의 반환 될 값을 넣어 줄 수 있다.

 

CROSS JOIN - 교차조인

SELECT
  E1.LastName, E2.FirstName
FROM Employees E1
CROSS JOIN Employees E2
ORDER BY E1.EmployeeID;
-- 직원들의 각각 LastName에 이름을 추가한 데이터를 모두 조합한다. 직원은 모두 9명이 있는데
-- LastName + FirstName을 반복 조합하기 때문에 총 81개의 이름조합 데이터를 가져온다.

 

UNION - 집합으로 다루기

  • UNION = 중복을 제거한 집합
  • UNION ALL = 중복을 제거하지 않은 집합
SELECT CustomerName AS Name, City, Country, 'CUSTOMER'
FROM Customers
UNION
SELECT SupplierName AS Name, City, Country, 'SUPPLIER'
FROM Suppliers
ORDER BY Name;
-- 위에 쿼리와 아래 쿼리를 같은 별명으로 만들어 줘서 합친 후에 모두 불러오는 기능이다.

 

합집합

SELECT CategoryID AS ID FROM Categories
WHERE CategoryID > 4
UNION
SELECT EmployeeID AS ID FROM Employees
WHERE EmployeeID % 2 = 0;

-- UNION ALL로 바꿔볼 것

-- CategoryID는 1~8이고 EmployeeID는 1~9이다. UNION을 사용했기 때문에 5, 6, 7, 8
-- 그리고 EmployeeID의 2의 배수인 2, 4를 반환한다. 그리고 6, 8은 중복이기 때문에 생략됐다.

-- 만약 UNION ALL 을 사용하게 되면 합집합. 즉, 생략하지 않고 모두 반환하기 때문에
-- 값은 5, 6, 7, 8, 2, 4, 6, 8 을 반환하게 된다.

 

 

교집합

SELECT CategoryID AS ID
FROM Categories C, Employees E
WHERE 
  C.CategoryID > 4
  AND E.EmployeeID % 2 = 0
  AND C.CategoryID = E.EmployeeID;
-- FROM에서 Employees E를 선언해 준 것은 WHERE에서 써먹기 위함이고 조건으로
-- C.CategoryID = E.EmployeeID 를 추가해서 교집합을 가져온다.

 

차집합

SELECT CategoryID AS ID
FROM Categories
WHERE 
  CategoryID > 4
  AND CategoryID NOT IN (
    SELECT EmployeeID
    FROM Employees
    WHERE EmployeeID % 2 = 0
  );
-- 조건을 통해서 차집합을 만들어준다.

 

대칭 차집합

SELECT ID FROM (
  SELECT CategoryID AS ID FROM Categories
  WHERE CategoryID > 4
  UNION ALL
  SELECT EmployeeID AS ID FROM Employees
  WHERE EmployeeID % 2 = 0
) AS Temp 
GROUP BY ID HAVING COUNT(*) = 1;
-- 조건을 통해서 두 쿼리사이에 교집합이 아닌 모든 것들을 반환하게 만들었다.

 

마치며...

MySQL에 대한 기술포스팅은 여기까지 해야겠다... 무궁무진하게 많기 때문에 가장 자주 쓰일 것 같은 내용만 포스팅 했다. 실제로 MySQL내에서도 사용하면서 응용만 한다면 어떤 데이터든 가져 올 수 있겠다는 자신감 조금은 생긴 것 같다.

 

예시자료 출처 -  https://www.yalco.kr/lectures/sql/

2023. 1. 30. 21:34

MySQL의 SELECT기능 다뤄보기

이 게시글은 전반적으로  SELECT기능을 기술한 것이 대부분이다.

이 게시글에 모든 예시는 아래 링크를 통해서 사용해 볼 수 있다.스키마 이름이나 칼럼이름 역시 같아 결과값이 그대로 적용되어 보여진다.

https://www.w3schools.com/mysql/trymysql.asp?filename=trysql_select_all

 

MySQL Tryit Editor v1.0

WebSQL stores a Database locally, on the user's computer. Each user gets their own Database object. WebSQL is supported in Chrome, Safari, and Opera. If you use another browser you will still be able to use our Try SQL Editor, but a different version, usin

www.w3schools.com

 

데이터 Customers에서 모든내용 보기

SELECT * FROM Customers;
-- 이와 같이 주석을 달 수 있다.

 

원하는 column(열)만 골라서 보기

SELECT CustomerName FROM Customers;
--Customers스키마에서 CustomerName의 열만 골라서 보여준다.

SELECT CustomerName, ContactName, Country
FROM Customers;
--이렇게 다중으로 볼 수도 있다.

 

테이블의 column이 아닌 값 보기

SELECT
  CustomerName, 1, 'Hello', NULL
FROM Customers;
-- 1과 Hello와 NULL은 테이블의 column이 아니다.
-- 하지만 위와같이 입력하면 해당 내용과 같은 것들만 보여진다.

 

원하는 row(행)만 선택해서 보기

SELECT * FROM Orders
WHERE EmployeeID = 3;
-- 데이터 Orders에서 모든것 중에서 WHERE(조건문)을
-- 사용해서 EmployeeID가 3인 행만 보여지게 된다.

SELECT * FROM OrderDetails 
WHERE Quantity < 5;
-- 이런식으로 조건문을 꼭 등호가 아니여도 된다.
-- if문처럼 TRUE or FALSE로 반환되도록 잘 붙여주면 된다.

 

원하는 데이터 순서대로 가져오기

ORDER BY 구문을 사용하여 특정 column을 기준으로 잡고
-- 오름, 내림차순으로 볼 수 있다. Default는 오름차순이다.
-- ASC = 오름차순  /  DESC = 내림차순

SELECT * FROM Customers
ORDER BY ContactName;
-- 아무것도 지정해주지 않았기 때문에 ContactName을 기준으로
-- 오름차순으로 정렬된다.

SELECT * FROM OrderDetails
ORDER BY ProductID ASC, Quantity DESC;
-- 이런 경우 먼저 나온 column이 먼저 기준이 되어 정렬되고
-- 먼저 정렬된 것을 기준으로 그 다음 column이 정렬된다.

 

원하는 만큼만 데이터 가져오기

-- LIMIT {가져올 갯수} 또는 LIMIT {건너뛸 갯수}, {가져올 갯수} 를 사용하여,
-- 원하는 위치에서 원하는 만큼만 데이터를 가져올 수 있다.

SELECT * FROM Customers
LIMIT 10;
-- (첫줄부터 10까지)

SELECT * FROM Customers
LIMIT 0, 10;
-- (0~10까지)

SELECT * FROM Customers
LIMIT 30, 10;
-- (30번째줄부터 시작해서 10줄까지)

 

원하는 별명(alias)으로 데이터 가져오기

SELECT
  CustomerId AS ID,
  CustomerName AS NAME,
  Address AS ADDR
FROM Customers;

SELECT
  CustomerId AS '아이디',
  CustomerName AS '고객명',
  Address AS '주소'
FROM Customers;

 

 

사칙연산

 

1. 기본 사칙연산

 

  • +, -, *, /
    각각 더하기, 빼기, 곱하기, 나누기
  • %, MOD
    나머지
SELECT 1 + 2;
SELECT 5 - 2.5 AS DIFFERENCE;
SELECT 3 * (2 + 4) / 2, 'Hello';
SELECT 10 % 3;

 

  • 문자열에 사칙연산을 가하면 0으로 인식한다.
SELECT 'ABC' + 3;
SELECT 'ABC' * 3;
SELECT '1' + '002' * 3;
-- 숫자로 구성된 문자열은 숫자로 자동인식

 

  • 또 다른 예제
SELECT
  OrderID + ProductID
FROM OrderDetails;
-- OrderID의 숫자와 ProductID의숫자가 더해진다.

SELECT
  ProductName,
  Price / 2 AS HalfPrice
FROM Products;
-- ProductName과 Price를 나누기 2한 결과값의
-- 데이터이름을 HalfPrice로 보여지게 했다.

 

 

2. 참/거짓 관련 연산자

 

  • MySQL에서는 TRUE는 1, FALSE는 0으로 저장됩니다.
SELECT TRUE, FALSE;
-- true = 1  , false = 0

 

  • 또 다른 예제
SELECT !TRUE, NOT 1, !FALSE, NOT FALSE;
-- !true = 0, not 1 = 0, !false = 1, not false = 1
-- 트루의 반대는 펄스라서 0, not은 아니다의 뜻

SELECT 0 = TRUE, 1 = TRUE, 0 = FALSE, 1 = FALSE;
-- 0, 1, 1, 0
-- 1=true이고 0=false이기 때문이다.

SELECT * FROM Customers WHERE City = 'Berlin';
-- 전체 데이터에서 where조건문에 의해서 시티가 베를린인 것을  select한다.

 

  • IS = 양쪽이 모두 TRUE 또는 FALSE
    IS NOT = 한쪽은 TRUE, 한쪽은 FALSE
SELECT TRUE IS TRUE;
-- true는 true가 맞다.  return 1;

SELECT TRUE IS NOT FALSE;
-- true는 false가 아니다. return 1;

SELECT (TRUE IS FALSE) IS NOT TRUE;
-- true는 false이다. <= 이것은 true가 아니다. return 1;

 

  • AND, && = 양쪽이 모두 TRUE일 때만 TRUE
    OR, || = 한쪽은 TRUE면 TRUE
    Javascript와 동일하게 작동한다.
SELECT TRUE AND FALSE, TRUE OR FALSE;
-- Javascript ver.
-- true && false = false, true || false = true

SELECT 2 + 3 = 6 OR 2 * 3 = 6;
-- 5와 6이지만 or을 사용했기때문에 true를 반환.

SELECT * FROM Orders
WHERE
  CustomerId = 15 AND EmployeeId = 4;
-- and를 썼기 때문에 두 조건 모두 충족하는 데이터만 보임.
-- CustomerId = 15 OR EmployeeId = 4; 이면 두 조건중 하나만
-- 충족해도 보여지게 된다.

SELECT * FROM Products 
WHERE
  ProductName = 'Tofu' OR CategoryId = 8;
-- 두 조건중 하나라도 충족한 경우 모두 보여진다.

SELECT * FROM OrderDetails
WHERE
  ProductId = 20
  AND (OrderId = 10514 OR Quantity = 50);
-- ProductId = 20이면서 OrderId = 10514이거나
-- Quantity = 50인 데이터를 모두 보여준다.

 

  • = 양쪽 값이 같음
    !=, <> 양쪽 값이 다름
    >, < (왼쪽, 오른쪽) 값이 더 큼
    >=, <= (왼쪽, 오른쪽) 값이 같거나 더 큼
SELECT 1 = 1, !(1 <> 1), NOT (1 < 2), 1 > 0 IS NOT FALSE;
-- 1, 1, 0, 1

SELECT 'A' = 'A', 'A' != 'B', 'A' < 'B', 'A' > 'B';
-- 1, 1, 1, 0
-- 알파뱃은 나중에 나오는 알파뱃일수록 크다고 판정된다.

-- MySQL의 기본 사칙연산자는 대소문자 구분을 하지 않는다.
SELECT 'A' = 'a';
-- true이다. 구분하지 않기때문에

SELECT
  ProductName, Price,
  Price > 20 AS EXPENSIVE 
FROM Products;
-- ProductName과 Price데이터를 보여주고
-- Price가 20보다 높으면 EXPENSIVE라는 별명에
-- 1이 추가된다 (20보다 높으면 참이 되기 때문)

SELECT
  ProductName, Price,
  NOT Price > 20 AS CHEAP 
FROM Products;
-- ProductName과 Price데이터를 보여주고
-- Price가 20보다 크지 않은 것(not)은 CHEAP이라는 별명에
-- 1이 추가 된다.

 

  • BETWEEN {MIN} AND {MAX} = 두 값 사이에 있다.
    NOT BETWEEN {MIN} AND {MAX} =  두 값 사이가 아닌 곳에 있다.
SELECT 5 BETWEEN 1 AND 10;
-- 5는 1과 10 사이에 있다. = true

SELECT 'banana' NOT BETWEEN 'Apple' AND 'camera';
-- a와 c사이에 b가 있는건 맞지만 not이 붙어서 false를 반환한다.

SELECT * FROM OrderDetails
WHERE ProductID BETWEEN 1 AND 4;
-- OrderDetails에서 ProductID가 1~4인 데이터만 보여진다.

SELECT * FROM Customers
WHERE CustomerName BETWEEN 'b' AND 'c';
-- b와 c사이는 b로 시작하는 모든 문장을 말한다.
-- c를 넘기진 말아야 하니 c로시작하는 문장은 제외된다.

 

  • IN (...) 괄호 안의 값들 가운데 있다.
    NOT IN (...) 괄호 안의 값들 가운데 없다.
SELECT 1 + 2 IN (2, 3, 4) 
-- 1+2 = 3 괄호안에 3 있으니까 true를 반환한다.

SELECT 'Hello' IN (1, TRUE, 'hello') 
-- hello가 있으니 true를 반환. 대소문자를 구분안한다는 것을 기억하자.

SELECT * FROM Customers
WHERE City IN ('Torino', 'Paris', 'Portland', 'Madrid') 
-- Customers데이터에서 City내부에 
-- 'Torino', 'Paris', 'Portland', 'Madrid'가 있는 데이터만 보여진다.

 

  • LIKE '... % ...' 0~N개 문자를 가진 패턴
    LIKE '... _ ...' _ 갯수만큼의 문자를 가진 패턴
SELECT
  'HELLO' LIKE 'hel%',   = 1
-- hel이후 어떤 문자가 와도 true

  'HELLO' LIKE 'H%',   = 1
-- h로 시작하고 그 이후 어떤 문자가 있던 true

  'HELLO' LIKE 'H%O',   = 1
-- H로 시작해서 O로 끝나기만 하면 true

  'HELLO' LIKE '%O',   = 1
-- 앞에 몇글자가 있든 무슨 글자가 있든 O로 끝나면 true

  'HELLO' LIKE '%HELLO%',  = 1
-- HELLO라는 글자 앞과 뒤에 어떤 문자가 얼마나 있든
-- HELLO만 있다면 true, 0~N개라서 아무것도 없이 HELLO여도 true

  'HELLO' LIKE '%H',   = 0 
-- H로 끝나야 true인데 HELLO이기때문에 O로 끝나서 false를 반환한다.

  'HELLO' LIKE 'L%'   = 0
-- L로 시작해야 하는데 HELLO라서 false를 반환한다.

 

연산자  총정리!

SELECT
  'HELLO' LIKE 'HEL__',    = 1
  'HELLO' LIKE 'h___O',    = 1
  'HELLO' LIKE 'HE_LO',    = 1
  'HELLO' LIKE '_____',    = 1
  'HELLO' LIKE '_HELLO',    = 0
  'HELLO' LIKE 'HEL_',    = 0
  'HELLO' LIKE 'H_O'    = 0
-- 무슨 글자든 _ 의 갯수에 맞춰서 트루와 펄스를 반환한다.


/*

[ +, -, *, / ] === 각각 더하기, 빼기, 곱하기, 나누기

[ %, MOD ] === 나머지

[ IS ] === 양쪽이 모두 TRUE 또는 FALSE

[ IS NOT] === 한쪽은 TRUE, 한쪽은 FALSE

[ AND, && ] === 양쪽이 모두 TRUE일 때만 TRUE

[ OR, || ]=== 한쪽은 TRUE면 TRUE

[ = ] === 양쪽 값이 같음

[ !=, <> ] === 양쪽 값이 다름

[ >, < ] ===(왼쪽, 오른쪽) 값이 더 큼

[ >=, <= ] === (왼쪽, 오른쪽) 값이 같거나 더 큼

[ BETWEEN {MIN} AND {MAX} ] === 두 값 사이에 있음

[ NOT BETWEEN {MIN} AND {MAX} ] === 두 값 사이가 아닌 곳에 있음

[ IN (...)] === 괄호 안의 값들 가운데 있음

[ NOT IN (...) ] === 괄호 안의 값들 가운데 없음

[ LIKE '... % ...' ] === 0~N개 문자를 가진 패턴

[ LIKE '... _ ...' ] === _ 갯수만큼의 문자를 가진 패턴

*/

 

 

 

 ※  예시가 너무 방대하여 이해가 난해한 경우만 간략한 예시를 작성했다.  ※

Math

  • ROUND = 반올림
    CEIL = 올림
    FLOOR = 내림

  • ABS = 절대값
  • GREATEST() = 가장 큰 값
  • LEAST() = 가장 작은 값

  • MAX()  = 가장 큰 값
    MIN()  = 가장 작은 값
    COUNT()  = 갯수 (NULL값 제외)
    SUM()  = 총합
    AVG()  = 평균 값

  • TRUNCATE(N, n) = N을 소숫점 n자리까지 선택

 

문자열 케이스

  • UCASE, UPPER = 모두 대문자로
    LCASE, LOWER = 모두 소문자로
  • CONCAT(...) = 괄호 안의 인자(내용)를 이어붙임
    SELECT CONCAT('HELLO', ' ', 'THIS IS ', 2021)
    = (HELLO THIS IS 2021) 숫자도 문자열로 자동변환되어 더해진다.

    CONCAT_WS(S, ...) = 첫번째 인자로 이어붙임
    SELECT CONCAT_WS('-', 2021, 8, 15, 'AM')
    = (2021-8-15-AM)

  • SUBSTR(), SUBSTRING() = 주어진 값에 따라 문자열 자름
    SELECT
    SUBSTR('ABCDEFG', 3),
    = 왼쪽에서 3번째 문자부터 읽어온다. (CDEFG)

    LEFT() = 왼쪽부터 N글자
    RIGHT() = 오른쪽부터 N글자

 

그룹함수

 

1. 예시

SELECT Country FROM Customers
GROUP BY Country;
-- 중복되는 것들을 모두 하나로 묶어서 하나씩만 보여진다.


SELECT 
  Country, City,
  CONCAT_WS(', ', City, Country) as 'city, country'
FROM Customers
GROUP BY Country, City;
-- GROUP BY를 컨트리와 시티에 해주어서 Country의 값이 같은
-- 값이라도 City의 값이 다르면 다 보여준다.

 

2. 그룹함수 활용하기

SELECT
  COUNT(*), OrderDate
FROM Orders
GROUP BY OrderDate;
-- 그룹으로 묶인 값들의 갯수도 같이 볼 수 있다.

SELECT
  ProductID,
  SUM(Quantity) AS QuantitySum
FROM OrderDetails
GROUP BY ProductID
ORDER BY QuantitySum DESC;

SELECT
  CategoryID,
  MAX(Price) AS MaxPrice, 
  MIN(Price) AS MinPrice,
  TRUNCATE((MAX(Price) + MIN(Price)) / 2, 2) AS MedianPrice,
  TRUNCATE(AVG(Price), 2) AS AveragePrice
FROM Products
GROUP BY CategoryID;

SELECT 
  CONCAT_WS(', ', City, Country) AS Location,
  COUNT(CustomerID)
FROM Customers
GROUP BY Country, City;

Tryit Editor을 통해서 사용해보면 결과값을 확인할 수 있다.

 

  • WITH ROLLUP = 전체의 집계값 (ORDER BY와는 사용불가능)
SELECT
  Country, COUNT(*)
FROM Suppliers
GROUP BY Country
WITH ROLLUP;
-- COUNT의 합계가 가장 밑에 추가된다.

 

  • HAVING = 그룹화된 데이터 걸러내기
SELECT
  Country, COUNT(*) AS Count
FROM Suppliers
GROUP BY Country
HAVING Count >= 3;
-- Count로 집계된 데이터중 3과 같거나 큰 값만 보여준다.

 

  • WHERE는 그룹하기 전 데이터, HAVING은 그룹 후 집계에 사용한다.
SELECT
  COUNT(*) AS Count, OrderDate
FROM Orders
WHERE OrderDate > DATE('1996-12-31')
GROUP BY OrderDate
HAVING Count > 2;
-- WHERE로 1996-12-31보다 높은 수의 날짜들로 거른 후
-- OrderDate를 그룹화 시켜준 후 그룹화가 되었으니 HAVING으로 2보다 높은 수만 보여준다.

SELECT
  CategoryID,
  MAX(Price) AS MaxPrice, 
  MIN(Price) AS MinPrice,
  TRUNCATE((MAX(Price) + MIN(Price)) / 2, 2) AS MedianPrice,
  TRUNCATE(AVG(Price), 2) AS AveragePrice
FROM Products
WHERE CategoryID > 2
GROUP BY CategoryID
HAVING
  AveragePrice BETWEEN 20 AND 30
  AND MedianPrice < 40;
-- 이와같은 예시로 WHERE로 먼저 데이터를 걸러준 후 GROUP BY를
-- 사용해서 묶어준 후 HAVING으로 걸러낼 수 있다.

 

  • DISTINCT = 중복된 값들을 제거한다.
    GROUP BY 와 달리 집계함수가 사용되지 않는다.
    GROUP BY 와 달리 정렬하지 않으므로 더 빠르다.
SELECT DISTINCT CategoryID
FROM Products;
-- 위의 GROUP BY를 사용한 쿼리와 결과 비교
-- GROUP BY는 오름차순(Default)으로 정렬되어 보여주지만 DISTINCT는
-- 정렬하지 않은채로 데이터를 보여준다.

SELECT COUNT DISTINCT CategoryID
FROM Products;
-- 오류 발생
-- DISTINCT는 집계함수를 사용할 수 없어서 오류가 발생한다.

 

  • GROUP BY와 DISTINCT 함께 활용
SELECT
  Country,
  COUNT(DISTINCT CITY)
FROM Customers
GROUP BY Country;
-- 그룹으로 묶어줄때 집계함수 내부에 포함시켜 줄 수 있다.

 

 

마치며...

포스팅을 하다보니 너무 방대해서 포스팅을 나눠서 해야할 필요성을 느껴, 이만작성하고 다음 포스팅에서 상관 서브쿼리와 비상관 서브쿼리, JOIN 등에 대해서 포스팅을 마저 해야겠다.

 

예시자료 출처 -  https://www.yalco.kr/lectures/sql/

'Backend' 카테고리의 다른 글

[ TypeORM ] 서버와 SQL의 맵핑  (0) 2023.02.18
[ MySQL ] 상관,비상관 서브쿼리와 JOIN  (0) 2023.02.02
[ MySQL ] 데이터베이스  (4) 2023.01.29
[ Nest.js ] Node.js기반의 API 프레임워크  (0) 2023.01.24
Node.js란 무엇인가?  (0) 2022.11.27