블로그는 나의 힘!
[ Programing ]/Algorithm2023. 6. 21. 00:18

#ifndef _FINITE_STATE_H
#define _FINITE_STATE_H

#include <map>
#include <functional>

class CFiniteStateMachine;
class CFiniteState
{
     friend class CFiniteStateMachine;

private:
     CFiniteState(DWORD state) { m_StateID = state; }
     virtual ~CFiniteState()
     {
          std::map<DWORD, DWORD>::iterator iter, iterPrev;
          iter = TransitionList.begin();
          while (iter != TransitionList.end())
          {
               iterPrev = iter++;
               TransitionList.erase(iterPrev);
          }
     }

     DWORD GetStateID() const { return m_StateID; }
     void AddTransition(DWORD inputEvent, DWORD outputStateID) { TransitionList[inputEvent] = outputStateID; }
     void DeleteTransition(DWORD inputEvent) { TransitionList.erase(inputEvent); }
     DWORD GetCount() { return TransitionList.size(); }

     bool OutputState(DWORD inputEvent, DWORD& outputState)
     {
          std::map<DWORD, DWORD>::iterator iter;
          iter = TransitionList.find(inputEvent);
          if (iter == TransitionList.end())
          {
               //LogError("[CRITICAL] CFiniteState::OutputState(%d)", inputEvent);
               return false;
          }

          outputState = TransitionList[inputEvent];
          return true;
     }

private:
     DWORD m_StateID;
     std::map<DWORD, DWORD> TransitionList;
};

#endif     //!< FiniteState.h


///////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////

#ifndef _FINITE_STATE_MACHINE_H
#define _FINITE_STATE_MACHINE_H

#include "FiniteState.h"
#include <ctime>
#include <string>

class CFiniteStateMachine
{
public:
     CFiniteStateMachine() { m_CurrentState = 0; }
     virtual ~CFiniteStateMachine()
     {
          std::map<DWORD, CFiniteState*>::iterator iter, iterPrev;
          iter = m_StateList.begin();
          while (iter != m_StateList.end())
          {
               iterPrev = iter++;
               delete iterPrev->second;
               m_StateList.erase(iterPrev);
          }
     }

     void SetName(const char* name_) { m_name = name_; }
     LONGLONG GetCurrentStateTime() { return m_nCurrentStateTime; }

     bool GetOutputState(DWORD inputEvent, DWORD& outputState)
     {
          std::map<DWORD, CFiniteState*>::iterator iter;
          CFiniteState* state;
          iter = m_StateList.find(GetCurrentStateID());
          if (iter == m_StateList.end())
          {
               //logError("[CRITICAL] CFiniteStateMachine[%s]::GetOutputState(%d)", m_name.c_str(), inputEvent);
               return 0;
          }

          state = m_StateList[GetCurrentStateID()];
          return state->OutputState(inputEvent, outputState);
     }

     void AddStateTransition(DWORD stateID, DWORD inputEvent, DWORD outputStateID)
     {
          std::map<DWORD, CFiniteState*>::iterator iter;
          CFiniteState* state;
          iter = m_StateList.begin();
          while (iter != m_StateList.end())
          {
               state = iter->second;
               if (state->GetStateID() == stateID)
                    break;
               iter++;
          }

          if (iter == m_StateList.end())
          {
               state = new CFiniteState(stateID);
               m_StateList[state->GetStateID()] = state;
          }

          state->AddTransition(inputEvent, outputStateID);
     }

     void DeleteTransition(DWORD stateID, DWORD inputEvent);
     {
          std::map<DWORD, CFiniteState*>::iterator iter, iterPrev;
          CFiniteState* state;
          iter = m_StateList.begin();
          while (iter != m_StateList.end())
          {
               iterPrev = iter;
               state = iter->second;
               if (state->GetStateID() == stateID)
                    break;
               iter++;
          }

          if (iter == m_StateList.end())
               return;

          state->DeleteTransition(inputEvent);
          if (0 == state->GetCount())
          {
               delete state;
               m_StateList.erase(iterPrev);
          }
     }

     void SetCurrentState(DWORD stateID)
     {
          std::map<DWORD, CFiniteState*>::iterator iter;
          iter = m_StateList.find(stateID);
          if (iter == m_StateList.end())
          {
               //logError("[CRITICAL] CFiniteStateMachine[%s]::SetCurrentState(%d)", m_name.c_str(), stateID);
               return;
          }

          m_CurrentState = iter->second;
          m_nCurrentStateTime = GetTime();
     }

     DWORD GetCurrentStateID() const
     {
          if (0 == m_CurrentState)
          {
               //logError("[CRITICAL] CFiniteStateMachine[%s]::GetCurrentStateID()", m_name.c_str());
               return 0;
          }
          return m_CurrentState->GetStateID();
     }

     bool StateTransition(int event)
     {
          if (0 == m_CurrentState)
          {
               //logError("[CRITICAL] CFiniteStateMachine[%s]::StateTransition(%d)", m_name.c_str(), event);
               return false;
          }
          else
          {
               DWORD OutputState;
               if (true == m_CurrentState->OutputState(event, OutputState))
               {
                    //logDebug("FSM[%s] Event:%d State:%d", m_name.c_str(), event, m_CurrentState->GetStateID());
                    m_CurrentState = m_StateList[OutputState];
                    m_nCurrentStateTime = GetTime();
                    return true;
               }
               else
               {
                    //logError("FSM[%s] Event:%d State:%d", m_name.c_str(), event, m_CurrentState->GetStateID());
                    return false;
               }
          }
     }

private:
     std::map<DWORD, CFiniteState*m_StateList;
     std::string m_name;
     CFiniteState* m_CurrentState;
     LONGLONG m_nCurrentStateTime;
};

#endif     //!< FiniteStateMachine.h

///////////////////////////////////////////////////////////////////////////

#ifndef __BASE_PROCESS_H_
#define __BASE_PROCESS_H_

#include "FiniteStateMachine.h"

class BaseProcess
{
public:
     BaseProcess(const char* name_) : m_timeAdvance(0) { m_FSM.SetName(name_); }
     virtual ~BaseProcess() {}

     //!< FSM Pure virtual functions
     virtual void SetTransition(DWORD event) = 0;
     virtual void ProcessState(int timeAdvance) = 0;

protected:
     virtual void RegisterState() = 0;
     virtual void RegisterTransition() = 0;
     void SetTimeAdvance(int timeAdvance) { m_timeAdvance = timeAdvance; }
     int GetTimeAdvance() const { return m_timeAdvance; }
     bool CheckState(DWORD state) const { return (m_FSM.GetCurrentStateID() == state); }

protected:
     int m_timeAdvance;
     CFiniteStateMachine m_FSM;     //!< state transition
};

#endif     //!< BaseProcess.h

///////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////

#ifndef _CREATURE_BASE_PROCESS_H
#define _CREATURE_BASE_PROCESS_H

#include "BaseProcess.h"

template<class T>
class CObjBaseProcess : public BaseProcess
{
public:
     CObjBaseProcess(T& container, const char* name_) : BaseProcess(name_), m_container(container) {}
     virtual ~CObjBaseProcess() {}

     virtual void InitFSM() = 0;
     virtual bool InitData() = 0;

     //!< Get User
     T& GetContainer() { return m_container; }
     const T& GetContainer() const { return m_container; }

private:
     T& m_container;
};

#endif     //!< ObjBaseProcess.h


/**********************************************************/
/**********************************************************/

///////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////
// Sample

//!< CUser.h
class CUser // : public CCreature
{
public:
     CUser();
     virtual ~CUser() {}

     virtual void Run(LONGLONG nCurTime, int tickDelay);

     void InitFSMProcess();
     bool InitUser();
     void InitProcessManager();
     void Parse(char *pBuf, int nLen);
     bool Send(const char *pBuf, int nLen);

public:
     CUserDataProcess m_UserDataProcess;
     CUserCtrlProcess m_UserCtrlProcess;
     CUserLifeProcess m_UserLifeProcess;
     CUserItemProcess m_UserItemProcess;
     CUserBattleProcess m_UserBattleProcess;
     CUserSpellProcess m_UserSpellProcess;
     CUserMoveProcess m_UserMoveProcess;
     CUserProductProcess m_UserProductProcess;
     CUserProcessManager m_UserProcessMgr;
}

///////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////
//!< CUser.cpp
void CUser::InitFSMProcess()
{
     m_UserCtrlProcess.InitFSM();
     m_UserDataProcess.InitFSM();
     m_UserItemProcess.InitFSM();
     m_UserBattleProcess.InitFSM();
     m_UserSpellProcess.InitFSM();
     m_UserMoveProcess.InitFSM();
     m_UserLifeProcess.InitFSM();
     m_UserProductProcess.InitFSM();

     InitProcessManager();
}

bool CUser::InitUser()
{
     //CCreature::InitCreature();

     if (false == m_UserDataProcess.InitData())     return false;
     if (false == m_UserCtrlProcess.InitData())     return false;
     if (false == m_UserItemProcess.InitData())     return false;
     if (false == m_UserBattleProcess.InitData())     return false;
     if (false == m_UserSpellProcess.InitData())     return false;
     if (false == m_UserMoveProcess.InitData())     return false;
     if (false == m_UserLifeProcess.InitData())      return false;
     if (false == m_UserProductProcess.InitData())     return false;

     return true;
}

void CUser::Run(LONGLONG nCurTime, int tickDelay)
{
     int timeAdvance = TIMETICK_IN_MSEC + tickDelay;

     if (true == m_UserMoveProcess.IsInMap())
     {
          //CCreature::Run(nCurTime, tickDelay);

          m_UserCtrlProcess.ProcessState(timeAdvance);
          m_UserLifeProcess.ProcessState(timeAdvance);
          if (true == m_UserLifeProcess.IsAlive())
          {
               m_UserMoveProcess.ProcessState(timeAdvance);
               m_UserBattleProcess.ProcessState(timeAdvance);
          }
          m_UserItemProcess.ProcessState(timeAdvance);
     }
}

void CUser::Parse(char *pBuf, int nLen)
{
     if (static_cast<int>(3 * sizeof(int)) > nLen)
     {
          //logError("Parse Error: Invalid Packet Length: %d", nLen);
          return;
     }

     int uid, cid, nCommand, offset = 0;

     memcpy(&nCommand, pBuf+offset, sizeof(int));
     offset += sizeof(int);
     memcpy(&uid, pBuf+offset, sizeof(int));
     offset += sizeof(int);
     memcpy(&cid, pBuf+offset, sizeof(int));

     //!< replace nCommand position
     memcpy(pBuf+offset, &nCommand, sizeof(int));
     pBuf += offset;
     nLen -= offset;

     m_UserCtrlProcess.UpdateAliveTime();

     if (true == m_UserCtrlProcess.Parse(uid, nCommand, pBuf, nLen))     return;
     if (true == m_UserMoveProcess.Parse(nCommand, pBuf, nLen))     return;
     if (true == m_UserSpellProcess.Parse(nCommand, pBuf, nLen))     return;
     if (true == m_UserBattleProcess.Parse(nCommand, pBuf, nLen))     return;
     if (true == m_UserLifeProcess.Parse(nCommand, pBuf, nLen))     return;
     if (true == m_UserItemProcess.Parse(nCommand, pBuf, nLen))     return;
     if (true == m_UserProductProcess.Parse(nCommand, pBuf, nLen))     return;
}

///////////////////////////////////////////////////////////////////////////

///////////////////////////////////////////////////////////////////////////

enum eEVENT_USER

     EVENT_USER_ON = 1,
     EVENT_USER_OFF,

     //!< Move
     EVENT_USER_WARP,
     EVENT_USER_OUTMAP,
     EVENT_USER_INMAP,
     EVENT_USER_STOP,
     EVENT_USER_MOVE,
     EVENT_USER_GETON,
     EVENT_USER_GETDOWN,

     //!< Battle
     EVENT_USER_ATTACK,
     EVENT_USER_LOSTTARGET,

     //!< Life
     EVENT_USER_INVINCIBLE,
     EVENT_USER_VINCIBLE,
     EVENT_USER_RESURRECT,
     EVENT_USER_DIE,
     EVENT_USER_MEAL_START,
     EVENT_USER_MEAL_END,

     //!< Item (associated inven)
     EVENT_USER_EXCHANGE_START,
     EVENT_USER_EXCHANGE_END,
     EVENT_USER_SHOP_START,
     EVENT_USER_VENDOR_START,
     EVENT_USER_VENDOR_END,
     EVENT_USER_ADMINCTL_START,
     EVENT_USER_ADMINCTL_END,
     EVNET_USER_POST_START,
     EVENT_USER_POST_END,

     //!< Expertise (manufacture, extract)
     EVENT_USER_EXPERTISE_MAKING,
     EVENT_USER_EXPERTISE_EXTRACTING,
     EVENT_USER_EXPERTISE_COMPLETE,
};

///////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////

#ifndef _OBJECT_STATE_H
#define _OBJECT_STATE_H

//////////////////////////////////////////////////////////////////////
// CObjState
template<class T>
class CObjState
{
public:
     CObjState( T& parent ) : m_parent( parent ) {}
     virtual ~CObjState() {}
     virtual void Enter() = 0;
     virtual void Process() = 0;
     virtual void Exit() = 0;

     T& GetObj() { return m_parent; }

protected:
     T& m_parent;
};

#endif     //!< ObjState.h

///////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////

#ifndef _USER_STATE_PRODUCT_H
#define _USER_STATE_PRODUCT_H

#include "ObjState.h"

enum eSTATE_EXPERTISE 
{
     STATE_USER_EXPERTISE_NORMAL,
     STATE_USER_EXPERTISE_MAKING,
     STATE_USER_EXPERTISE_EXTRACTING,
     MAX_USER_STATE_EXPERTISE,
};

//!< USER Expertise States
class CUser;
class CUserExpertiseNormal : public CObjState<CUser>
{
public:
     CUserExpertiseNormal(CUser& parent) : CObjState<CUser> (parent) {}
     virtual ~CUserExpertiseNormal() {}
     virtual void Enter();
     virtual void Process();
     virtual void Exit();
};

class CUserExpertiseMaking : public CObjState<CUser>
{
public:
     CUserExpertiseMaking(CUser& parent) : CObjState<CUser> (parent) {}
     virtual ~CUserExpertiseMaking() {}
     virtual void Enter();
     virtual void Process();
     virtual void Exit();
};

class CUserExpertiseExtracting : public CObjState<CUser>
{
public:
     CUserExpertiseExtracting(CUser& parent) : CObjState<CUser> (parent) {}
     virtual ~CUserExpertiseExtracting() {}
     virtual void Enter();
     virtual void Process();
     virtual void Exit();
};

#endif     //!< UserStateProduct.h

///////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////

//////////////////////////////////////////////////////////////////////

// Process
#ifndef _USER_PRODUCT_PROCESS_H
#define _USER_PRODUCT_PROCESS_H

#include "FSM/ObjBaseProcess.h"
#include "UserStateProduct.h"

class CUser;
class CUserProductProcess : public CObjBaseProcess<CUser>
{
public:
     CUserProductProcess(CUser& user);
     virtual ~CUserProductProcess() {}

private:
     CUserProductProcess();
     CUserProductProcess( const CUserProductProcess& rhs);
     CUserProductProcess& operator = (const CUserProductProcess& rhs);

     void ParsePacketClientGameserverProductKit(char* pBuf, int nLen);

public:
     virtual void InitFSM();
     virtual bool InitData();
     virtual void SetTransition(DWORD event);
     virtual void ProcessState(int timeAdvance);
     virtual void RegisterState();
     virtual void RegisterTransition();

     DWORD GetState() const { return m_FSM.GetCurrentStateID(); }
     bool Parse(int nCommand, char* pBuf, int nLen);

     bool DropProductKit(const int nProductID);
     void SetMaking(int nSpellID);
     void EndState();

private:
     CObjState<CUser>* m_pState;     //!< current Expertise state
     CObjState<CUser>* m_pStateList[MAX_USER_STATE_EXPERTISE];     //!< Expertise state list
}

#endif     //!< UserProductProcess.h

///////////////////////////////////////////////////////////////////////////
//!< CUserProductProcess.cpp
#include "UserProductProcess.h"
#include "User.h"

CUserProductProcess::CUserProductProcess(CUser& user) 
                                                     : CObjBaseProcess<CUser>(user, "CUserProductProcess")

{
     RegisterState();
     RegisterTransition();
}

void CUserProductProcess::InitFSM()
{
     m_pState = m_pStateList[STATE_USER_EXPERTISE_NORMAL];
     m_FSM.SetCurrentState(STATE_USER_EXPERTISE_NORMAL);
}

bool CUserProductProcess::InitData()
{
     return true;
}

void CUserProductProcess::RegisterState()
{
     m_pStateList[STATE_USER_EXPERTISE_NORMAL]
                              = (CObjState<CUser>*) new
CUserExpertiseNormal(GetContainer());

     m_pStateList[STATE_USER_EXPERTISE_MAKING]
                              = (CObjState<CUser>*) new
CUserExpertiseMaking(GetContainer());

     m_pStateList[STATE_USER_EXPERTISE_EXTRACTING]
                              = (CObjState<CUser>*) new
CUserExpertiseExtracting(GetContainer());

}

void CUserProductProcess::RegisterTransition()
{
     m_FSM.AddStateTransition(STATE_USER_EXPERTISE_NORMAL,
                                   EVENT_USER_EXPERTISE_MAKING,
                                   STATE_USER_EXPERTISE_MAKING
);

     m_FSM.AddStateTransition(STATE_USER_EXPERTISE_NORMAL,
                                   EVENT_USER_EXPERTISE_EXTRACTING,
                                   STATE_USER_EXPERTISE_EXTRACTING
);


     m_FSM.AddStateTransition(STATE_USER_EXPERTISE_MAKING,
                                   EVENT_USER_EXPERTISE_COMPLETE,
                                   STATE_USER_EXPERTISE_NORMAL
);


     m_FSM.AddStateTransition(STATE_USER_EXPERTISE_EXTRACTING,
                                   EVENT_USER_EXPERTISE_COMPLETE,
                                   STATE_USER_EXPERTISE_NORMAL
);

}

void CUserProductProcess::SetTransition(DWORD event)
{
     if (true == m_FSM.StateTransition(event))
     {
          m_pState->Exit();
          m_pState = m_pStateList[m_FSM.GetCurrentStateID()];
          m_pState->Enter();
     }
}

void CUserProductProcess::ProcessState(int timeAdvance)
{
     if (nullptr != m_pState)
     {
          SetTimeAdvance(timeAdvance);
          m_pState->Process();
     }
}


bool CUserProductProcess::Parse(int nCommand, char* pBuf, int nLen)
{
     bool bCatched =false;
     switch (nCommand)
     {
     case _PACKET_CLIENT_GAMESERVER_PRODUCTKIT_ID:
          {
               ParsePacketClientGameserverProductKit(pBuf, nLen);
               bCatched = true;
          }break;
     }
     return bCatched;
}

void CUserProductProcess::ParsePacketClientGameserverProductKit(char* pBuf, int nLen)
{
     CPacket <_PACKET_CLIENT_GAMESERVER_PRODUCTKIT> RecvPacket;

     if (false == RecvPacket.ReadData(pBuf, nLen))
     {
          //logError("Packet[%d]: Failed at Packet Reading", RecvPacket.m_Data.nID);
          return;
     }
     DropProductKit(RecvPacket.m_Data.nProductID);
}

bool CUserProductProcess::DropProductKit(const int nProductID)
{
     DWORD curstate = GetContainer().m_UserProductProcess.GetState();     //!< check state
     if (STATE_USER_EXPERTISE_MAKING != curstate)
     {
          GetContainer().m_UserSpellProcess.RemoveSpell(nProductID);
          m_nProductIndex = GetContainer().m_UserDataProcess.ReSetProductKit(nProductID);
          if (true == IS_VALID_PRODUCTINDEX(m_nProductIndex))     //!< Success DropProductKit 
          {
               //SendDeleteProduct(m_nProductIndex, nProductID);
               return true;
          }
          else     //!< FAIL
          {
               //SendErrorProduct(RESULT_FAIL_PRODUCT_KIT, nProductID);
               return false;
          }
     }
     return false;
}

void CUserProductProcess::SetMaking(int nSpellID)
{
     SetTransition(EVENT_USER_EXPERTISE_MAKING);
     //GetContainer().m_UserItemProcess.SetTransition(EVENT_USER_EXPERTISE_MAKING);
     m_SpellID = nSpellID;
}

void CUserProductProcess::EndState()
{
     DWORD curstate = GetState();
     switch (curstate)
     {
     case STATE_USER_EXPERTISE_MAKING:
     case STATE_USER_EXPERTISE_EXTRACTING:
          {
               SetTransition(EVENT_USER_EXPERTISE_COMPLETE);
               //GetContainer().m_UserItemProcess.SetTransition(EVENT_USER_EXPERTISE_COMPLETE);
          }break;
     }     //!< !switch()
}

 

Posted by Mister_Q
[ Programing ]/Database2023. 5. 23. 10:47

# DESC : 테이블 구조 확인
> DESC [table]



# ALTER TABLE : 테이블 변경
> ALTER TABLE [table] ADD([column], BIGINT, NOT NULL);    -- 컬럼 추가
> ALTER TABLE [table] RENAME TO [newTableName];     -- 테이블 이름 변경
> ALTER TABLE [table] RENAME COLUMN [column] TO [newColumnName];     -- 컬럼 이름 변경



# DROP : 테이블 삭제
> DROP TABLE [table] CASADE CONSTRAINT;
> DROP TABLE [table];



# VIEW : 가상 테이블 (데이터 딕셔너리 테이블에 SQL문만 저장되 디스크 공간 할당 없음.)
> CREATE VIEW [viewTable] AS SELECT * FROM [table] WHERE [column] = [value];
> SELECT [column] FROM [viewTable] WHERE [column] >= [value];



# INSERT : 데이터 추가
> INERT INTO [table]( [column1], ... ) VALUES( [value], ... );



# UPDATE : 데이터 수정
> UPDATE [table] SET [column1] = [value1], ... WHERE [column] = [value];



# DELETE : 데이터 삭제 (빈번한 데이터 삭제는 자제하자. isDisable 컬럼 만들어 on/off 체크 추천.)
> DELETE FROM [table];
> DELETE FROM [table] WHERE [column] = [value];



# Alias (AS) : 별칭
> SELECT ta.[column] FROM [table] AS ta WHERE ta.[column] >= [value];



# BETWEEN [A] AND [B] : A와 B까지 조회. 반대로 한다면 NOT BETWEEN
> SELECT [column] FROM [table] WHERE BETWEEN [column1] AND [column2];
> SELECT [column] FROM [table] WHERE NOT BETWEEN [column1] AND [column2];




# IN : 조건에 있는 값 조회. 2가지 이상 조건 할려면 ( [A], [B] ) IN ( ( [조건1], [조건2] ), ... )
> SELECT [column] FROM [table] WHERE [column1] IN( [value1], ... );
> SELECT [column] FROM [table] WHERE [column1] NOT IN( [value1], ... );     -- NOT IN 조건 제외한 조회.
> SELECT [column] FROM [table] WHERE ( [column1], [column2] ) IN( ( [value1], [value2] ), ... );





참고 : https://blog.naver.com/jehun2001/222904580349

 

[코딩 노트] [SQL] DESC / ALTER / DROP / INSERT / ALIAS

코드아카데미 강좌는 끝났으나 부족한 게 많아 보충합니다. 참고하는 교재는 아래와 같습니다. https://sea...

blog.naver.com

 

Posted by Mister_Q
[ Programing ]/Database2023. 5. 23. 10:25

> SELECT [column] FROM [table] ORDER BY [column] DESC LIMIT 1;
> SELECT MAX( [column] ) FROM [table];

둘의 결과는 같으나 쿼리 최적화에 대한 고민은 해봤을듯 하다.
테스트에선 차이가 보이지 않으나 대용량에서 사용시 조회 차이가 눈에 보일 정도의 딜레이가 될수 있기에.

ORDER BY  자체는 상당히 부담이 큰 조건으로 왠만하면 사용을 자제한다.
조회할 테이블을 전체 정렬을 하는 상황이 되어 부담 스러운 일이 되지만,
LIMIT 1이 있어 풀 조회 전에 차단이 되 덜 부담 스러운 상황이 되는 꼼수라고 할까.

MAX()는 자체 지원하는 함수.
검증된 기능이라는 이야기.

이러면 뭘 쓸까 고민이 되는데 DESC 구조 조회를 해 보면.
> DESC SELECT MAX( [column] ) FROM [table]; 


Extra 에 Select tables optimized away 라고 최적화된 기능이라고 나온다.
MAX 값을 출력하는 것이라면 ORDER BY [column] DESC LIMIT 1 보다 MAX()를 활용하자.

 
 

Posted by Mister_Q