Please note that when I using the word "schema" I mean Schema as the security feature provided by Sql Server ("dbo"...) that you must provide with the table name if you want your query to succeed, NOT its metadata (columns...).
I'm using this bit of code to get the table metadata (columns, types, etc) :
// Parameter table includes the schema name.
public DataTable GetTableSchema(string table)
{
var tbl = new DataTable();
using (var conn = new SqlConnection(ConnectionString))
using (var adapter = new SqlDataAdapter(String.Format("SELECT * FROM {0} WHERE 1=0", table), conn))
{
tbl = adapter.FillSchema(tbl, SchemaType.Source);
}
return tbl;
}
My issue is that the property DataTable.TableName doesn't contain the table schema ("dbo", or any custom schema) and I can't find any property in the object that allows me to get that information, so it is lost during the process (or I have to pass several variables to methods, while I'd like to keep everything in the DataTable object, which should be logical).
Where / how can I get it along with the database structure ?
Only solution I found : adding tbl.TableName = table; before returning the table but it feels... wrong.
You could query the INFORMATION_SCHEMA.TABLES view and get your info from the TABLE_SCHEMA field
"SELECT TABLE_SCHEMA FROM INFORMATION_SCHEMA.TABLES where table_name = '" + table "'";
It is not clear why you need this info, so I can only suggest to run this code
SqlCommand cmd = new SqlCommand(#"SELECT TABLE_SCHEMA
FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_NAME = #table", con);
cmd.Parameters.Add("#table", SqlDbType.NVarChar).Value = table;
string schema = (string)cmd.ExecuteScalar();
There is also an alternative, that could return some of the info on your table and the table_schema in a single call
DataTable dt = new DataTable();
dt = cnn.GetSchema("COLUMNS", new string[] { null, null, table, null });
Console.WriteLine(dt.Rows[0]["TABLE_SCHEMA"].ToString());
This code returns a lot of info about your table and also the TABLE_SCHEMA colum.
Not sure if this approach is suitable for your purpose because here the datatable returned contains a row for each column and each column of this DataTable contains details about the columns (Like OrdinalPosition, IsNullable, DataType, Character_Maximum_Length)
Related
System.Data.SqlClient.SqlException: 'Ambiguous column name
'SchoolID'.'
I need to select a SchoolName == SchoolID and an AcademicYear from two combo-boxes that are found in two database tables School-Info and School_AcademicYear
Also SchoolID in School_AcademicYear is Foreign Key and its Primary Key in School_Info, I am using inner join to join these two tables but an error is occuring
Ambiguous column name 'SchoolID'
con.Open();
adp = new SqlDataAdapter("SELECT AcademicYearID,AcademicYear,SchoolID FROM School_AcademicYear INNER JOIN School_Info ON School_AcademicYear.AcademicYearID = School_Info.SchoolID where School_AcademicYear.AcademicYearID = '" + AcademicYearID + "'", con);
dt = new DataTable();
adp.Fill(dt);
dataGridViewSchoolNMergeAcYear.DataSource = dt;
con.Close();
If you join two tables that contain columns with the same name and you refer to one of these columns in the SELECT list, then you need to specify from which table are you getting the values. So to solve this problem let's start using some alias for the table names. Using the alias in front of the column's name correctly identify the columns source table.
While at it, I have also changed your string concatenation to a parameterized query. It is a lot better because it avoids parsing errors and a well known security problem called Sql Injection
using(SqlConnection con = new SqlConnection(.......))
{
string cmdText = #"SELECT a.AcademicYearID,a.AcademicYear,i.SchoolID
FROM School_AcademicYear a INNER JOIN School_Info i
ON a.AcademicYearID = i.SchoolID
WHERE a.AcademicYearID = #id";
con.Open();
adp = new SqlDataAdapter(cmdText, con);
adp.SelectCommand.Parameters.Add("#id", SqlDbType.Int).Value = AcademicYearID;
dt = new DataTable();
adp.Fill(dt);
dataGridViewSchoolNMergeAcYear.DataSource = dt;
}
To be complete this answer introduces also the using statement around the disposable connection object. In this way the connection is closed and disposed when the code exits the using block. Note that I suppose that AcademicYearID is a number and not a string so, the parameter is of type SqlDbType.Int instead of NVarChar.
You have multiple columns in those tables with name SchoolID.
You have to specify the column name, because sql cannot know which one you want. Example: School_Info.SchoolID
adp = new SqlDataAdapter(`
SELECT AcademicYearID,AcademicYear,School_Info.SchoolID
FROM School_AcademicYear
INNER JOIN School_Info ON School_AcademicYear.AcademicYearID = School_Info.SchoolID
where School_AcademicYear.AcademicYearID = '` + AcademicYearID + "'", con);
I have this code in C#, but I need it to select all columns EXCEPT the first column of the table (the identity column), so that when I insert the data into an identical table in a different database, the destination database assigns its own identity column values:
SqlCommand commandSourceData = new SqlCommand($"SELECT * FROM dbo.{tableName};", sourceConnection);
SqlDataReader reader = commandSourceData.ExecuteReader();
Is there a way to do this?
If you want a generic solution for every column in your database you can use this kind of code
public string GetColumnsWithoutIdentity(string tableName, SqlConnection con)
{
SqlDataAdapter da = new SqlDataAdapter($"SELECT * FROM dbo.{tableName} where 1=0", con);
DataTable dt = new DataTable();
da.FillSchema(dt, SchemaType.Source);
var cols = dt.Columns.Cast<DataColumn>().Where(x => !x.AutoIncrement).Select(x => x.ColumnName);
return string.Join(",", cols);
}
Now you can use the returned string to build an Sql statement without the autoincrement column.
Notice that this code is vulnerable to Sql Injection. You should be absolutely sure that the tableName parameter used to build the first query is not typed directly by your user. Let it choose from a whitelist (readonly) of predefined tables (and also this is not 100% safe)
Another drawback is the fact that you need to hit the database two times. Once to get the schema with the info about the AutoIncrement column and one to fill the datatable after that.
I would like to know is there a way to remove a specific column when displaying in a datagridview. Below is the sql statemnt to retrieve
OleDbDataAdapter oda = new OleDbDataAdapter("select * from BHR_2016_FEB_CIT4114_FYP_GD", con);
DataTable dt = new DataTable();
oda.Fill(dt);
dt.Columns.Remove("Fingerprint_Template");
dataGridViewAttendanceDatabase.DataSource = dt;
The reasons is because, i have a type of format CLOB, therefore it will not display in datagridview. So i plan remove a column which stores the CLOB format. I can specify, but the problem i will be selecting table based on combo box and each table have different number of columns such as a table might have 31 column, another table might have 28 column. So how i could remove only a single column. Thanks in advanced.
There is still an error as when i state select *, it includes the column which consist of CLOB format. Therefore error exist at oda.Fill(dt);. Is there a way to select * except a columns which consist of CLOB format. This help is really appreciated.
you can just do this
dt.Columns.Remove("xyz");
Update
It seems Fill does not support CLOB data. Above solution will not work.You need to do change in SQL itself. My suggestion is to move everything in stored procedure and use meta data to extract column which you want (or exclude column which you do not want.) Here is sample code. Please note I have not tested it so you may find some minor issue but the code gives you gist of what you can do to solve your problem
C# Code
OleDbConnection oc= new OleDbConnection("[pass your connection string]");
OleDbCommand ocom = new OleDbCommand();
ocom.CommandText = "Abc"; // Abc is stored procedure
ocom.Connection = oc;
ocom.CommandType = CommandType.StoreProcedure;
ocom.Parameters.AddWithValue("#tableName","PQR") // pass your table name
ocom.Parameters.AddWithValue("#databaseName","IJK"); // pass your database name
OleDbDataAdapter oda = new OleDbDataAdapter(ocom);
DataTable dt = new DataTable();
oda.Fill(dt);
dataGridViewAttendanceDatabase.DataSource = dt;
SQL Script
Create Porocedure Abc (#tableName varchar(255),#databaseName varchar(255))
Begin
SET #sql = CONCAT('SELECT ', (SELECT REPLACE(GROUP_CONCAT(COLUMN_NAME), 'Fingerprint_Template,', '') FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = #tableName AND TABLE_SCHEMA = #databaseName), CONCAT(' FROM ',#tableName));
PREPARE stmt FROM #sql;
EXECUTE stmt;
End
I'm wanting to get a list of the column names returned from a SQL SELECT statement. Can someone suggest an easy way to do this?
I have a tool that lets users define a query using any SQL SELECT statement. The results of the query are then presented in a custom manner. To set up the presentation, I need to know the column names so that the user can store formatting settings about each column.
Btw, the formatting settings are all being created via ASP.NET web pages, so the query results will end up in .NET if that helps with any ideas people have.
Any ideas?
You should be able to do this using the GetName method. Something like this probably:
SqlDataReader mySDR = cmd.ExecuteReader();
for(int i = 0;i < mySDR.FieldCount; i++)
{
Console.WriteLine(mySDR.GetName(i));
}
This is something you could do entirely from a asp.net page. No special/extra SQL required.
Assuming SQL Server: You could use SET FMTONLY to just return metadata (and not the actual data), e.g.:
USE AdventureWorks2008R2;
GO
SET FMTONLY ON;
GO
SELECT *
FROM HumanResources.Employee;
GO
SET FMTONLY OFF;
GO
You can get by something as following
Note : You need to fill the DataTable of the Dataset.........
DataSet1 DataSet1 = new DataSet1();
DataTable dt = DataSet1.Tables(0);
DataColumn dc = null;
foreach (DataColumn dc_loopVariable in dt.Columns) {
dc = dc_loopVariable;
Response.write(dc.ColumnName.ToString() + " " + dc.DataType.ToString() + "<br>");
}
Another method to just return meta data is
select top 0 * from table
If you know the table name you could try using:
desc <table_name>
I'm assuming you are using SQL Server.
Or as an alternative:
SELECT COLUMN_NAME
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = 'TableNameGoesHere'
ORDER BY ORDINAL_POSITION
You might want to use the second option if you are going to be using ASP.NET
This will get you more than the column name if you need more information about each column like size, ordinal,etc. A few of the most important properties are listed, but there are more.
Note, DataObjects.Column is a POCO for storing column information. You can roll your own in your code. Also, note I derive the .Net type as well, useful for converting SQL data types to .Net (C#) ones. ConnectionString and TableName would be supplied from a caller.
using (SqlConnection conn = new SqlConnection(ConnectionString))
{
conn.Open();
SqlCommand comm = new SqlCommand("Select top(1) * from " + TableName + " Where 1=0");
comm.CommandType = CommandType.Text;
comm.Connection = conn;
using (SqlDataReader reader = comm.ExecuteReader(CommandBehavior.KeyInfo))
{
DataTable dt = reader.GetSchemaTable();
foreach (DataRow row in dt.Rows)
{
//Create a column
DataObjects.Column column = new DataObjects.Column();
column.ColumnName = (string)row["ColumnName"];
column.ColumnOrdinal = (int)row["ColumnOrdinal"];
column.ColumnSize = (int)row["ColumnSize"];
column.IsIdentity = (bool)row["IsIdentity"];
column.IsUnique = (bool)row["IsUnique"];
//Get the C# type of data
object obj = row["DataType"];
Type runtimeType = obj.GetType();
System.Reflection.PropertyInfo propInfo = runtimeType.GetProperty("UnderlyingSystemType");
column.type = (Type)propInfo.GetValue(obj, null);
//Set a string so we can serialize properly later on
column.DataTypeFullName = column.type.FullName;
//I believe this is SQL Server Data Type
column.SQLServerDataTypeName = (string)row["DataTypeName"];
//Do something with the column
}
}
}
I read an excel sheet into a datagrid.From there , I have managed to read the grid's rows into a DataTable object.The DataTable object has data because when I make equal a grid's datasource to that table object , the grid is populated.
My Problem : I want to use the table object and manipulate its values using SQL server,(i.e. I want to store it as a temporary table and manipulate it using SQL queries from within C# code and , I want it to return a different result inte a grid.(I don't know how to work with temporary tables in C#)
Here's code to execute when clicking button....
SqlConnection conn = new SqlConnection("server = localhost;integrated security = SSPI");
//is connection string incorrect?
SqlCommand cmd = new SqlCommand();
//!!The method ConvertFPSheetDataTable Returns a DataTable object//
cmd.Parameters.AddWithValue("#table",ConvertFPSheetDataTable(12,false,fpSpread2_Sheet1));
//I am trying to create temporary table
//Here , I do a query
cmd.CommandText = "Select col1,col2,SUM(col7) From #table group by col1,col2 Drop #table";
SqlDataAdapter da = new SqlDataAdapter(cmd.CommandText,conn);
DataTable dt = new DataTable();
da.Fill(dt); ***// I get an error here 'Invalid object name '#table'.'***
fpDataSet_Sheet1.DataSource = dt;
//**NOTE:** fpDataSet_Sheet1 is the grid control
Change your temp table from #table to ##table in both places.
Using ## means a global temp table that stays around. You'll need to Drop it after you have completed your task.
Command = " Drop Table ##table"
Putting the data into a database will take time - since you already have it in memory, perhaps LINQ-to-Objects (with DataSetExtensions) is your friend? Replace <int> etc with the correct types...
var query = from row in table.Rows.Cast<DataRow>()
group row by new
{
Col1 = row.Field<int>(1),
Col2 = row.Field<int>(2)
} into grp
select new
{
Col1 = grp.Key.Col1,
Col2 = grp.Key.Col2,
SumCol7 = grp.Sum(x => x.Field<int>(7))
};
foreach (var item in query)
{
Console.WriteLine("{0},{1}: {2}",
item.Col1, item.Col2, item.SumCol7);
}
I don't think you can make a temp table in SQL the way you are thinking, since it only exists within the scope of the query/stored procedure that creates it.
If the spreadsheet is a standard format - meaning you know the columns and they are always the same, you would want to create a Table in SQL to put this file into. There is a very fast way to do this called SqlBulkCopy
// Load the reports in bulk
SqlBulkCopy bulkCopy = new SqlBulkCopy(connectionString);
// Map the columns
foreach(DataColumn col in dataTable.Columns)
bulkCopy.ColumnMappings.Add(col.ColumnName, col.ColumnName);
bulkCopy.DestinationTableName = "SQLTempTable";
bulkCopy.WriteToServer(dataTable);
But, if I'm understanding your problem correctly, you don't need to use SQL server to modify the data in the DataTable. You c an use the JET engine to grab the data for you.
// For CSV
connStr = string.Format("Provider=Microsoft.JET.OLEDB.4.0;Data Source={0};Extended Properties='Text;HDR=Yes;FMT=Delimited;IMEX=1'", Folder);
cmdStr = string.Format("SELECT * FROM [{0}]", FileName);
// For XLS
connStr = string.Format("Provider=Microsoft.JET.OLEDB.4.0;Data Source={0}{1};Extended Properties='Excel 8.0;HDR=Yes;IMEX=1'", Folder, FileName);
cmdStr = "select * from [Sheet1$]";
OleDbConnection oConn = new OleDbConnection(connStr);
OleDbCommand cmd = new OleDbCommand(cmdStr, oConn);
OleDbDataAdapter da = new OleDbDataAdapter(cmd);
oConn.Open();
da.Fill(dataTable);
oConn.Close();
Also, in your code you ask if your connection string is correct. I don't think it is (but I could be wrong). If yours isn't working try this.
connectionString="Data Source=localhost\<instance>;database=<yourDataBase>;Integrated Security=SSPI" providerName="System.Data.SqlClient"
Pardon me, if I have not understood what you exactly want.
If you want to perform SQL query on excel sheet, you could do it directly.
Alternatively, you can use SQL Server to query excel (OPENROWSET or a function which I dont remember right away). Using this, you can join a sql server table with excel sheet
Marc's suggestion is one more way to look at it.
Perhaps you could use a DataView. You create that from a DataTable, which you already have.
dv = new DataView(dataTableName);
Then, you can filter (apply a SQL WHERE clause) or sort the data using the DataView's methods. You can also use Find to find a matching row, or FindRows to find all matching rows.
Some filters:
dv.RowFilter = "Country = 'USA'";
dv.RowFilter = "EmployeeID >5 AND Birthdate < #1/31/82#"
dv.RowFilter = "Description LIKE '*product*'"
dv.RowFilter = "employeeID IN (2,4,5)"
Sorting:
dv.Sort = "City"
Finding a row: Find the customer named "John Smith".
vals(0)= "John"
vals(1) = "Smith"
i = dv.Find(vals)
where i is the index of the row containing the customer.
Once you've applied these to the DataView, you can bind your grid to the DataView.
Change the command text from
Select col1,col2,SUM(col7) From #table group by col1,col2
to
Select col1,col2,SUM(col7) From ##table group by col1,col2