我正在使用 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
但它们仅适用于表而不是查询。
我想我能够找到一种从查询中获取所有列名称的方法。
我使用了
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);
}
}
我用许多查询测试了它,包括使用表名和列别名的查询。
这段代码可能可以进一步重构,但这是我能做的最大的事情,因为我没有足够的经验来进一步重构。
希望这可以帮助其他面临同样问题的人。