データベースからグループごとに最大値であったレコードを取得したい
データベースからグループごとに最大値であったレコードを取得したい。最大値そのものではなくて、あるカラムが最大値になっているレコードすべてを取得。
学校のテストのスコアを管理するDBを想定してみる。
まず生徒の情報を保存するテーブルを作る。
create table student ( student_id integer primary key, class integer, name varchar(128) );
次に、科目の情報を管理するテーブルを作る
create table subject ( subject_id integer primary key, name varchar(32) );
そして、各生徒の科目ごとの得点を管理するテーブルを作る
create table exam ( id integer primary key, student_id integer, subject_id integer, score integer );
テーブルの準備ができたので、テストの結果を挿入。
insert into student values(0,1,'佐藤'); insert into student values(1,2,'鈴木'); insert into student values(2,3,'高橋'); insert into student values(3,1,'田中'); insert into student values(4,2,'伊藤'); insert into student values(5,3,'渡辺'); insert into subject values(0, '国語'); insert into subject values(1, '算数'); insert into exam values(0, 0, 0, 80); insert into exam values(1, 0, 1, 70); insert into exam values(2, 1, 0, 50); insert into exam values(3, 1, 1, 40); insert into exam values(4, 2, 0, 20); insert into exam values(5, 2, 1, 90); insert into exam values(6, 3, 0, 50); insert into exam values(7, 3, 1, 76); insert into exam values(8, 4, 0, 39); insert into exam values(9, 4, 1, 75); insert into exam values(10, 5, 0, 19); insert into exam values(11, 5, 1, 100);
わかりやすいように一旦全部が載っている表にして、この後の実行結果と見比べることにする。
select subject.subject_id, subject.name as subject_name, student.student_id, student.class, student.name as student_name, score, id from exam left join subject on exam.subject_id = subject.subject_id left join student on exam.student_id = student.student_id order by subject.name, student.class, student.name;
各科目の最高得点をとってみる
select subject.name, max(score) from exam join subject on exam.subject_id = subject.subject_id group by subject.name;
クラスごとの最高得点
select student.class, subject.name, max(score) from exam join student on exam.student_id = student.student_id join subject on exam.subject_id = subject.subject_id group by student.class, subject.name order by student.class, subject.name;
では誰がこの最高得点だったのか?
select l.class, r.subject_name, max_score, r.name from ( select student.class, subject.subject_id, max(score) as max_score from exam join student on exam.student_id = student.student_id join subject on exam.subject_id = subject.subject_id group by student.class, subject.subject_id, subject.name) as l left join ( select student.class, subject.name as subject_name, subject.subject_id, score, student.name from exam join student on exam.student_id = student.student_id join subject on exam.subject_id = subject.subject_id ) as r on l.class = r.class and l.subject_id = r.subject_id and l.max_score = r.score order by l.class, r.subject_name;
もっとシンプルに書けないものか。。。WITH句を使えば若干シンプル?
with t as ( select subject.subject_id, subject.name as subject_name, student.student_id, student.class, student.name as student_name, score from exam left join subject on exam.subject_id = subject.subject_id left join student on exam.student_id = student.student_id ) select l.class, t.subject_name, l.max_score, t.student_name from ( select class, subject_id, max(score) as max_score from t group by class, subject_id ) as l left join t on l.class = t.class and l.subject_id = t.subject_id and l.max_score = t.score order by l.class, t.subject_name;
もう少しスマートに、、最大値がある行を抽出する方法を工夫。
with t as ( select subject.name as subject_name, student.class, student.name as student_name, score from exam left join subject on exam.subject_id = subject.subject_id left join student on exam.student_id = student.student_id ) select class, subject_name, score, student_name from t where not exists ( select 1 from t as t0 where t.class = t0.class and t.subject_name = t0.subject_name and t.score < t0.score ) order by class, subject_name;
RockyLinuxにKeycloakをインストールした
全部rootで実施。
ポートを開ける
firewall-cmd --add-port=8080/tcp --zone=public --permanent firewall-cmd --reload
インストールする
dnf -y install java-11-openjdk dnf -y install curl cd /tmp curl -OL https://github.com/keycloak/keycloak/releases/download/18.0.2/keycloak-18.0.2.tar.gz cd /usr/local/bin tar /tmp/zxvf keycloak-18.0.2.tar.gz
環境変数にKeycloakの管理者のアカウント名とパスワードを設定しておく。
export KEYCLOAK_ADMIN=admin export KEYCLOAK_ADMIN_PASSWORD=admin
起動する
cd /usr/local/bin/keycloak-18.0.2/bin ./kc.sh start-dev
とりあえず起動だけしたいのであればここまででOK。
Dockerを使うのであれば、ここまでと同じことが以下だけで完了する。
docker run -p 8080:8080 -e KEYCLOAK_ADMIN=admin -e KEYCLOAK_ADMIN_PASSWORD=admin quay.io/keycloak/keycloak:18.0.2 start-dev
、、、このあともいろいろ調べたのだが、しばらくkeycloakは使わないことになったので、おしまい。
Reactでログインしたら元々いたページに戻すやつ
useLocationで今いるページのパスを取得、useNavigateでページのパスを渡しながらログイン画面に飛ばして、ログインに成功したら先ほど渡されたパスに移動する。
具体的には、
npx create-react-app 云々 --template typescript
をした直後の状態として、App.jsxを以下のように書き換えるとなんとなく理解できる。
import React, { useState } from "react"; import { BrowserRouter, Routes, Route, Link, useLocation, useNavigate, Navigate, } from "react-router-dom"; // トップページ const TopPage = () => { return ( <> <h2>トップページ</h2> {["a", "b", "c"].map((x: string, i: number) => { return ( <p key={i}> ・<Link to={"/" + x}>{x}</Link> </p> ); })} </> ); }; // コンテンツ const Comp = () => { const location = useLocation(); const navigate = useNavigate(); return ( <> <h2>コンテンツ</h2> 私は今{location.pathname}にいます。 <br /> <button onClick={(e) => { navigate("/login", { state: location.pathname }); }} > ログイン画面にリダイレクトするイメージ </button> <hr /> <Link to="/">トップページ</Link> </> ); }; // ログイン画面 const Login = () => { const location = useLocation(); const navigate = useNavigate(); return ( <> {location.state}から来たようだ <p> <button onClick={(e) => { let url: string = location.state as string; navigate(url); }} > ログイン </button> </p> <hr /> <Link to="/">トップページ</Link> </> ); }; // function App() { return ( <> <BrowserRouter> <Routes> <Route path="/login" element={<Login />} /> <Route path="/a" element={<Comp />} /> <Route path="/b" element={<Comp />} /> <Route path="/c" element={<Comp />} /> <Route path="/" element={<TopPage />} /> </Routes> </BrowserRouter> </> ); } export default App;
ReactのContext
React内でグローバル変数的にアクセスできるContextを使い、ログインしたあとでそのユーザ名などを保持する機能を実現したかった。 ちなみにContextとは別にcookieなどにアクセストークンを管理し、本当に大事なデータはアクセストークンを使わないと取得できないようにすることでセキュリティは担保すればよいと思っている。
以下でプロジェクトを作った直後のところからスタート。
npx create-react-app 云々 --template typescript
いきなり一番重いところから。App.tsxを以下のように書き換える。
import React, { createContext, useState } from "react"; import { BrowserRouter, Routes, Route } from "react-router-dom"; import { Header } from "./Header"; import { Login } from "./Login"; import { Logout } from "./Logout"; import { Contents } from "./Contents"; import { Admin } from "./Admin"; import { Portal } from "./Portal"; // // コンテキストで管理したいデータの型を定義する。 // export type UserInfo = { user_name: string; mail_address: string; admin_flag: number; }; // // コンテキストを作る。初期値も与える。 // export const UserContext = createContext({ user_name: "初期値", mail_address: "初期値", admin_flag: 0, }); function App() { // // コンテキスト上に保持する変数とそれを変更する関数の宣言 // const [user_info, setUserInfo] = useState<UserInfo>({ user_name: "", mail_address: "", admin_flag: -1, }); // コンテキストを参照したいコンポーネントたちを // コンテキスト.Providerでくくる。 // LoginとLogoffにはコンテキストの値を変更するための // 関数を渡す。それ以外はそれぞれのコンポーネント内で // コンテキストにアクセスしにいく。 return ( <> <UserContext.Provider value={user_info}> <Header /> <BrowserRouter> <Routes> <Route path="/login" element={<Login setUserInfo={setUserInfo} />} /> <Route path="/logout" element={<Logout setUserInfo={setUserInfo} />} /> <Route path="/contents" element={<Contents />} /> <Route path="/admin" element={<Admin />} /> <Route path="/" element={<Portal />} /> </Routes> </BrowserRouter> </UserContext.Provider> </> ); } export default App;
ログイン用のコンポーネントをLogin.tsxというファイルのなかに以下のように書いて実装。 本当のログイン処理はもうちょっと真面目に書く必要あり。
import { useState } from "react"; import { Link } from "react-router-dom"; import { UserInfo } from "./App"; // // ユーザ情報を変更する関数を受け取るためのインタフェース // interface LoginProps { setUserInfo: (userInfo: UserInfo) => void; } export const Login = (props: LoginProps) => { const [username, setUsername] = useState<string>(""); const [password, setPassword] = useState<string>(""); const procLogin = () => { // ログインを処理を行う。 // この実装はContextの使い方の勉強用なので省略し、 // 代わりにダミーの値を入れる。 console.log(username, password); if (username === "user01" && password === "user01") { // コンテキストを書き換える props.setUserInfo({ user_name: "大谷翔平", mail_address: "shohei.otani@example.com", admin_flag: 0, }); } else if (username === "admin" && password === "admin") { // コンテキストを書き換える props.setUserInfo({ user_name: "長嶋茂雄", mail_address: "nagashima@example.com", admin_flag: 1, }); } }; return ( <> <p> username : <input type="text" value={username} onChange={(e)=>{setUsername(e.target.value);}} /> </p> <p> password : <input type="password" value={password} onChange={(e)=>{ setPassword(e.target.value); }} /> </p> <p> <button onClick={(e) => { procLogin(); }}>login</button> </p> <hr /> <Link to="/">ポータルへ</Link> </> ); };
ログアウトでcontextの中身を初期化する。 Logout.tsxというファイルを作って以下のように書く。
import { Link } from "react-router-dom"; import { UserInfo } from "./App"; // // ユーザ情報を変更する関数を受け取るためのインタフェース // interface LogoutProps { setUserInfo: (userInfo: UserInfo) => void; } export const Logout = (props: LogoutProps) => { return ( <> <button onClick={(e) => { props.setUserInfo({ user_name: "", mail_address: "", admin_flag: -1, }); }} > ログオフする </button> <Link to="/">ポータルへ</Link> </> ); };
どのURLにアクセスしてもページの上のほうにログインしているユーザの名前などが表示される領域を作る。 Header.tsxという名前のファイルを作り以下のように書く。
import { useContext } from "react"; import { UserContext } from "./App"; // // 各ページの先頭に表示する、ログイン中のユーザ名とそのメアドを表示するイメージ // export const Header = () => { const user = useContext(UserContext); return ( <> [ {user.user_name} | {user.mail_address} ] <br /> </> ); };
ポータル画面では、ログインしているかどうか、またログインしている場合はそのユーザの権限次第で表示するリンクを切り替える。 Portal.tsxというファイルを作って以下のように書く。
import { useContext } from "react"; import { Link } from "react-router-dom"; import { UserContext } from "./App"; export const Portal = () => { const user = useContext(UserContext); // 権限ごとにリンクを変更する。 // (アクセス制御は直リンも考慮しリンク先で実施) return ( <> <h1>ポータル</h1> {user.admin_flag === 1 ? ( <> <Link to="/admin">管理ページ</Link> | </> ) : ( "" )} {user.admin_flag === -1 ? ( <Link to="/login">ログイン</Link> ) : ( <> <Link to="/contents">コンテンツ</Link> | <Link to="/logout">ログアウト</Link> </> )} </> ); };
あとは、適当にコンテンツを作る。 Contents.tsxという名前のファイルを作って以下のようなかんじで書く。
import { Link } from "react-router-dom"; export const Contents = () => { return ( <> <h1>コンテンツ</h1> ログインしていればみることができます。 ただこの実装だと直接URLをたたくとログインしていなくても見えてしまうので、 実際の実装ではAPIでとってくるなどしてアクセスコントロールすること。 <hr /> <Link to="/">ポータルに戻る</Link> </> ); };
以上。
Goのinterface
Goで単体試験などをしたくなった場合、テスト対象となる機能が呼び出す関数を外から渡したくなる。 そんなときはinterfaceを使うことで実現できる。
こんな感じ
package main import "fmt" // インタフェースとして、どんな関数を具備している必要があるかを定義。 type Speaker interface { Greeting() } // そのインタフェースで定義された関数を使う関数を定義 // 単体テストのことを想定しているならば、これがテスト対象 func Run(i Speaker) { i.Greeting() } // インタフェースを持たせる構造体を定義 type Japanese struct { } // インタフェースに合うように、インタフェースで定義されている // 関数を構造体に対して実装 func (p *Japanese)Greeting() { fmt.Println("こんにちは") } // もうひとつ、同じインタフェースを持たせる別の構造体を定義しておいて、 type American struct { } // 先ほどと同じく、インタフェースに合うように、インタフェースで定義されている // 関数を構造体に対して実装 func (a *American)Greeting() { fmt.Println("Hi!") } // 使ってみる func main() { // Japanese構造体を渡してもちゃんと実行できるし、 japanesePeople := &Japanese{} Run(japanesePeople) // American構造体を渡してもちゃんと実行できる americanPeople := &American{} Run(americanPeople) }
Redmineを試したくなった
Dockerを使うのが簡単だ。
docker -p 3000:3000 redmine
管理者の初期アカウントはadmin、パスワードもadmin。
管理者権限のあるアカウントでログインして、以下の作業を実施していく。 ページ最上部の左寄りの場所に「管理」というリンクがあるのでそこをクリックして管理画面に遷移。各項目の設定画面へのリンクがあるのでそちらで作業する。
- ユーザを作る
- ユーザはAPIでも作ることができるので、何かのシステムと同じユーザを作るなどといったことも簡単にできる。
- ロールを作る
- ステータスを作る
- トラッカーを作るのに必要
- トラッカーを作る
- プロジェクトを作る
- APIで作ることも可能、だけど、そんなにたくさん作るものでもないのであまり意味ないか?
- プロジェクトにユーザを追加する、同時にそのプロジェクトにおけるそのユーザのロールも設定する。
- チェックボックスになっているので、同じロールの人であれば複数のユーザを一気に登録可能。
- 優先度を作る
- チケットを作るときに必要
- 設定の中の値のリストの中にあるので注意
この辺までいじって、自分がやりたいことができないことがわかったので、終了。 (複数のグループをissueに割り当てたかった)
ginを使って作成したWebアプリをsystemdを使ってデーモンとして動かす
ginを使って作成したWebアプリをデーモンとして動かす方法について、少し前の記事でsupervisordを用いる方法を延々書いてしまったが、ここではsystemdを使う方法を書いてみる。
1. ログをsyslogに吐くようにする。
デーモン化したらログは画面ではなくファイルに出したいところ。 Linuxで動かす(よね?)なら、syslogを使ってしまうのがお手軽だ。
logger, err := syslog.New( syslog.LOG_NOTICE|syslog.LOG_USER, "SampleWebApp") if err != nil { panic(err) } gin.SetMode(gin.ReleaseMode) gin.DisableConsoleColor() gin.DefaultWriter = logger
2. systemd用のユニットファイルを作成する。
たぶん、これくらいが一番シンプルなんじゃないかな。。
[Unit] Description=Sample Web Application [Service] ExecStart=/usr/local/bin/SampleWebApp KillMode=process Restart=always [Install] WantedBy=multi-user.target
3. sytemctlコマンドで起動する。
systemctl restart サービス名
4. おまけ firewalldでポートを開放する
とりあえず今の状態を確認して、
firewall-cmd --list-all
たとえばこんなコマンドを打って、
firewall-cmd --add-port=3000/tcp --zone=public --permanent
そのあとリロードしてから、
firewall-cmd --reload
再度状態を確認すると、
firewall-cmd --list-all
先ほど追加したポートが開放されていることがわかるはず。