import java.time.LocalDateTime;

public class SampleEntity {

private Long id;
private LocalDateTime startDt;

// Getter & Setter
public Long getId() {
    return id;
}

public void setId(Long id) {
    this.id = id;
}

public LocalDateTime getStartDt() {
    return startDt;
}

public void setStartDt(LocalDateTime startDt) {
    this.startDt = startDt;
}

}

HitFilm Express: VFX(Visual Effects)와 모션 그래픽에 강점을 가진 무료 동영상 편집 프로그램입니다. 3D 합성, 모션 트래킹, 입자 효과 등 다양한 시각 효과 도구를 제공합니다. 초보자가 사용하기에는 초기 학습이 필요하며, 일부 고급 기능은 유료로 제공됩니다.

DaVinci Resolve:
할리우드 영화 제작에도 사용될 정도로 강력한 색 보정 기능을 제공하는 고급 편집 툴입니다. 멀티캠 편집, 3D 편집, 고급 모션 그래픽, 오디오 편집 도구까지 통합되어 있습니다.
고급 기능을 무료로 제공하지만, 고사양 컴퓨터가 필요하고 인터페이스가 복잡하여 초보자에게는 어려울 수 있습니다.

Filmora:
초보자부터 전문가까지 쉽게 사용할 수 있는 인터페이스를 제공하며, AI 자동 자막, 음악 생성기, 텍스트 음성 변환 등 다양한 AI 기능을 지원합니다. 7,000개 이상의 특수 효과 및 리소스가 내장되어 있어 간편하게 영상을 제작할 수 있습니다.
무료 버전에서는 워터마크가 생성되고 고급 기능 사용이 제한됩니다.

TypeScript 4.x + React 17 + ag-Grid 환경에서 Scenario, Process (Target, No Start, Success, Failed, Error), Comparison (Target, Success, Failed) 항목을 2단 헤더 구조로 구성한 예제를 아래에 제공합니다.

아래 예제는 다음을 포함합니다:

ag-Grid 2단(header group) 구조 설정

React 17, TypeScript 환경

Scenario, Process, Comparison 3개의 그룹 컬럼

// App.tsx
import React from "react";
import { AgGridReact } from "ag-grid-react";
import "ag-grid-community/styles/ag-grid.css";
import "ag-grid-community/styles/ag-theme-alpine.css";
import { ColDef, ColGroupDef } from "ag-grid-community";

const App: React.FC = () => {
const columnDefs: (ColDef | ColGroupDef)[] = [

{
  headerName: "Scenario",
  children: [
    { headerName: "Scenario", field: "scenario", width: 150 }
  ],
},
{
  headerName: "Process",
  children: [
    { headerName: "Target", field: "processTarget", width: 120 },
    { headerName: "No Start", field: "processNoStart", width: 120 },
    { headerName: "Success", field: "processSuccess", width: 120 },
    { headerName: "Failed", field: "processFailed", width: 120 },
    { headerName: "Error", field: "processError", width: 120 },
  ],
},
{
  headerName: "Comparison",
  children: [
    { headerName: "Target", field: "comparisonTarget", width: 120 },
    { headerName: "Success", field: "comparisonSuccess", width: 120 },
    { headerName: "Failed", field: "comparisonFailed", width: 120 },
  ],
},

];

const rowData = [

{
  scenario: "Scenario A",
  processTarget: 100,
  processNoStart: 10,
  processSuccess: 80,
  processFailed: 5,
  processError: 5,
  comparisonTarget: 100,
  comparisonSuccess: 75,
  comparisonFailed: 25,
},
{
  scenario: "Scenario B",
  processTarget: 200,
  processNoStart: 20,
  processSuccess: 160,
  processFailed: 10,
  processError: 10,
  comparisonTarget: 200,
  comparisonSuccess: 150,
  comparisonFailed: 50,
},

];

return (

<div className="ag-theme-alpine" style={{ height: 400, width: "100%" }}>
  <AgGridReact
    columnDefs={columnDefs}
    rowData={rowData}
    domLayout="autoHeight"
  />
</div>

);
};

export default App;

MariaDB에서 SELECT 절에서 중복된 결과를 제거하려면 DISTINCT 키워드를 사용합니다.

기본 문법

SELECT DISTINCT column1, column2, ...
FROM table_name;

예시

예제 테이블: students

idnamemajor
1AliceCS
2BobMath
3AliceCS
4DavePhysics
SELECT DISTINCT name, major FROM students;

결과:

namemajor
AliceCS
BobMath
DavePhysics
DISTINCT는 지정한 열의 전체 조합이 중복일 때만 제거합니다.

단일 컬럼 중복 제거

SELECT DISTINCT name FROM students;

이 경우 name 값이 동일하면 한 번만 나타납니다.

참고사항

  • DISTINCTGROUP BY와도 유사한 결과를 낼 수 있지만, GROUP BY는 집계 함수와 함께 자주 사용됩니다.
  • DISTINCTORDER BY와 함께 사용해도 됩니다:

    SELECT DISTINCT name FROM students ORDER BY name;

원하는 컬럼 조합이나 예제가 있다면 알려주시면 구체적으로 도와드릴 수 있어요.

react17, typescript, ag-grid 환경에서 this.apiSet.api.forEachNode((node) => { 하여 그리드의 항목 중 activityNo:string; 항목(숫자가 입력 또는 빈값) 중 가장 큰 값을 구하고 그리드 목록 중 activityNo 빈값이 있으면 가장 큰 값에서 +1씩 증가하며 세팅하는 예제 만들어 주세요

// ActivityGrid.tsx
import React, { useRef, useCallback } from 'react';
import { AgGridReact } from 'ag-grid-react';
import { AgGridReact as AgGridReactType } from 'ag-grid-react/lib/agGridReact';
import { ColDef } from 'ag-grid-community';

import 'ag-grid-community/styles/ag-grid.css';
import 'ag-grid-community/styles/ag-theme-alpine.css';

interface RowDataType {
id: number;
activityNo: string;
name: string;
}

const initialRowData: RowDataType[] = [
{ id: 1, activityNo: '3', name: 'Task A' },
{ id: 2, activityNo: '', name: 'Task B' },
{ id: 3, activityNo: '10', name: 'Task C' },
{ id: 4, activityNo: '', name: 'Task D' },
{ id: 5, activityNo: '2', name: 'Task E' },
];

const columnDefs: ColDef[] = [
{ field: 'id' },
{ field: 'activityNo' },
{ field: 'name' },
];

const ActivityGrid = () => {
const gridRef = useRef<AgGridReactType>(null);

const fillActivityNumbers = useCallback(() => {

const api = gridRef.current?.api;
if (!api) return;

const nodes: RowDataType[] = [];
api.forEachNode(node => {
  if (node.data) {
    nodes.push(node.data);
  }
});

// 1. 최대 숫자 추출
const activityNumbers = nodes
  .map(row => parseInt(row.activityNo))
  .filter(num => !isNaN(num));

let max = activityNumbers.length > 0 ? Math.max(...activityNumbers) : 0;

// 2. 빈 값 채우기
nodes.forEach(row => {
  if (!row.activityNo || row.activityNo.trim() === '') {
    max += 1;
    row.activityNo = String(max);
  }
});

// 3. 변경 사항 적용
api.setRowData([...nodes]);

}, []);

return (

<div>
  <button onClick={fillActivityNumbers}>Fill Empty Activity Numbers</button>
  <div className="ag-theme-alpine" style={{ height: 300, width: 600 }}>
    <AgGridReact
      ref={gridRef}
      rowData={initialRowData}
      columnDefs={columnDefs}
      domLayout="autoHeight"
    />
  </div>
</div>

);
};

export default ActivityGrid;