MySQL/Hierarchical Query2019. 1. 5. 08:58

WITH RECURSIVE, UNION을 사용하여 계층구조 질의를 작성하기 위한 기초 정리

원문참조 : https://dev.mysql.com/doc/refman/8.0/en/with.html


MySQL의 WITH 절은 WITH절에 포함된 서브커리의 결과집합을 일단 이름을 붙여서 메모리에 저장하고 WITH 절 외부의 메인 커리에서 그 이름을 이용하여 마치 테이블처럼 사용할 수 있다. 그런 점에서 가상의 테이블이라고 설명할 수도 있다


# UNION 결과 : 1,2 출력

SELECT 1 AS num

UNION

SELECT 2 AS num;


# WITH, UNION 결과 : 1,2 출력

WITH test AS

(

SELECT 1 AS num

UNION ALL

SELECT 2 AS num

)

SELECT * FROM test;


# WITH, UNION 결과 : 1,2,2,2,... (emp테이블의 행수만큼 2가 반복됨)

WITH test AS

(

SELECT 1 AS num

UNION ALL

SELECT 2 AS num FROM emp # 이 문장은 emp 테이블의 행수만큼 반복된다

)

SELECT * FROM test;


# 서브커리에서 바깥 WITH 테이블 참조. 결과: RECURSIVE 가 없으므로 오류발생

WITH test AS

(

    SELECT 1 AS num

    UNION

    SELECT num+1 AS num FROM test WHERE num<10

)

SELECT * FROM test;


# 내부에 UNION 이 없으므로 오류

# Recursive Common Table Expression은 UNION을 포함해야 함

WITH RECURSIVE test AS

(

SELECT num+1 AS num FROM test WHERE num<10

)

SELECT * FROM test;


# RECURSIVE 문장에 정지조건이 없으므로 오류

WITH RECURSIVE test AS

(

    SELECT 1 AS num

    UNION

    SELECT num+1 AS num FROM test

)

SELECT * FROM test;


# 오류가 없는 정상적인 문장

# Recursive 내부에 UNION을 사용하고 정지조건도 사용한 경우

WITH RECURSIVE test AS

(

    SELECT 1 AS num

    UNION

    SELECT num+1 AS num FROM test WHERE num<10

)

SELECT * FROM test;


# Recursive 내부에는 한개 이상의 Non-Recursive문장이 포함되어야 하므로 아래의 문장은 오류

# Non-Recursive 문장이란 내부에서 바깥 WITH RECURSIVE 테이블을 참조하지 않는 문장을 의미함

WITH RECURSIVE test AS

(

    SELECT 1 AS num FROM test WHERE num<10

    UNION

    SELECT num+1 AS num FROM test WHERE num<10

)

SELECT * FROM test;


WITH RECURSIVE test AS

(    # Non-Recursive 문장( 첫번째 루프에서만 실행됨 )

    SELECT 1 AS num FROM emp # 첫 루프에서 emp 테이블의 행 수만큼 자체 반복됨

    UNION ALL

    # Recursive 문장(읽어 올 때마다 행의 위치가 기억되어 다음번 읽어 올 때 다음 행으로 이동함)

    SELECT num+1 AS num FROM test WHERE num<10

)

SELECT * FROM test;



다수개의 결과집합에 대한 이름을 붙일 때는 다음과 같은 형식을 사용한다

WITH name1 AS

(

   SELECT ~ (Sub Query)

),

name2 AS

(

   SELECT ~ (Sub Query)

)

SELECT ~ (Main Query)



WITH RECURSIVE 관련 정리

  • 메모리 상에 가상의 테이블을 저장
  • 반드시 UNION 사용
  • 반드시 비반복문도 최소한 1개 요구됨
  • 서브커리에서 바깥의 가상의 테이블을 참조하는 문장(반복문)이 반드시 필요함
  • 반복되는 문장은 반드시 정지조건이 요구됨
  • 가상의 테이블을 구성하면서 그 자신(가상의 테이블)을 참조하여 값을 결정할 때 유용함



상세한 해석 연습

아래의 문장에서 사용된 emp 테이블에는 총 10개의 행이 저장되어 있다고 가정한다

아래의 문장을 MySQL8에서 실행하면 1~10 사이의 숫자가 순서대로 10개씩 총 100개 출력된다

왜 그러한 결과가 나오는지 논리적으로 설명하고 그림으로 그릴 수 있는 정도까지 연습해보기 바란다


WITH RECURSIVE test AS

(    # Non-Recursive 문장( 첫번째 루프에서만 실행됨 )

    SELECT 1 AS num FROM emp # 첫 루프에서 emp 테이블의 행 수만큼 자체 반복됨

    UNION ALL

    # Recursive 문장(읽어 올 때마다 행의 위치가 기억되어 다음번 읽어 올 때 다음 행으로 이동함)

    SELECT num+1 AS num FROM test WHERE num<10

)

SELECT * FROM test;



아래의 계층구조 질의에서 사용할 함수

REPEAT(str, count)

 - str 문자열을 count 수만큼 반복하여 결합한다

 - SELECT REPEAT(' * ', 10);


CONCAT(exp1, exp2, ......)

 - 2개 이상의 파라미터를 문자열로 결합한다

 - 첫번째 파라미터는 필수이다


SELECT CONCAT ( REPEAT( ' ', 10 ), title ) AS title FROM bbs;



CREATE TABLE emp

(

    id INT AUTO_INCREMENT PRIMARY KEY, 

    ename VARCHAR(255), 

    parent_id INT DEFAULT 0 REFERENCES id

);


INSERT INTO emp VALUES

(1, 'name1', 0),

(2, 'name2', 1),

(3, 'name3', 1),

(4, 'name4', 3),

(5, 'name5', 0),

(6, 'name6', 3),

(7, 'name7', 4),

(8, 'name8', 5),

(9, 'name9', 8),

(10, 'name10',8);


WITH RECURSIVE tmp1 AS

(

    SELECT id, ename, parent_id, 

    ename AS path, 1 AS lvl

    FROM emp WHERE parent_id=0

    UNION ALL

    SELECT e.id, e.ename, e.parent_id, 

    CONCAT(t.path,',',e.ename) AS path, t.lvl+1 AS lvl 

    FROM tmp1 t JOIN emp e ON t.id=e.parent_id

)

SELECT id, CONCAT(REPEAT(' ', lvl*4), ename) ename, parent_id, path, lvl 

FROM tmp1 

ORDER BY path# path 임시컬럼을 사용하여 정렬하면 계층구조로 정렬된다



Workbench에서 위의 문장을 실행한 결과



Posted by cwisky
Networking/Insert Game Data2018. 12. 26. 22:21

VaRest 플러그인을 이용하여 게임 데이터를 웹서버로 전달하고 MySQL 에 저장하는 예


무료 PHP 웹호스팅 준비

awardspace.com 사이트에 회원가입하고 무로 PHP 계정을 얻는다

awardspace.com 사이트의 Hosting Tools > Database Manager / MySQL Database를 클릭하여 데이터베이스를 생성한다

Hosting Tools > Database Manager / phpMyAdmin4를 선택하여 테이블(game_history)을 생성한다

game_history 테이블에 컬럼 5개를 생성하고 아래처럼 각 컬럼의 자료형을 지정한다



위에서 생성한 game_history 테이블의 game_start_date, game_end_date 컬럼의 자료형인 datetime은 날짜와 시간을 동시에 저장할 수 있으며 그 데이터 포맷은 '2018-12-03 12:31:45' 와 같이 년-월-일 시:분:초 를 표현한다


위에서 생성한 game_history 테이블에 게임 데이터를 저장하는 기능을 할 PHP를 작성한다


insert_game_data.php

<?php

  $game_title = $_REQUEST['game_title'];

  $player_id =  $_REQUEST['player_id'];

  $score =       $_REQUEST['score'];

  $start_time = $_REQUEST['game_start_date'];

  $end_time =  $_REQUEST['game_end_date'];

  

  $host='fdb24.awardspace.net';

  $user='2915954_user';

  $pass='2915954Database';

  $db='2915954_user';


  $arr = array();

  

  $conn = mysqli_connect($host,$user,$pass,$db);

  if ($conn->connect_error) {

    $arr["connection"] = false;

    return;

  }

  if($conn) {

    $arr["connection"] = true;

  }

 

  $sql = "INSERT INTO game_history (game_title, player_id, score, game_start_date, game_end_date) ".

          "VALUES ('$game_title', '$player_id', $score, '$start_time','$end_time')";


  $inserted = mysqli_query($conn, $sql);


  if ($inserted) {

    $arr['inserted'] = true;

  } else {

    $arr['inserted'] = false;

  }

  mysqli_close($conn);

  $json = json_encode($arr);

  echo $json;

?>


언리얼 런처를 실행하여 마켓플레이스를 클릭하고 VaRest 플러그인을 찾아 언리얼 엔진에 설치한다

언리얼 엔진의 기본 프로젝트 템플릿을 이용하여 프로젝트를 생성한다


언리얼 에디터의 블루프린트 버튼을 누르고 레벨블루프린트를 열고 다음과 같이 시간 문자열을 생성하여 리턴하는 함수를 생성한다


GetDateTime함수(MySQL의 datetime 형 데이터를 생성하는 기능)



레벨블루프린트에 다음과 같이 VaRest 플러그인을 이용하여 서버측 스크립트(insert_game_data.php)에 데이터를 전달하는 기능을 작성한다(웹서버에 요청을 전달하는 기능)

Posted by cwisky
Networking/VaRest Plugin2018. 12. 22. 17:59

http 요청 및 응답처리를 위한 VaRest 플러그인 사용법


언리얼 엔진 4.11 ~ 현재 최신버전(4.21)까지 지원하는 플러그인


설치 및 테스트


언리얼 마켓플레이스에서 VaRest 를 검색하고 엔진에 설치하기를 누른다

언리얼 엔진을 이용하여 프로젝트를 생성한다

편집 > 플러그인 > VaRest 활성화 체크 > 화면 우측하단 [지금 재시작] 버튼 누름

언리얼 에디터가 다시 실행되면 블루프린트 입력 가능한 곳에 아래의 내용을 입력한다



VaRest 플러그인 작동 테스트 (https://alyamkin.com/ 사이트에 대한 GET 방식 요청)



게임을 실행하고 키보드에서 T 키를 누르면 아래와 같이 게임화면에 http 응답문자열이 표시된다




PHP 무료 호스팅 서비스 이용하기

언리얼 프로젝트에서 접속할 웹서버가 필요할 때 PHP 무료계정을 이용하면 빠르고 쉽게 웹서버를 구축할 수 있다


무료 PHP 계정으로 유명한 infinityfree.net 은 다른 곳에 비해서 더 좋은 프로그래밍 환경을 제공하고 있지만 언리얼 엔진에서 접속하면 자바스크립트가 지원되지 않는 클라이언트라는 오류 메시지가 표시되면서 PHP가 실행되지 않는 문제가 있어서 아쉽지만 사용할 수가 없다


아래 예제에서 사용한 웹사이트 무료계정

awardspace.com


PHP에서 단순한 문자열을 응답하는 경우

<?php

  echo "Hello";

?>



여러개의 문자열을 쉼표로 구분하여 한개의 문자열로 응답하는 경우

<?php

  echo "Hello, World, Bye";

?>

위와 같은 경우에는 블루프린트에서 수신된 응답 문자열을 Parse into Array 노드를 이용하여 배열을 생성하고 ForEachLoop노드를 이용하여 모든 문자열을 화면에 표시할 수 있다



PHP 에서 간단한 JSON 문자열을 응답으로 출력하는 경우

<?php

  header('Content-type: application/json; charset:utf-8');

  $arr = array();

  $arr["login"] = true;

  $arr["error"] = false;

  $json = json_encode($arr);

  echo urldecode($json);

?>




POST 방식 요청으로 웹서버에 파라미터 전송하기


블루프린트에서 POST 요청을 전달하기 위해서는 위의 블루프린트에서도 생성한 적이 있는 RequestObject 객체의 Set String Field 노드를 이용하여 파라미터 이름, 값을 웹서버로 전송할 수 있다.



웹서버측에서 파라미터를 수신하는 예

<?php

  $ParamValue = $_REQUEST["Param Name"];

  echo ParamValue;

?>



PHP에서 JSON 문자열을 생성하여 클라이언트에게 응답하는 예

<?php

  header('Content-type: application/json; charset=UTF-8');

  $arr = array();

  $arr["login"] = true;

  $arr["error"] = false;

  $jsonStr = json_encode($arr);

  echo $jsonStr;

?>



PHP에서 MySQL 데이터베이스에 접속하는 예 ( http://unrealengine.epizy.com/mysql_test.php )


<?php


  $host='sql113.epizy.com';

  $user='epiz_23187906';

  $pass='HQpyNNHkYp';

  $db='epiz_23187906_User';


  $conn = mysqli_connect($host,$user,$pass,$db);

  if ($conn->connect_error) {

    die("Connection failed: " . $conn->connect_error);

  }

  if($conn) {

    echo "Connection successful"; 

    echo "<br>";

  }

 

  $select_query = "SELECT * FROM Player ";


  $result_set = mysqli_query($conn, $select_query);


  if (mysqli_num_rows($result_set) > 0) {

    while($row = mysqli_fetch_assoc($result_set)) {

        echo "Player ID: " . $row["PlayerID"]. " - Email: " . $row["Email"]. " - Game: " . $row["Game"]. "<br>";

    }

  } else {

    echo "0 results";

  }

  mysqli_close($conn);

?>



로그인 결과를 JSON 문자열로 응답하는 PHP(http://unreal.mywebcommunity.org/unreal_login.php?id=smith&pwd=smithpwd)

<?php

header('Content-type: application/json; charset=UTF-8');

  $id = $_REQUEST["id"];

  $pwd = $_REQUEST["pwd"];


  $result = "";

  

  // DB 코드 시작

  $host='fdb24.awardspace.net';

  $user='2915954_user';

  $pass='2915954Database';

  $db='2915954_user';


  $arr = array();

  

  $conn = mysqli_connect($host,$user,$pass,$db);

  if ($conn->connect_error) {

$arr["error"] = true;

        echo json_encode($arr);

return;

  }

  if($conn) {

    $arr["connection"] = true;

  }

 

  $select_query = "SELECT * FROM Member WHERE memid='$id' AND mempwd='$pwd' ";


  $result_set = mysqli_query($conn, $select_query);


  if (mysqli_num_rows($result_set) > 0) {

$arr["login"] = true;

  } else {

$arr["login"] = false;

  }

  mysqli_close($conn);

  $json = json_encode($arr);

  echo $json;

?>




VaRest 플러그인을 이용하여 GET 방식으로 위의 PHP에 로그인 요청하는 예

 - 서버측에서 JSON 문자열을 송신하여 언리얼 측에서 JSON 오브젝트를 수신





VaRest 플러그인을 이용하여 POST 방식으로 위의 PHP에 로그인 요청하는 예




localhost:3000/login 요청으로 실행되는 Node.js 웹서버 코드


var express = require('express');
var app = express();
var qs = require('querystring');

app.get('/login', function(req, res){
console.log("id=%s, pass=%s", req.query.id, req.query.pass);
login(req.query.id, req.query.pass, res);
});

app.post('/login', function(req, res){
var body = '';

req.on('data', function (data) {
body += data;
if (body.length > 1e6)
req.connection.destroy();
});

req.on('end', function () {
var post = qs.parse(body);
console.log("수신된 파라미터:"+post.id+", "+post.pass);
login(post.id, post.pass, res);
});
});

app.listen(3000, function () {
console.log('Example app listening on port 3000!');
});


function login(empID, empName, res)
{
const oracledb = require('oracledb');
oracledb.getConnection(
{
user : "hr",
password : "hr",
connectString : "127.0.0.1/XEPDB1"
},
function(err, connection)
{
if(err){
console.error(err.message);
return;
}
console.log("오라클 접속 성공");
connection.execute(
"SELECT * FROM employees WHERE employee_id=:a AND first_name=:b",
[empID, empName],
{
maxRows:1
},
function(err, result)
{
var jsonObj = {};

if(err) {
console.error(err.message);
connection.close();
jsonObj.result = "Login Failed!";
} else{
//console.log(result.metaData); // Column names etc
console.log(result.rows);
if(result.rows.length>0) {
console.log("검색 성공")
jsonObj.result = "Login OK";
}else{
console.log("검색 실패")
jsonObj.result = "Login Failed!";
}
connection.close();
}
res.end(JSON.stringify(jsonObj));
}
); // end of execute()
}
); // end of getConnection()
}


Posted by cwisky