如何从不会返回任何行的查询中获取列名称列表

问题描述 投票:0回答:1

我正在使用 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.js express sequelize.js mysql-connector
1个回答
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);
    }



}

我用许多查询测试了它,包括使用表名和列别名的查询。

这段代码可能可以进一步重构,但这是我能做的最大的事情,因为我没有足够的经验来进一步重构。

希望这可以帮助其他面临同样问题的人。

© www.soinside.com 2019 - 2024. All rights reserved.