반응형

SQL Server 에서 오픈쿼리 사용 시 8000자를 넘어가면 실행이 불가능하다.

 

오픈쿼리 특성 상 테이블변수를 사용하지 못하기에 WHERE 절에서 IN 으로 조회하게 되는데

대상이 많아지면 8000자를 넘는일이 자주 발생한다.

 

예제

DECLARE @V_TABLE TABLE
(
	ORD_NO INT ,
	ORD_NAME VARCHAR(30)
)

DECLARE @QUERY NVARCHAR(MAX)
DECLARE @MULTI_ORD_NO NVARCHAR(MAX)


-- 1. 대상 테이블로 받을 때 행 별 콤마 추가
-- ex) 1,2,3
SELECT @MULTI_ORD_NO = STRING_AGG(CAST(ORD_NO AS NVARCHAR(MAX)), ',') FROM (SELECT ORD_NO FROM @V_TABLE) A

SET @QUERY = N'
SELECT 1 FROM A WHERE KEY IN ('+ @MULTI_ORD_NO +')
'

EXECUTE (@QUERY) AT LINKED_SERVER

 

참고로 STRING_AGG 할 때 꼭 NVARCHAR(MAX)로 캐스팅해서 써야 한다.

반응형
반응형

 

winform xUnit 단위 테스트 중 쿼리를 실행하는 메서드가 실행되지 않아서 삽질 끝에 해결했습니다.

 

1. Could not load file or assembly 'System.Data.SqlClient' 에러

 

~.sqlClient~(0.0.0.0) ~ 에러

 

xUnit 프로젝트에서 sqlClient 어셈블리 참조 시 바로 중단됨

 

구글링 결과 system.data.sqlClient 어셈블리를 Microsoft.data.sqlClient 으로 변경 후 해결된다 하여 system.data.sqlClient 제거 후 Nuget으로 Microsoft.data.sqlClient로 변경

단위테스트 실행했는데 잘 돌아가나 싶었으나 소수의 테스트만 통과를 못함

디버그 해보니 sqlConnectionstring 에서 예외 발생됨

이미 퇴근시간이 훌쩍 지나 포기하려 했으나 날린 시간이 아까워서 해결하고 가기로 마음먹음

Microsoft.data.sqlClient 로 변경 후 xUnit에서 어셈블리 참조는 되지만

sqlConnectionstring 에서 계속 다른 예외 발생

 

 

 

2. Microsoft.data.sqlClient로 변경 후, DB 연결 에러(신뢰되지 않은 기관에서 인증 체인을 발급했습니다) 

 

혹시나 해서 winform앱을 실행하니 DB인증 에러 발생.

이제는 xUnit도 안되고 winform앱도 DB 연결이 불가능해짐.

그래도 관련 자료가 있어 금방 해결됨

sqlConnectionString에 {server=..~~..~..};TrustServerCertificate=True; 추가하니 DB 인증에러 해결됨

다시 테스트 돌렸으나 1번 해결 후 상황과 같음 xUnit에서는 여전히 안됨 

 

3. xUnit 프로젝트에 Microsoft.data.sqlClient 참조

 

2번 에러 해결 후  1번 에러에서 버전이 4.0.0.0으로 변경되고

똑같이 sqlConnectionString에서 예외 발생됨

(tryExecption ~ 이런걸로 내용이 조금 바뀌어 있었는데 기억하기 싫음)

단위 테스트 프로젝트에도 1번에서와 같이 Nuget으로 Microsoft.data.sqlClient 설치 후 해결

 

 

결론

1. system.data.sqlClient 제거 

2. 어플리케이션, 단위테스트 프로젝트에 각각 Microsoft.data.sqlClient 설치 

3. DB 인증 에러 발생시 TrustServerCertificate=True 추가

 

 

Microsoft.data.sqlClient 로 변경 후 다른 문제 있으면 추가로 업데이트 예정.

 

 

반응형
반응형

 

타이머를 이용해서 간단하게 구현할수 있습니다.

 

유저에게 필수 입력 항목을 강조할때 사용했습니다.

 

1) 코드 예제

 

이해가 안되시면 코드전체를 긁어서 사용하시면 됩니다.

 private Timer mBlinkTimer = new Timer();
       private bool mStart = false;
​
       public Form1()
       {
           InitializeComponent();
       }
​
       Button btnBlink;
       Button btnRun;
​
       private void Form1_Load(object sender, EventArgs e)
       {
           this.Size = new Size(1000, 500);
​
           btnBlink = new Button();
           btnBlink.Size = new Size(300, 300);
           btnBlink.Location = new Point((this.Width - btnBlink.Size.Width) / 2, (this.Height - btnBlink.Size.Height) / 2);
           this.Controls.Add(btnBlink);
​
           btnRun = new Button();
           btnRun.Text = "Start/Stop";
           btnRun.Size = new Size(100, 0);
           btnRun.Dock = DockStyle.Right;
           this.Controls.Add(btnRun);
​
           btnRun.Click += btnRun_Click;
​
           mBlinkTimer.Interval = 500;
           mBlinkTimer.Tick += BlinkTimer_Tick;
​
           btnBlink.BackColor = Color.Gainsboro;
       }
​
       private void BlinkTimer_Tick(object sender, EventArgs e)
       {
           if (mStart)
           {
               if (btnBlink.BackColor == Color.Gainsboro)
               {
                   btnBlink.BackColor = Color.Yellow;
               }
               else
               {
                   btnBlink.BackColor = Color.Gainsboro;
               }
           }
       }
​
       private void btnRun_Click(object sender, EventArgs e)
       {
           mBlinkTimer.Enabled = !mStart;
           mStart = !mStart;
       }

Timer Interval에 반짝일 속도를 설정하고 

Timer Tick 이벤트에서

멤버 변수인 불린 start가 true 일때만 반짝이게 하는 코드입니다.

 

 

 

2) 결과

 

반응형
반응형

 

 

1. 코드 예시

​
       private DataTable CreateDataTable()
       {
           DataTable dt = new DataTable();
​
           dt.Columns.Add("NUM", typeof(int));
           dt.Columns.Add("NAME", typeof(string));
           dt.Columns.Add("DESC", typeof(string));
​
           for(int i=1; i <= 10; i++)
           {
               var dr = dt.NewRow();
​
               dr["NUM"] = i;
               dr["NAME"] = i % 2 == 0 ? "이름" : null;
               dr["DESC"] = "";
​
               dt.Rows.Add(dr);
           }
​
           return dt;
       }
       
       private void Form1_Load(object sender, EventArgs e)
       {
           gridControl1.DataSource = CreateDataTable();
​
           gridView1.Columns["NAME"].ShowButtonMode = DevExpress.XtraGrid.Views.Base.ShowButtonModeEnum.ShowAlways;
       }
​
     
​
       private void gridView1_CustomRowCellEdit(object sender, DevExpress.XtraGrid.Views.Grid.CustomRowCellEditEventArgs e)
       {
           RepositoryItemButtonEdit rbtnEdit = new RepositoryItemButtonEdit();
​
           if(e.Column.FieldName == "NAME")
           {
               if(e.CellValue.ToString() == "")
               {
                   e.RepositoryItem = rbtnEdit;
               }
           }
       }

 

CustomRowCellEdit 이벤트에서 표시하고 싶은 셀에 조건을 두어 RepositoryItem에 버튼을 추가한다.

 

 

 

2. 실행 결과

 

반응형
반응형

 

MS SQL ORDER BY 조건

 

프로그래밍시 정렬을 해도되지만

  

쿼리단계에서 ORDER BY CASE 문을 이용한 정렬 방법이다

 

다음과 같은 테이블이 있을때

 

USER_ST 3, 2, 1, 4 로 정렬하려면

 

SELECT * FROM USERS
ORDER BY 
  CASE 
    WHEN USER_ST = 3 THEN 0 
	WHEN USER_ST = 2 THEN 1
	WHEN USER_ST = 1 THEN 3
	WHEN USER_ST = 4 THEN 4
	ELSE 99
  END ASC

위와 같이 CASE 를 사용하여 임의로 순서를 지정해주면 된다.

 

 

 

SELECT * FROM USERS
ORDER BY 
  CASE 
    WHEN USER_ST = 3 THEN 0 
	WHEN USER_ST = 2 THEN 1
	WHEN USER_ST = 1 THEN 3
	WHEN USER_ST = 4 THEN 4
	ELSE 99
  END ASC,
  CASE 
    WHEN USER_ID = 'PARK' THEN 0
	ELSE 1
  END ASC

위와 같이 CASE 문을 여러개 중복 사용하여 정렬도 가능하다.

반응형
반응형

 

C# 디렉토리 파일 구분 방법

 

사내 시스템에서 사용자가 파일 업로드 시

 

업로드 파일을 압축해서 보관하다가 압축파일 다운 후 압축을 해제하도록 했는데

 

사용자가 USB STICK을 사용하다 보니 압축 해제 속도가 나오지 않아서

 

그냥 폴더 상태로 보관하도록 루틴을 변경했다.

 

그러다보니 기존 ZIP파일일때와 폴더일때 구분이 필요하게 되었다.

 

 

            string path = "Selected Path";

            FileAttributes attr = File.GetAttributes(path);
            if ((attr & FileAttributes.Directory) == FileAttributes.Directory)
            {
                //디렉토리(폴더)
            }
            else
            {
                //파일
            }
반응형
반응형

 

 

C# 숨김 폴더 체크 방법

이번 작업 중에 이동식 드라이브를 연결하면

 

모든 디렉토리와 파일을 삭제해야하는데

 

이동식 드라이브 내부에 두가지 숨겨진 폴더가 있어서

 

예외처리가 필요했다.

 

 

숨김폴더 체크 예제

using System.IO;

            string path = "D:\\";

            DirectoryInfo dir = new DirectoryInfo(path);

            if((dir.Attributes & FileAttributes.Hidden) == FileAttributes.Hidden)
            {
                Console.WriteLine("숨김 폴더");
            }

 

저는 디렉토리 &파일 삭제 재귀 함수에

 

Hidden 이면 예외처리하게 사용

 

 

 

반응형
반응형

 

QM6 배출가스 점검 등 

 

결혼 후 6개월정도 차 없이 지내다가

 

없는 살림이지만 집안에 차 한대는 있어야 겠다 싶어서 큰 맘먹고 구매했었습니다

전기차를 사고 싶었지만 와이프의 극구 반대로 SUV차량을 구매하게 되었고

평소 출퇴근 거리가 짧고 가성비가 좋은 QM6 가솔린으로 구매했습니다.

원하던 차가 아니라 첫 새차지만 사실 애정이 가지는 않네요..

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

배출가스 점검등 발생 이유?

 

 

 

2년 4개월정도 운행하면서 잔고장 한번 없이 잘 타고 있었는데

갑자기 주행 중에 배출가스 장치 점검 등이 점등 되었습니다.

열심히 구글링을 하고 동호회 카페도 확인해본 결과 

주유구를 꽉 닫지 않았을때 점등 될 수 있다고 합니다.

사실 점검등이 들어온날 주유를 했는데 주유구를 잠굴때 띠딕 소리 한번만나게

대충 닫았던 것 이 생각나서 여러번 다시 닫고 

 

2주 정도 주행 하였으나 결국 점검등이 사라지지는 않았습니다.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

센터 방문 후기

 

그동안 프로젝트가 바빠서 연차를 못 냈기에 점검도 받을 겸

월요일에 연차를 내고 오전 11시에 방문했습니다.

센터에 방문하니 접수하시는분도 주유구 꽉 닫았냐고 물어보시더라구요

꽉 닫았다고 말씀드리니, 점검이 필요하다고 하시고 쉬고 계시라고 하십니다.

대기시간 포함해서 총 1시간 소요되었고, 센서 점검 후 이상 없다 하여 끝.

 

어떤 문제가 있었다 오류다 등의 피드백이 없어서 조금 아쉽네요

 

 

 

 

 

 

반응형
반응형

최근 압축 및 압축해제를 구현해야해서 라이브러리를 찾아보던 중

IONIC.ZIP 라이브러리를 사용하게 되었습니다.

 

설치는 비주얼스튜디오 NuGet에서 Ionic으로 검색하면 설치할 수 있습니다.

 

예제는 선택한 폴더를 압축/압축해제 하는 예제 코드 입니다.

 

1. 압축

        private void zipFolder()
        {
            string sSourcePath = tbLoadFolderPath.Text;
            string sTargetPath = tbSavePath.Text;

            try
            {
                using (ZipFile zip = new ZipFile())
                {
                    DirectoryInfo di = new DirectoryInfo(sSourcePath);
                    //프로그레스바 최대 값 (디렉토리내 모든 파일 수)
                    mMaximum += di.GetFiles("*.*", System.IO.SearchOption.AllDirectories).Count();

                    //프로그레스바 이벤트 설정
                    zip.SaveProgress += Zip_SaveProgress;


                    FileInfo[] infos = di.GetFiles("*.*", SearchOption.AllDirectories);

                    string[] files = new string[infos.Length];

                    for (int i = 0; i < infos.Length; i++)
                    {
                        files[i] = infos[i].FullName;
                    }

                    byte[] b = null;
                    string fileName = string.Empty;

                    foreach (string file in files)
                    {
                        fileName = file.Replace(sSourcePath, "");

                        //기본 인코딩 타입으로 읽기
                        b = Encoding.Default.GetBytes(fileName);

                        // IBM437로 변환
                        fileName = Encoding.GetEncoding("IBM437").GetString(b);

                        zip.AddEntry(fileName, File.ReadAllBytes(file));
                    }

                    DirectoryInfo dir = new DirectoryInfo(sTargetPath);

                    if(!dir.Exists)
                    {
                        dir.Create();
                    }

                    string[] split = sSourcePath.Split('\\');

                    string sZipName = split[split.Length - 1];

                    zip.Save($"{sTargetPath}\\{sZipName}.zip");
                }
                Process.Start(sTargetPath);
            }
            catch(Exception ex)
            {
                MessageBox.Show($"{ex.Message}\r\n압축 실패");
                return;
            }
        }

 

2. 압축 해제

 

       private void unZipFile()
        {
            string sSourcePath = tbLoadZipPath.Text;
            string sTargetPath = tbSavePath.Text;

            try
            {
                using (ZipFile zip = ZipFile.Read(sSourcePath))
                {
                    FileInfo fi = new FileInfo(sSourcePath);

                    zip.ExtractProgress += Extract_Progress;

                    //프로그레스바 맥시멈 값
                    mMaximum = zip.Entries.Count;

                    DirectoryInfo dir = new DirectoryInfo(sTargetPath);

                    if (!dir.Exists)
                    {
                        dir.Create();
                    }

                    string saveFolderPath = $"{sTargetPath}\\{Path.GetFileNameWithoutExtension(sSourcePath)}";

                    for (int i = 0; i < zip.Entries.Count; i++)
                    {
                        ZipEntry entry = zip[i];

                        //IBM437 인코딩
                        byte[] byteIbm437 = Encoding.GetEncoding("IBM437").GetBytes(zip[i].FileName);
                        //euckr 인코딩
                        string euckrFileName = Encoding.GetEncoding("euc-kr").GetString(byteIbm437);

                        zip[i].FileName = euckrFileName;

                        entry.Extract(saveFolderPath, ExtractExistingFileAction.OverwriteSilently);
                    }
                    Process.Start(saveFolderPath);
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show($"{ex.Message}\r\n압축 해제 실패");
                return;
            }
        }

 

 

3. 예제 소스 코드 파일

 

 

 

 

 

 

 

 

 

 

 

 

WindowsFormsApp3.zip
0.86MB

프로그레스바를 ionic.zip에서 지원하는 이벤트로만 구현했는데

개인적으로 사용하실때에는 조금 손보는게 좋을 것 같습니다.

 

예제 코드는 이전에 구글링한 자료에 집에서 생각나는것만 정리한 정도이니 참고만 하시면 될 것 같습니다

 

 

 

반응형
반응형

SQL SERVER AFTER 트리거 사용 시

특정 컬럼 업데이트를 감지하여 작동하는 쿼리 예제 입니다.

 

흔히 이력 정보를 저장할 때, 특정 컬럼의 값이 변하는지 감지하고

감지하는 컬럼의 값이 변할때만 작동하도록 해야하는 상황이 많습니다.

 

예제.

사용자 테이블과 사용자 이력 테이블 트리거 작성

 

트리거를 가지고 있는 USERS 테이블 

*[USER_ID], [USER_NM], [USER_PWD], [USER_ST], [EDT_DT]

 

USERS 테이블의 이력을 저장할 USERS_HIS 테이블

*[USER_ID], *[HIS_IDX], [USER_NM], [USER_ST], [EDT_DT]

 

목표는 USERS 테이블의 [USER_ST] (유저 상태)컬럼 값이 변할 때 이력을 저장하는 것 입니다.

[USER_ST]가 변할 때 USERS_HIS 테이블에 데이터를 삽입하도록 트리거를 작성하면 됩니다.

 

예제는 다음과 같습니다.

 

 

 

 

트리거 INSERTED 와 DELTED 시에는 그냥 이력을 삽입하면되니 UPDATED만 예제로 작성하겠습니다.

 

1. 트리거 쿼리문

SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
-- =============================================
-- Author:		davi
-- Create date: 210323
-- Description:	Insert history when [USERS]'s USER_ST updated
-- =============================================
CREATE TRIGGER [dbo].[TRG_USERS_UPDATED]
   ON  [dbo].[USERS]
   AFTER INSERT,DELETE,UPDATE
AS 
BEGIN
	-- SET NOCOUNT ON added to prevent extra result sets from
	-- interfering with SELECT statements.

	SET NOCOUNT ON;
    --USER 테이블에서 이력을 저장할 컬럼 변수 생성
	DECLARE
	@USER_ID varchar(20),
	@HIS_IDX int,
	@USER_NM varchar(10),
	@USER_ST	char(1),
	@EDT_DT datetime

		--I : INSERTED
		--U : UPDATED
		--D : DELETED
		DECLARE @TRG_ACTION char(1)

		--INSERTED
		SET @TRG_ACTION = 'I'

		--U : UPDATED
		--D : DELETED
		IF EXISTS(SELECT * FROM deleted)
		  BEGIN
		    SET @TRG_ACTION = (CASE WHEN EXISTS (SELECT * FROM inserted) THEN 'U' ELSE 'D' END)
		  END


		  --UPDATED 만 작성
		  IF @TRG_ACTION = 'U'
		   BEGIN
			SELECT 
			  @USER_ID = INS.USER_ID, @USER_NM = INS.USER_NM, @USER_ST = INS.USER_ST, @EDT_DT = INS.EDT_DT
			FROM DELETED DEL
			JOIN INSERTED INS ON DEL.USER_ID = INS.USER_ID --PK로 JOIN
			WHERE 
			DEL.USER_ID = INS.USER_ID AND
			DEL.USER_ST != INS.USER_ST --컬럼 값이 다르면 USER_ST가 변경되었을때이다.  
		   END

		   IF(@USER_ID IS NOT NULL)
		     BEGIN
			   --USERS_HIS 테이블에서 해당 USER_ID의 변경 순서 +1
			   SELECT @HIS_IDX = ISNULL(MAX(HIS_IDX),0)+1 FROM USERS_HIS WITH(NOLOCK) WHERE USER_ID = @USER_ID

			   --USERS_HIS 삽입
			   INSERT INTO USERS_HIS 
			   (USER_ID, HIS_IDX, USER_NM, USER_ST, EDT_DT) VALUES
			   (@USER_ID, @HIS_IDX, @USER_NM, @USER_ST, @EDT_DT)
			 END

END
GO

 

해당 쿼리는 업데이트 후 정보를 이력으로 저장 했습니다.

 

 

 

2. 트리거 검증 

 

 1) 검증 쿼리

--변경전
SELECT * FROM USERS

SELECT * FROM USERS_HIS

--USER_ST 및 다른 값 업데이트
UPDATE USERS
  SET USER_ST = 2, USER_NM = '이지은', EDT_DT = GETDATE()
WHERE USER_ID = 'davi'

-- USER_ST 업데이트 후
SELECT * FROM USERS

SELECT * FROM USERS_HIS

 

 2) 결과

 

 

간단하게 작성하려고 했는데 은근히 시간이 오래 걸리네요.

 

참고 하시고 오류 있으면 댓글 부탁드립니다.

반응형

+ Recent posts