Я использую 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, но они предназначены только для таблиц, а не для запросов.
Попробуйте использовать console.info(queryResponse[1]), чтобы увидеть, есть ли там имена столбцов.
@Barmar queryResponse — это массив, содержащий строки, сгенерированные в результате выполнения запроса, переданного функции. Я попробовал использовать console.info(queryResponse); и получил { id: 53, name: "John Doe", Gender: "M", ... } Спасибо за ответ!
В документации сказано, что это массив [results, metadata]. results — это массив строк, возвращаемых запросом. metadata — дополнительная информация.
Результат, который вы показываете, выглядит как queryResponse[0][0].
На самом деле, возможно, использование type: db.sequelize.QueryTypes.SELECT позволяет исключить метаданные и просто вернуть результаты. Попробуйте без этого.
@Barmar Я пробовал использовать type: db.sequelize.QueryTypes.SELECT и без него, в обоих случаях я получаю 2 нулевых массива. (2) [Array(0), Array(0)]





Думаю, мне удалось найти способ получить все имена столбцов из запроса, включая типы данных столбцов.
Я использовал 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, чтобы получить любые данные обо всех столбцах.
Я тестировал его со многими запросами, включая запросы, которые используют псевдонимы для имен таблиц, а также для столбцов.
Этот код, вероятно, можно подвергнуть значительному рефакторингу, но это был максимум, который я мог сделать, поскольку у меня недостаточно опыта, чтобы идти дальше.
queryResponse[1]— метаданные запроса. Я ожидаю, что оно где-то там.