Как получить список имен столбцов из запроса, который не возвращает ни одной строки

Я использую MySQL, ExpressJS и Sequelize для создания функции, которая возвращает список столбцов из заданного запроса SQL. Функция должна работать, даже если запрос не возвращает строк.

Вот текущая реализация моей функции:

async getColumnsFromQuery ( query ) {
    try {
        let queryResponse = await db.sequelize.query ( query, {
            type: db.sequelize.QueryTypes.SELECT
        });
        
        let columns = [];
        
        if ( queryResponse.length > 0 ) {
            columns = Object.keys( queryResponse[ 0 ] );
        }
        
        return columns;

    } catch ( err ) {
        throw err;
    }
}

Эта функция отлично работает, когда запрос возвращает строки, но если запрос не возвращает строк, массив columns пуст. Мне нужно убедиться, что функция по-прежнему возвращает правильные имена столбцов, даже если данные не возвращаются.

Я пробовал использовать INFORMATION_SCHEMA, SHOW COLUMNS или DESCRIBE, но они предназначены только для таблиц, а не для запросов.

queryResponse[1] — метаданные запроса. Я ожидаю, что оно где-то там.
Barmar 11.06.2024 22:41

Попробуйте использовать console.info(queryResponse[1]), чтобы увидеть, есть ли там имена столбцов.

Barmar 11.06.2024 22:46

@Barmar queryResponse — это массив, содержащий строки, сгенерированные в результате выполнения запроса, переданного функции. Я попробовал использовать console.info(queryResponse); и получил { id: 53, name: "John Doe", Gender: "M", ... } Спасибо за ответ!

Owais Farooq 11.06.2024 23:05

В документации сказано, что это массив [results, metadata]. results — это массив строк, возвращаемых запросом. metadata — дополнительная информация.

Barmar 11.06.2024 23:10

Результат, который вы показываете, выглядит как queryResponse[0][0].

Barmar 11.06.2024 23:12

На самом деле, возможно, использование type: db.sequelize.QueryTypes.SELECT позволяет исключить метаданные и просто вернуть результаты. Попробуйте без этого.

Barmar 11.06.2024 23:13

@Barmar Я пробовал использовать type: db.sequelize.QueryTypes.SELECT и без него, в обоих случаях я получаю 2 нулевых массива. (2) [Array(0), Array(0)]

Owais Farooq 12.06.2024 20:26
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
7
63
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Думаю, мне удалось найти способ получить все имена столбцов из запроса, включая типы данных столбцов.

Я использовал node-sql-parser библиотеку, которая анализирует операторы SQL. Используя результат анализа запроса, я смог получить имена всех столбцов.

Вот как я это сделал:

const { Parser } = require('node-sql-parser');

// For SELECT TableName.* FROM ....
getAllColumnsFromAllTables (allTableAllColumns) {
    const TablesFromDB = Object.keys(allTableAllColumns);
    return TablesFromDB.map(dbTable => {
        return allTableAllColumns[dbTable].columns;
    });
}
// For SELECT TableName.* FROM ....
getAllColumnsFromATable(allTableAllColumns, tableAlias) {
    const TablesFromDB = Object.keys(allTableAllColumns);
    for ( let i = 0 ; i < TablesFromDB.length; i++ ) {
        if (TablesFromDB[i].toLowerCase() === tableAlias[table].toLowerCase()) {
            // let tableColumns = allTableAllColumns[TablesFromDB[i]].columns.map( column => `${table.as ? table.as : table.table}.${column}`)
            return allTableAllColumns[TablesFromDB[i]].columns;
        }
    }
}
// For SELECT TableName.ColumnName FROM ....
getAColumnFromATable(allTableAllColumns, tableAlias, col) {
    const TablesFromDB = Object.keys(allTableAllColumns);
    for ( let i = 0 ; i < TablesFromDB.length; i++ ) {
        if (TablesFromDB[i].toLowerCase() === tableAlias[table].toLowerCase()) {
            for (let j = 0; j < allTableAllColumns[TablesFromDB[i]].columns.length; j++) {
                const dbCol = allTableAllColumns[TablesFromDB[i]].columns[j];
                if (dbCol.COLUMN_NAME.toLowerCase() === col.expr.column.toLowerCase()) {
                    return dbCol;
                }
            }
        }
    }
}
// For SELECT ColumnName FROM ....
getAColumnFromAllTables (allTableAllColumns, col) {
    const TablesFromDB = Object.keys(allTableAllColumns);
    for ( let i = 0 ; i < TablesFromDB.length; i++ ) {
        for (let j = 0; j < allTableAllColumns[TablesFromDB[i]].columns.length; j++) {
            const dbCol = allTableAllColumns[TablesFromDB[i]].columns[j];
            if (dbCol.COLUMN_NAME.toLowerCase() === col.expr.column.toLowerCase()) {
                return dbCol;
            }
        }
    }
}
// Gets all columns of tables from database
async getAllColumnsOfTablesFromDB(tableNames) {
    const allTableAllColumns = {};

    var query = `
        SELECT 
            TABLE_NAME,
            COLUMN_NAME,
            DATA_TYPE,
            CHARACTER_MAXIMUM_LENGTH,
            NUMERIC_PRECISION,
            NUMERIC_SCALE
        FROM 
            INFORMATION_SCHEMA.COLUMNS
        WHERE 
            TABLE_NAME IN (:tableNames)
            AND TABLE_SCHEMA = :DB_NAME;        
    `;

    await db.sequelize.query(query,  {
        replacements: {
            tableNames: tableNames,
            DB_NAME: this.dbConfig.database
        }
    }).spread((results, metadata) => {
        results.map(row => {
            if (allTableAllColumns[row.TABLE_NAME] && allTableAllColumns[row.TABLE_NAME].columns) {
                allTableAllColumns[row.TABLE_NAME].columns.push(row)
            } else {
                allTableAllColumns[row.TABLE_NAME] = {};
                allTableAllColumns[row.TABLE_NAME].columns = [row]
            }
        });
    });

    return allTableAllColumns;
}

async getColumnNames(ast) {
    let columns = [];
    let tableAlias = {};
    let tableNames = ast.from.map( t => t.table );
    let allTableAllColumns = await this.getAllColumnsOfTablesFromDB(tableNames);

    ast.from.forEach (table => {
        if (table.as) {
            tableAlias[table.as] = table.table
        } else {
            tableAlias[table.table] = table.table
        }
    });

    ast.columns.forEach(col => {
        if (col.expr.type === 'column_ref') {
            const table = col.expr.table;
            const column = col.expr.column === '*' ? '*' : `${table}.${col.expr.column}`;

            if (!col.expr.table && col.expr.column === '*') {
                // For SELECT * FROM ....
                return columns.push(...this.getAllColumnsFromAllTables(allTableAllColumns));
            } else if (col.expr.table && column === '*') {
                // For SELECT TableName.* FROM ....
                return columns.push(...this.getAllColumnsFromATable (allTableAllColumns, tableAlias));
            } else if (col.expr.table && col.expr.column) {
                // For SELECT TableName.ColumnName FROM ....
                return columns.push(this.getAColumnFromATable(allTableAllColumns, tableAlias, col));
            } else if (!col.expr.table && col.expr.column) {
                // For SELECT ColumnName FROM ....
                return columns.push(this.getAColumnFromAllTables(allTableAllColumns, col));
            }
        }
    });

    return columns;
}

async getColumnsFromQuery( query ) {
    try {
        await db.sequelize.query ( query ); // to check if query works
        const parser = new Parser();

        const pasredQuery = parser.astify(query);
        let columns = [];

        if (Array.isArray(pasredQuery)) {
            // only giving columns for 1st query if the result of parsed queries results in multiple queries
            columns = await this.getColumnNames( pasredQuery[0] );
        } else {
            columns = await this.getColumnNames( pasredQuery );
        }

        return response.SendResponse(res, columns, 0, "List of Columns");

    } catch ( err ) {
        return response.SendResponse(res, "An Error occoured", 1, err);
    }



}

Пример теста приведенного выше кода:

let query = `
    SELECT * FROM Customer INNER JOIN database.Order ON database.Order.CustomerId = Customer.Id;
`
getColumnsFromQuery ( query );

Возврат:

[{
            "TABLE_NAME": "Customer",
            "COLUMN_NAME": "Id",
            "DATA_TYPE": "int",
            "CHARACTER_MAXIMUM_LENGTH": null,
            "NUMERIC_PRECISION": 10,
            "NUMERIC_SCALE": 0
        },
        {
            "TABLE_NAME": "Customer",
            "COLUMN_NAME": "Name",
            "DATA_TYPE": "varchar",
            "CHARACTER_MAXIMUM_LENGTH": 10,
            "NUMERIC_PRECISION": null,
            "NUMERIC_SCALE": null
        },
        {
            "TABLE_NAME": "Customer",
            "COLUMN_NAME": "Age",
            "DATA_TYPE": "int",
            "CHARACTER_MAXIMUM_LENGTH": 10,
            "NUMERIC_PRECISION": 10,
            "NUMERIC_SCALE": 0
        },
        .
        .
        .
        {
            "TABLE_NAME": "Order",
            "COLUMN_NAME": "OrderName",
            "DATA_TYPE": "varchar",
            "CHARACTER_MAXIMUM_LENGTH": 10,
            "NUMERIC_PRECISION": null,
            "NUMERIC_SCALE": null
        },
        {
            "TABLE_NAME": "Order",
            "COLUMN_NAME": "OrderDate",
            "DATA_TYPE": "datetime",
            "CHARACTER_MAXIMUM_LENGTH": null,
            "NUMERIC_PRECISION": null,
            "NUMERIC_SCALE": null
        },
        {
            "TABLE_NAME": "Customer",
            "COLUMN_NAME": "Age",
            "DATA_TYPE": "int",
            "CHARACTER_MAXIMUM_LENGTH": 10,
            "NUMERIC_PRECISION": 10,
            "NUMERIC_SCALE": 0
        },
        .
        .
        .
]

Вы можете изменить запрос в getAllColumnsOfTablesFromDB, чтобы получить любые данные обо всех столбцах.

Я тестировал его со многими запросами, включая запросы, которые используют псевдонимы для имен таблиц, а также для столбцов.

Этот код, вероятно, можно подвергнуть значительному рефакторингу, но это был максимум, который я мог сделать, поскольку у меня недостаточно опыта, чтобы идти дальше.

Другие вопросы по теме