< 개발자 별 commit 순위 확인 >

git shortlog -sn | nl

- s 옵션 : 개발자별 commit 개수 요약

- n 옵션 : 개발자별 commit 개수 순위 정리 

 

nl 명령 : line number 명시 (순위 표시 용으로 사용) 

 

- 해당 부분은 gui 로 확인 가능 ( Github 해당 프로젝트 -> Insights -> Contributors) 

Q. 왜 GUI로 보지않고 CLI 로 보는가? 

A. 전체의 history보다 최근의 history,  특정 폴더에 집중적으로, 등등 확인하기 위해 

이렇게 세부적인 사항은 GUI로 확인할 수 없음 

즉, 세부적인 경향 파악을 위해 GUI가 아닌 CLI를 사용

 

(1) 특정 폴더에 집중적으로 순위 확인 

git shortlog -sn -- (디렉토리명)

(2) 특정 폴더 + 특정날짜 이후(최근) 순위 확인 

git shortlog -sn --after=2019-01-01 -- mnist

(3) merge commit 제거한 순위 확인

git shortlog -sn --no-merges

* commit : 소스 변경 히스토리 단위

* merge commit : "병합이 되었음을 나타내는 빈 commit" 

 

< 전체 소스파일 수정내역 개수 (commit 개수) 확인 >

git log --oneline | wc -l

wc -l 명령 : (파일) 라인 수 개수 측정

- GUI로도 확인 가능 -> "숫자"commits

- 팀 프로젝트의 commit 전체 개수 == base commit이라고 부름 

 

Q. base commit 은 계속 똑같은가? 

A. No. 당연히 변경됨. **우리는 base commit 위에 작업을 함**

 

(1) 전체 소스파일 수정내역 리스트 확인

git log --oneline

- 실행시 나오는 노란색글씨 : 소스파일 수정내역 고유한 ID (SHA1 해시값)

(2)전체 소스파일 수정내역(commit) 자세히 확인

git log -p

(3) 특정 날짜 기준 수정내역(commit) 확인 

git log --oneline --after=2020-01-01 --before=2020-06-30

(4) 특정 날짜 기준 수정내역(commit) 개수 확인

git log --oneline --after=2020-06-01 --before=2020-06-30 | wc

(5) 특정 날짜+파일/폴더 기준 수정내역(commit) 개수 확인

git log --oneline --after=2020-06-01 --before=2020-06-30 -- mnist/

- 협업 시 특정파일에 집중해서 개발하므로, 필요! 

(6) 최근이 아닌 옛날 것부터 소스파일 수정내역(commit) 확인

git log --oneline --reverse

* reverse 사용 시 주의 

- 첫번째꺼는 옛날꺼부터 3개

- 두번째 명령은 최신꺼 3개 중 거꾸로! 

 

< 특정 ID의 소스파일 수정 내역(commit) 내용 확인 >

git show (ID)

- commit 단위로 어떤 것을 수정했는지 확인할 수 있음

(1) 특정 ID의 소스파일 수정 파일 확인

git show 6c8e2ba | grep "diff --git"

(2) 특정 ID의 소스파일 수정 파일 개수 확인

git show 6c8e2ba | grep "diff --git" | wc -l

(3)전체 소스파일 수정내역(commit) 리스트에서, Merge commit 수정내역 확인

 

 

'Computer Science > git&github' 카테고리의 다른 글

Stash vs Checkout  (0) 2022.02.16
Branch 에서 소스파일 생성 및 수정  (0) 2022.02.16
기본 Git&Github 협업과정  (0) 2022.02.16
[git & github] 깃허브  (0) 2020.05.18
[git & github] 브랜치  (0) 2020.05.17

-Open UP에서 공개SW 실습교육 1일차-

<협업과정>

1. 참여할 프로젝트 Fork 복사

2. Fork한 프로젝트를 다운로드(Clone)

3. 프로젝트 분석(개발 경향 파악) : https://bskwak.tistory.com/243 참고 

4. config 설정 (저자(author) 정보) 

<5~7 branch 에서 작업 -> https://bskwak.tistory.com/244 참고>

5. commit 설정( 소스코드 수정분)

6. Fork한 프로젝트에 commit 업로드 ( == Push

7. 참여할 프로젝트에 commit 제출 ( == Pull-Request : PR

 

 

※ Commit 이란?

1. log message(수정한 이유) 

- ⭐️협업 시 중요⭐️

2. patch/ diff (코드 수정: 변화분) 

- ⭐️+(초록)/-(빨강) 코드를 볼 줄 알아야 함

 

※ Fork

- 공식 오픈소스 프로젝트의 내용들을 복사하는 것 (Github 안에서)

 

※ Clone

- history와 함께 소스코드를 다운받는 것 

- zip 다운과는 다름! (zip : commit history은 다운 X)

 

더보기

Q. 원본 프로젝트가 수정되면 fork한 프로젝트에 자동으로 반영이 되나?

A. No. 자동으로 반영이 되진 않음. 따로 해결을 해야함

 

< CLI(명령어 기반 인터페이스) vs GUI(그래픽 유저 인터페이스) >

※ CLI 

- 사진파일 1만개를 폴더 분류할 때 유리 (쉽게 말해, 드래그 하는 것보다 명령어를 사용하는 것이 편리) 

- 장점 : 세부기능 활용, 자동화(스크립트) 

※ GUI 

- 포토샵, 영상편집, 등에 유리

- 장점 : 쓰기 좋음 

- 단점 : 세부기능 활용, 자동화가 어려움

 

< 기본 명령어>

★ pwd

- print working directory

- 현재 디렉토리 출력

 

 ls

- 현재 디렉토리 내부내용(내부에 존재하는 파일들) 출력

 

 cd

- change directory

- 현재 폴더 경로 변경 

cd .. : 상위 디렉토리로 이동 

 

 mv

- 파일명/디렉토리명 변경 or 파일  경로 이동

 

 rm

- rm 파일명 :파일 삭제

- rm -r 디렉토리명 : 디렉토리 삭제

 

 touch

- 빈 파일 생성

 

 clear

- 터미널 화면 정리

 

"command not found" 에러는 오타!

 

** 협업 프로세스 ** 

1,2 과정

master 표시 : git으로 관리되는 폴더이다! 라는 의미 

 

"No such file or directory"  에러 

- 해당 파일/폴더가 없는 경우에 발생 

- 이미 그 폴더에 들어와 있는 경우에도 발생

1. pwd 를 통해 어느 폴더에 있는지 확인 -> 이미 그 폴더인지 확인가능

2. ls 를 통해 현재 폴더 안에 무엇이 있는지 확인 -> 이동할지, 등등 결정 

 

* config 설정

git config -l

- config 설치 확인

 

* upstream vs origin

upstream : 공식 오픈소스에 push

origin : fork한 프로젝트/본인 프로젝트에 push

 

nums = [-2,1,-3,4,-1,2,1,-5,4]

이 배열에서 가장 큰 합을 가진 부분 배열은 [4,-1,2,1], 최대합은 6이다

 

어떻게 구해야 할까?

 

# Brute Force

카데인 알고리즘이 생각나지 않는다면 Brute Force 접근법으로 풀어야한다!

Brute-Force란, 모든 경우의 수를 다 구해본 후에 max값을 찾는것

풀이를 간략하게라도 적어보자면,,

1. nums의 index 0부터 시작해서 nums[0]과 함께할수있는(?) 모든 부분합을 구한다.

2. nums[0]의 가능한 모든 부분합 중 가장 큰 값을 변수/리스트/..에 저장

3. 나머지 index 1부터 n-1까지 '1','2' 반복

4. 그러면 리스트던지, 변수던지, 각 인덱스에 해당하는 큰 값들이 모아짐

5. 그 중에서 가장 큰 값을 출력하면 됨

 

이 방식은 풀이만 봐도 정말 비효율적이다..

배열의 크기가 작으면 물론 금방 풀리겠지만, 커지면 커질수록 '1-2'번 반복횟수가 많아지므로, 시간 복잡도는 O(n^2)가 된다

 

따라서 나는 "카데인 알고리즘"으로 접근했다

#카데인 알고리즘

예시로 들어 설명하면

nums=[-2,-1,5,-3]

일단 가볍게 아래와 같은 아이디어?로 시작한다

 

 

1. (자기자신 vs 자기자신+이전값) 들 중 큰값들만 선정

따라서 -2일땐 -2, -1일땐 -1, 5일땐 5, -3일땐 2

 

2. 1번 중 가장 큰 값 -> 5

 

 

 

 

이때, 조금만 더 생각해보면

어짜피 같은 값을 더해주니까 굳이 모든 값을 비교해볼 필요가 없다..

 

따라서,

1.  max(자기자신 vs 자기자신+이전값 중 가장 큰 값)

따라서 -2일땐 -2, -1일땐 -1, 5일땐 5, -3일땐 2

 

2. 1번 중 가장 큰 값 -> 5

 

 

 

 

"이전 값"을 사용하는 것이 가장 큰 핵심 포인트다!

 

여기서 나는 1번에 배열을 생성하여 저장하지 않고,

max(자기자신 vs 자기자신+이전값 중 가장 큰 값) = localMax 값과 이전 max값을 비교하여 globalMax 변수에 큰 값을 저장해서 풀었다..

class Solution:
    def maxSubArray(self, nums: List[int]) -> int:
        localMax = nums[0]
        globalMax = nums[0]
        for i in range(1,len(nums)):
            localMax = max(nums[i],localMax+nums[i])
            globalMax = max(localMax,globalMax)
        return globalMax

배열 순회를 한번만 하므로, 카데인 알고리즘의 시간복잡도는 O(n)이다.

 

1. JAVA 풀이

class Solution {
    public void wiggleSort(int[] nums) {
        Arrays.sort(nums);
        int[] answer = new int[nums.length];
        int len = nums.length;
        
        if (nums.length %2 != 0)
            len = nums.length+1;
        
        for(int i=0;i< len/2; i++){
            answer[i*2] = nums[len/2-i-1];
        }
        
        for(int i=0; i< nums.length/2; i++){
            answer[i*2+1] = nums[nums.length-i-1];
        }

        for(int i=0;i<nums.length; i++){
            nums[i] = answer[i];
        }
    }
}

 

Java는 새로운 배열을 만들어 풀면 어렵지 않게 풀 수 있었다

nums를 정렬한 다음 짝수, 홀수 index에 역순으로 대입해주면 된다!

 

2. python 풀이

class Solution:
    def wiggleSort(self, nums: List[int]) -> None:
        """
        Do not return anything, modify nums in-place instead.
        """
  
        nums.sort()
        half = len(nums)//2 if len(nums)%2==0 else len(nums)//2+1
        nums[::2],nums[1::2] = nums[:half][::-1],nums[half:][::-1]

pop()을 사용하면서 풀고 싶었지만,, 아무리 생각해도 비효율적인 것 같아서 이것저것 시도해보다가,, 결국 다른사람 풀이와 동일한 풀이가.. 독창적인 풀이를 하고 싶었지만.. 실패

 

# nums = [3,2,6,4,5,1,0] 라 가정할때

 

1. 오름차순 정렬

# nums = [0,1,2,3,4,5,6]

 

2. half는 "nums길이의 반 값"인데, 홀수일 경우는 1을 더하도록 설정한다.

** "/"가 아닌 "//"를 써야함 -> "/"의 경우 홀수 일 경우, 소수점이 생기므로 다음 코드를 실행할 때 문제 발생

# len(nums)의 값은 7이므로 half의 값은 4가 된다

 

3. 짝수자리에 0~(half-1) index까지의 값을 역순으로 차례로 대입(변경), 홀수 자리에 half~끝 index까지의 값을 역순으로 차례로 대입(변경)

** 따로 코드를 분리해서 작성하면, 짝수 먼저 실행, 홀수 이후 실행으로 되므로 원하는 결과값이 나오지 않는다! 

    따라서 "동시 실행"이 가능하도록 코드 작성

 

참고로, 짝수의 경우를 예로 들면,,

nums[::2]= nums[:half][::-1]

참고로 [::2]는 2칸씩 띄어 읽는다는 의미!  따라서, nums[::2] 는 [0,2,4,6] 

nums[:half] 는 처음부터 (half-1)index까지의 값을 뜻하고

[::-1]은 그 값을 역순 처리한다는 뜻이다

 

그림으로 해당 풀이를 보면,,

 

1. 루시와 엘라 찾기

동물 보호소에 들어온 동물 중 이름이 Lucy, Ella, Pickle, Rogan, Sabrina, Mitty인 동물의 아이디와 이름, 성별 및 중성화 여부를 조회하는 SQL 문을 작성해주세요.이때 결과는 아이디 순으로 조회해주세요.

SELECT ANIMAL_ID, NAME, SEX_UPON_INTAKE
FROM ANIMAL_INS
WHERE NAME IN ('Lucy','Ella','Pickle','Rogan','Sabrina','Mitty')
ORDER BY ANIMAL_ID

2. 이름에 el이 들어가는 동물 찾기

보호소에 돌아가신 할머니가 기르던 개를 찾는 사람이 찾아왔습니다. 이 사람이 말하길 할머니가 기르던 개는 이름에 'el'이 들어간다고 합니다. 동물 보호소에 들어온 동물 이름 중, 이름에 "EL"이 들어가는 개의 아이디와 이름을 조회하는 SQL문을 작성해주세요. 이때 결과는 이름 순으로 조회해주세요. 단, 이름의 대소문자는 구분하지 않습니다.

SELECT ANIMAL_ID, NAME
FROM ANIMAL_INS
WHERE ANIMAL_TYPE = "Dog" AND NAME LIKE "%EL%"
ORDER BY NAME

"EL"이 들어가는 개의 아이디와 이름 조회

따라서, 조건문은 1. 개 2. 이름에 "EL"이 들어가야 함

1. ANIMAL_TYPE = "Dog"

2. NAME LIKE "%EL%"

 

* 특정 문자가 포함되어 있음 -> LIKE 와 '%' 사용

(1) LIKE "%EL" : EL로 끝나는 단어

(2) LIKE "EL%" : EL로 시작하는 단어

(3) LIKE "%EL%" : "EL"을 포함하는 단어 

1. 없어진 기록 찾기 - LEFT OUTER JOIN

천재지변으로 인해 일부 데이터가 유실되었습니다. 입양을 간 기록은 있는데, 보호소에 들어온 기록이 없는 동물의 ID와 이름을 ID 순으로 조회하는 SQL문을 작성해주세요.

SELECT O.ANIMAL_ID, O.NAME
FROM ANIMAL_OUTS O LEFT OUTER JOIN ANIMAL_INS I ON O.ANIMAL_ID = I.ANIMAL_ID
WHERE I.ANIMAL_ID IS NULL
ORDER BY O.ANIMAL_ID

 

2. 있었는데요 없었습니다 - LEFT OUTER JOIN

관리자의 실수로 일부 동물의 입양일이 잘못 입력되었습니다. 보호 시작일보다 입양일이 더 빠른 동물의 아이디와 이름을 조회하는 SQL문을 작성해주세요. 이때 결과는 보호 시작일이 빠른 순으로 조회해야합니다.

SELECT I.ANIMAL_ID, I.NAME
FROM ANIMAL_INS I LEFT OUTER JOIN ANIMAL_OUTS O ON I.ANIMAL_ID = O.ANIMAL_ID
WHERE I.DATETIME>O.DATETIME
ORDER BY I.DATETIME

 

3. 오랜시간 보호한 동물(1) - LEFT OUTER JOIN

아직 입양을 못 간 동물 중, 가장 오래 보호소에 있었던 동물 3마리의 이름과 보호 시작일을 조회하는 SQL문을 작성해주세요. 이때 결과는 보호 시작일 순으로 조회해야 합니다.

SELECT I.NAME, I.DATETIME
FROM ANIMAL_INS I LEFT OUTER JOIN ANIMAL_OUTS O ON I.ANIMAL_ID = O.ANIMAL_ID
WHERE O.ANIMAL_ID IS NULL
ORDER BY I.DATETIME LIMIT 3

 

4. 보호소에서 중성화한 동물 - LEFT OUTER JOIN

보호소에서 중성화 수술을 거친 동물 정보를 알아보려 합니다. 보호소에 들어올 당시에는 중성화되지 않았지만, 보호소를 나갈 당시에는 중성화된 동물의 아이디와 생물 종, 이름을 조회하는 아이디 순으로 조회하는 SQL 문을 작성해주세요.

SELECT O.ANIMAL_ID, O.ANIMAL_TYPE, O.NAME
FROM ANIMAL_OUTS O LEFT OUTER JOIN ANIMAL_INS I ON O.ANIMAL_ID = I.ANIMAL_ID
WHERE I.SEX_UPON_INTAKE != O.SEX_UPON_OUTCOME 
ORDER BY O.ANIMAL_ID

 

1. 이름이 없는 동물의 아이디 -> IS NULL

동물 보호소에 들어온 동물 중, 이름이 없는 채로 들어온 동물의 ID를 조회하는 SQL 문을 작성해주세요. 단, ID는 오름차순 정렬되어야 합니다.

SELECT ANIMAL_ID
FROM ANIMAL_INS
WHERE NAME IS NULL
ORDER BY ANIMAL_ID

조건 : 이름이 없는 채로 들어온 동물의 ID -> WHERE NAME IS NULL

" == NULL " 의 형태는 쓸 수 없다 

 

2. 이름이 있는 동물의 아이디 -> IS NOT NULL

동물 보호소에 들어온 동물 중, 이름이 있는 동물의 ID를 조회하는 SQL 문을 작성해주세요. 단, ID는 오름차순 정렬되어야 합니다.

SELECT ANIMAL_ID
FROM ANIMAL_INS
WHERE NAME IS NOT NULL
ORDER BY ANIMAL_ID

조건 : 이름이 없는 채로 들어온 동물의 ID -> WHERE NAME IS NOT NULL

 

3. NULL 처리하기 -> IFNULL

입양 게시판에 동물 정보를 게시하려 합니다. 동물의 생물 종, 이름, 성별 및 중성화 여부를 아이디 순으로 조회하는 SQL문을 작성해주세요. 이때 프로그래밍을 모르는 사람들은 NULL이라는 기호를 모르기 때문에, 이름이 없는 동물의 이름은 "No name"으로 표시해 주세요.

SELECT ANIMAL_TYPE, IFNULL(NAME,"No name") AS NAME, SEX_UPON_INTAKE
FROM ANIMAL_INS
ORDER BY ANIMAL_ID

ANIMAL_TYPE, NAME, SEX_UPON_INTAKE 조회

여기서 NAME은 NULL일 때, "No name" , NULL이 아닐 때 NAME으로 분류된다.

이때 사용하는 함수는 " IFNULL"

 

cf) IFNULL vs NULLIF 

  • IFNULL(A, B) : A가 NULL이면 B리턴, A가 NULL이 아니면 A를 리턴
  • NULLIF(A,B) : A==B이면 NULL 리턴, A!=B이면 A 리턴

 

 ** ORACLE의 경우, 더 많은 함수를 쓸 수 있다!

 

NVL, NVL2, DECODE

  • NVL(A,B) : A가 NULL이면 B 리턴, A가 NULL이 아니면 A를 리턴 (MySQL의 IFNULL과 동일)
  • NVL2(A,NULL이 아닐경우 값,NULL일 경우의 값) : A,  A가 NULL이 아닐 경우와 A가 NULL일 경우
  • DECODE(A,IF_1, THEN_1, IF_2, THEN2, ...,default값) : 우리가아는 case문이랑 비슷, A가 IF_1이면 THEN_1, IF_2면 THEN_2 ... 없으면 default값 리턴
--NVL 사용
SELECT ANIMAL_TYPE, NVL(NAME,'No name') AS NAME, SEX_UPON_INTAKE
FROM ANIMAL_INS
ORDER BY ANIMAL_ID;

--NVL2 사용
SELECT ANIMAL_TYPE, NVL2(NAME,NAME,'No name') AS NAME, SEX_UPON_INTAKE
FROM ANIMAL_INS
ORDER BY ANIMAL_ID;

--DECODE 사용
SELECT ANIMAL_TYPE, DECODE(NAME, NULL,'No name',NAME) AS NAME, SEX_UPON_INTAKE
FROM ANIMAL_INS
ORDER BY ANIMAL_ID;

 

4. 입양 시각 구하기 (2)

보호소에서는 몇 시에 입양이 가장 활발하게 일어나는지 알아보려 합니다. 0시부터 23시까지, 각 시간대별로 입양이 몇 건이나 발생했는지 조회하는 SQL문을 작성해주세요. 이때 결과는 시간대 순으로 정렬해야 합니다.

 

첨엔 GROUP BY로 해결하려 했으나 COUNT가 0인 부분에 대해서 조회할 수 없어서 실패 

두번째 시도는 UNION ALL 이었는데,,,, (sol 2) 같이 일일히 다 써야하기 때문에 패스! 

(sol 1) SET, @변수 사용 

SET @HOUR := -1;
SELECT (@HOUR := @HOUR+1) AS HOUR, (SELECT COUNT(*) 
                                    FROM ANIMAL_OUTS
                                    WHERE HOUR(DATETIME) = @HOUR) AS COUNT
FROM ANIMAL_OUTS
WHERE @HOUR < 23

 

1. "SET" 사용 : 변수 선언 (어떤 변수에 특정 값을 할당 하는 것을 말함)

- 변수 앞에 @ 지정 : 프로시저가 종료되어도 유지됨 , 따라서 값 누적이 가능

 - " = ", " := "에 따라 의미가 달라진다 

  • " = " : 대입연산자, 비교연산자 
  • " := " : 대입연산자로만 사용,
  • = 는 대입연산자로도 쓸 수 있지만, 비교연산자와의 혼동을 막기위해 := 주로 사용

따라서, HOUR라는 변수를 프로시저가 종료되어도 유지되도록 설정하고, -1로 초기화 -> SET @HOUR := -1; 

 

※ 왜 -1 인가? 

SELECT 문에서 (@HOUR := @HOUR +1) AS HOUR 로 지정하는데, 이 식 때문에 -1+1 = 0 부터 시작하게 된다! 

 

2.  HOUR가 0~23까지 1씩 증가해야하므로 (@HOUR := @HOUR +1) 로 지정해주고 alias 사용하여 HOUR 칼럼명 지정

COUNT의 경우는, HOUR(DATETIME)과 @HOUR변수의 값이 동일할 때의 개수를 세어주면 되므로 아래와 같다

(SELECT COUNT(*) FROM ANIMAL_OUTS WHERE HOUR(DATETIME) = @HOUR) AS COUNT

 

3. FROM ANIMAL_OUTS(테이블 선정)

 

4. WHERE @HOUR<23 : @HOUR가 23이 될때까지

(sol 2) UNION ALL 사용

SELECT HOUR, COUNT
FROM(
    SELECT 0 AS HOUR, COUNT(*) AS COUNT FROM ANIMAL_OUTS WHERE HOUR(DATETIME) = 0
    UNION ALL
    SELECT 1 AS HOUR, COUNT(*) AS COUNT FROM ANIMAL_OUTS WHERE HOUR(DATETIME) = 1
    UNION ALL
    SELECT 2 AS HOUR, COUNT(*) AS COUNT FROM ANIMAL_OUTS WHERE HOUR(DATETIME) = 2
    UNION ALL
    SELECT 3 AS HOUR, COUNT(*) AS COUNT FROM ANIMAL_OUTS WHERE HOUR(DATETIME) = 3
    UNION ALL
    SELECT 4 AS HOUR, COUNT(*) AS COUNT FROM ANIMAL_OUTS WHERE HOUR(DATETIME) = 4
    UNION ALL
    SELECT 5 AS HOUR, COUNT(*) AS COUNT FROM ANIMAL_OUTS WHERE HOUR(DATETIME) = 5
    UNION ALL
    SELECT 6 AS HOUR, COUNT(*) AS COUNT FROM ANIMAL_OUTS WHERE HOUR(DATETIME) = 6
    UNION ALL
    SELECT 7 AS HOUR, COUNT(*) AS COUNT FROM ANIMAL_OUTS WHERE HOUR(DATETIME) = 7
    UNION ALL
    SELECT 8 AS HOUR, COUNT(*) AS COUNT FROM ANIMAL_OUTS WHERE HOUR(DATETIME) = 8
    UNION ALL
    SELECT 9 AS HOUR, COUNT(*) AS COUNT FROM ANIMAL_OUTS WHERE HOUR(DATETIME) = 9
    UNION ALL
    SELECT 10 AS HOUR, COUNT(*) AS COUNT FROM ANIMAL_OUTS WHERE HOUR(DATETIME) = 10
    UNION ALL
    SELECT 11 AS HOUR, COUNT(*) AS COUNT FROM ANIMAL_OUTS WHERE HOUR(DATETIME) = 11
    UNION ALL
    SELECT 12 AS HOUR, COUNT(*) AS COUNT FROM ANIMAL_OUTS WHERE HOUR(DATETIME) = 12
    UNION ALL
    SELECT 13 AS HOUR, COUNT(*) AS COUNT FROM ANIMAL_OUTS WHERE HOUR(DATETIME) = 13
    UNION ALL
    SELECT 14 AS HOUR, COUNT(*) AS COUNT FROM ANIMAL_OUTS WHERE HOUR(DATETIME) = 14
    UNION ALL
    SELECT 15 AS HOUR, COUNT(*) AS COUNT FROM ANIMAL_OUTS WHERE HOUR(DATETIME) = 15
    UNION ALL
    SELECT 16 AS HOUR, COUNT(*) AS COUNT FROM ANIMAL_OUTS WHERE HOUR(DATETIME) = 16
    UNION ALL
    SELECT 17 AS HOUR, COUNT(*) AS COUNT FROM ANIMAL_OUTS WHERE HOUR(DATETIME) = 17
    UNION ALL
    SELECT 18 AS HOUR, COUNT(*) AS COUNT FROM ANIMAL_OUTS WHERE HOUR(DATETIME) = 18
    UNION ALL
    SELECT 19 AS HOUR, COUNT(*) AS COUNT FROM ANIMAL_OUTS WHERE HOUR(DATETIME) = 19
    UNION ALL
    SELECT 20 AS HOUR, COUNT(*) AS COUNT FROM ANIMAL_OUTS WHERE HOUR(DATETIME) = 20
    UNION ALL
    SELECT 21 AS HOUR, COUNT(*) AS COUNT FROM ANIMAL_OUTS WHERE HOUR(DATETIME) = 21
    UNION ALL
    SELECT 22 AS HOUR, COUNT(*) AS COUNT FROM ANIMAL_OUTS WHERE HOUR(DATETIME) = 22
    UNION ALL
    SELECT 23 AS HOUR, COUNT(*) AS COUNT FROM ANIMAL_OUTS WHERE HOUR(DATETIME) = 23
    UNION ALL
    SELECT 24 AS HOUR, COUNT(*) AS COUNT FROM ANIMAL_OUTS WHERE HOUR(DATETIME) = 24
    ) AS t
WHERE HOUR >= 0 AND HOUR <= 23
ORDER BY HOUR

 

참고로, UNION ALL 과 UNION의 차이

UNION ALL은 중복을 제거하지 않고 모두! (합집합+교집합)

UNION은 중복을 제거한상태로 합치는 것! (합집합)

+ Recent posts