LoginSignup
0
0

SqlServerにmssqlを使って接続してJsonの入ったカラムから値を取り出したメモ

Posted at

概要

SQLバインドを使うtediousを使う、は試したので今回はmssqlを使ってSQL Serverに接続を試す。
題材とするSQLとしてJSONが格納されたカラムから配列をレコードとして取り出すことを試す。

データ

{
  "name": "キャラ名",
  "cards": [{ "name": "カード名1"},{ "name": "カード名2"}]
}

ソース

注意書きより、接続は使いまわすソースとする。

api/src/lib/database.ts
import * as sql from 'mssql';
import type { ISqlType, ConnectionPool, IRecordSet } from 'mssql';

interface ExecuteQueryArgs {
  query: string;
  params: {
    name: string;
    data: unknown;
    type: () => ISqlType;
  }[];
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  logger: (...args: any[]) => void;
}

export default class Database {
  private config: string;
  private poolconnection: ConnectionPool | null = null;
  private connected = false;

  constructor(connectionsString: string) {
    if (connectionsString === '') {
      throw new Error('Database connection string is empty');
    }
    console.log(`Database: config: ${JSON.stringify(connectionsString)}`);
    this.config = connectionsString;
  }

  private async connect() {
    try {
      console.log(`Database connecting...${this.connected}`);
      if (this.connected === false) {
        this.poolconnection = await sql.connect(this.config);
        this.connected = true;
        console.log('Database connection successful');
      } else {
        console.log('Database already connected');
      }
    } catch (error) {
      console.error(`Error connecting to database: ${JSON.stringify(error)}`);
    }
  }

  private async executeQuery<T>(args: ExecuteQueryArgs) {
    await this.connect();
    if (this.poolconnection == null) {
      args.logger('Database connection failed');
      return null;
    }
    const request = this.poolconnection.request();
    for (const { name, data, type } of args.params) {
      request.input(name, type, data);
    }
    args.logger(`Database query start`);
    const result = await request.query<T>(args.query);
    args.logger(
      `Database query end. records: ${result?.recordset?.length} affected: ${result?.rowsAffected[0]}`,
    );
    return result;
  }

  async readAll<T>(args: ExecuteQueryArgs) {
    const result = await this.executeQuery<T>(args);
    if (
      (function hasSets(sets?: unknown[]): sets is IRecordSet<T>[] {
        return sets != null && sets.length !== 0;
      })(result?.recordsets)
    ) {
      return result.recordsets[0];
    }
    return [] as T[];
  }

  async read<T>(args: ExecuteQueryArgs) {
    const result = await this.executeQuery<T>(args);
    const ret = result?.recordset[0];
    return ret;
  }

  async execute(args: ExecuteQueryArgs) {
    await this.connect();
    const result = await this.executeQuery(args);
    return result?.rowsAffected[0];
  }
}

接続文字列の設定。ここの接続文字列はSQLバインドで取得したものと同じものを使用。

api/src/lib/constants.ts
export const connectionStringSetting = 'DATABASE_CONNECTION_STRING';
export const connectionString = process.env[connectionStringSetting] || '';
api/src/functions/getCharacterCards.ts
import {
  app,
  HttpRequest,
  HttpResponseInit,
  InvocationContext,
} from '@azure/functions';
import { connectionString } from '@yakumi-api/lib/constants';
import Database from '@yakumi-api/lib/database';
import * as sql from 'mssql';

// 接続を使いまわすため、関数の外で定義
const db = new Database(connectionString);

export async function getCharacterCards(
  request: HttpRequest,
  context: InvocationContext,
): Promise<HttpResponseInit> {
  context.log(`Http function processed request for url "${request.params.id}"`);
  if (!request.params.id) {
    return {
      status: 400,
      body: JSON.stringify({ message: 'Missing character id' }),
    };
  }
  const ret = await db.readAll<{
    Id: string;
    CharacterName: string;
    CardName: string;
  }>({
    query: `
    WITH CharacterData (Id, CharacterName, Cards) AS (
        select id AS Id
             , JSON_VALUE(data, '$.name') AS CharacterName
             , JSON_QUERY(data, '$.cards') as Cards 
        from YakumiCharacter
        where id = @id
      )
      SELECT Id, CharacterName, CardName
      FROM CharacterData
           CROSS APPLY OPENJSON (Cards) WITH (CardName NVARCHAR(100) '$.name');
    `,
    params: [{ name: 'id', data: request.params.id, type: sql.NVarChar }],
    logger: (args) => context.log(args),
  });

  return { body: JSON.stringify(ret) };
}

app.http('getCharacterCards', {
  route: 'get-character-cards/{id}',
  methods: ['GET'],
  authLevel: 'anonymous',
  handler: getCharacterCards,
});

実行結果

select結果をO/Rマッピングした配列を返してくれる。

[
  {
    "Id": "hoge",
    "CharacterName": "キャラ名",
    "CardName": "カード名1"
  },
  {
    "Id": "hoge",
    "CharacterName": "キャラ名",
    "CardName": "カード名2"
  }
]

参考

組み込み関数を使用した JSON データの検証、クエリ、変更 (SQL Server)
OPNE_JSON - 例 3 - CROSS APPLY を使用してテーブルのセルに格納されている JSON データを行と結合する
WITH common_table_expression (Transact-SQL)

node-mssql
Node.js と mssql npm パッケージを使用して Azure SQL Database に接続し、クエリを実行する
Azure SQL Server の Session が減らない

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0