I'm trying to write a windows forms app in C# .Net 4 it connects to a SQL Server 2008 database and I want to Select highest number from a table where the number is stored as string!
string SQL = "select MAX(CONVERT(int, myField)) from myTable where myCode = '" + theCust + "'";
I have also tried Max(CAST(myField as Int)) in the select statement but both fail to return anything even though the Database has for the theCust two rows with 10001 and 10002. The Error i Get is "Enumeration yielded no results"
What am I doing wrong?
I'm using the in built System.Data.SqlClient and if I just do a
string SQL = "select myField from myTable where myCode = '" + theCust + "'";
it returns both numbers as strings. I know I could sort them in code but if the Database gets large that would not be a good approach!
I just tried it again with an int Field in the db and still got the same error! Is Max the wrong thing to be using?
You can try it like this:
SELECT TOP 1 CAST(MyColumn AS int) AS TheMax
FROM MyTable
ORDER BY TheMax DESC
So (using the sloppy method, always paramaterize!)
String sql = "SELECT TOP 1 CAST(MyColumn AS int) AS TheMax FROM MyTable WHERE MyParam = '" + param + "' ORDER BY TheMax Desc";
//Fill DataAdapter/DataReader etc.
Have this function in your database(s):
CREATE FUNCTION dbo.IsAllDigits (#MyString VARCHAR(8000))
RETURNS TABLE AS
RETURN (
SELECT CASE
WHEN #MyString NOT LIKE '%[^0-9]%'
THEN 1
ELSE 0
END AS IsAllDigits
)
because it's better than the in-build ISNUMERIC() in T-SQL.
Then you can use this query to get set of strings that convert to integer types without errors, and filter them like with TOP 1.
SELECT TOP 1 MyColumn AS TheMax
FROM MyTable
WHERE IsAllDigits(MyColumn)=1
ORDER BY MyColumn DESC
Related
I am trying to get SQL to list not found when a machine name lookup is not in the database. I have the query working but I need to integrate it into C# where I have other queries. As it stands it is a lot more convoluted than I want it to be, I am guessing there is a much more intelligent and concise why of having the SQL achieve the same result.
DECLARE #myHostCount as INT
DECLARE #myHostName1 as Varchar(50);
DECLARE #myHostName2 as Varchar(50) = 'Machine220054';
DECLARE #myHostCount1 as INT
DECLARE #myHostName3 as Varchar(50);
DECLARE #myHostName4 as Varchar(50) = 'Machine22054';
SET #myHostCount1 = (SELECT COUNT(*) FROM db_owner.host WHERE name LIKE '%Machine22054%')
SET #myHostName3 = (SELECT Name FROM db_owner.host WHERE name LIKE '%Machine22054%')
SET #myHostCount = (SELECT COUNT(*) FROM db_owner.host WHERE name LIKE '%Machine220054%')
SET #myHostName1 = (SELECT Name FROM db_owner.host WHERE name LIKE '%Machine220054%')
SELECT
CASE
WHEN #myHostCount = 1 THEN 'Found'
WHEN #myHostCount = 0 THEN 'Not Found'
END AS 'Result',
#myHostName2 AS 'HostName'
UNION
SELECT
CASE
WHEN #myHostCount1 = 1 THEN 'Found'
WHEN #myHostCount1 = 0 THEN 'Not Found'
END AS 'Result',
#myHostName4 AS 'HostName'
Output:
Result Machine Name
--------------------------
Found Machine220054
Not Found Machine22054
You can try this:
DECLARE #myHostName2 as Varchar(50) = 'Machine220054';
DECLARE #myHostName4 as Varchar(50) = 'Machine22054';
DECLARE #hostName TABLE (
Name VARCHAR(255)
);
INSERT INTO #hostName (Name)
VALUES (#myHostName2), (#myHostName4)
SELECT HostName = HostName.name,
Result = IIF(COUNT(*) = 0, 'Not Found', 'Found')
FROM #hostName AS HostName
LEFT JOIN db_owner.host ON host.name LIKE '%' + HostName.Name + '%'
GROUP BY HostName.name;
First: Try to remove as much procedural logic in SQL as possible.
Second: Try to minimize the use of "Like" queries if at all possible. It will hinder indexing and cause bottlenecks eventually, or, at least limit it to ending with a "%" instead of starting it with one.
Third: you are assuming that there will only be zero or one hit in your code. What if (given either non-unique or like based queries) there are more than one?
I'd go for something like this:
declare #looking table (name varchar(50))
insert into #looking (name) values ('Machine220054'),('Machine22054')
select case when t.name is null then 'Not Found' else 'Found' end as [FoundOrNot], t.name as [FoundName],l.name as [SearchName]
from #looking as l
left outer join db_owner.host as t
on t.name like '%' + l.name + '%'
UPDATE: Tested against a random table, removed "group by" on account of either one or no hit expected.
Just to provide an update, I ended up taking the results from the DR reader and entering them into a datatable, since the dr reader can only be read once. Then I compared the selections from the listbox with the contents from the datatable then added the difference to another datatable then displayed the results in a datagrid, this produced the desired outcome of results found, being listed and the objects not found being listed as not found.
I have thought about using distinct but im not too sure how to do it as a single query for efficiency of code, is there a way? I am basically trying to check if there is already an existing data entry, I am trying to check it with BookingTime. Thanks :)
This is my SQL query:
string bookingInfo = "INSERT INTO Booking(BookingDate, BookingTime, CustomerID, EmployeeID, ServiceType, BookingLength) " +
"VALUES (#BookingDate, #BookingTime, #CustomerID, #EmployeeID, #ServiceType, #BookingLength) " +
"where not exists (SELECT 1 FROM Booking WHERE BookingTime = #BookingTime)";
The error I receive: "Additional information: Query input must contain at least one table or query."
The best way is to let the database do the checking.
Create a unique index or constraint on the table:
create unique index unq_booking_bookingtime on booking(bookingtime);
Note: this is based on your query. It seems unlikely to me that only bookingtime defines uniqueness.
The database will then generate an error if it encounters duplicates. You can prevent the error using insert ignore or insert on duplicate key update (the latter is the preferred method).
The WHERE clause is invalid is invalid in the "INSERT ... VALUES" statement shown.
MySQL does provide an "INSERT ... SELECT ..." form of the INSERT statement (which does not use a VALUES clause). The SELECT statement can have a WHERE clause, but to include a WHERE clause in a SELECT, there has to be a FROM clause. You can use an inline view (aka a derived table) to return a single row, or you could use the builtin Oracle-style dummy table DUAL to return a single row. (We don't care what columns get returned, we just need one row returned.)
For example:
INSERT INTO Booking
( BookingDate
, BookingTime
, CustomerID
, EmployeeID
, ServiceType
, BookingLength
)
SELECT #BookingDate
, #BookingTime
, #CustomerID
, #EmployeeID
, #ServiceType
, #BookingLength
FROM ( SELECT 1 ) i
WHERE NOT EXISTS
( SELECT 1
FROM Booking b
WHERE b.BookingTime = #BookingTime
)
Now the SELECT statement (which can be tested separately from the INSERT) will return either zero or one rows. And whatever row it returns will be passed to the INSERT.
As an alternative to the "NOT EXISTS" predicate, you could use an anti-join pattern:
SELECT #BookingDate
, #BookingTime
, #CustomerID
, #EmployeeID
, #ServiceType
, #BookingLength
FROM ( SELECT 1 ) i
LEFT
JOIN Booking b
ON b.BookingTime = #BookingTime
WHERE b.BookingTime IS NULL
string bookingInfo = "INSERT INTO Booking(BookingDate, BookingTime, CustomerID, EmployeeID, ServiceType, BookingLength) " +
"SELECT #BookingDate, #BookingTime, #CustomerID, #EmployeeID, #ServiceType, #BookingLength " +
" FROM Booking WHERE NOT EXISTS (SELECT * FROM Booking WHERE BookingTime = #BookingTime) LIMIT 1 "
Try this SqlFiddle
I need to select all the columns names only from a SPLANNING_ROOMDATA2 table in such way that those names should not exist in a comma separated list of column names in another table. Here is my query in a C# ASP.NET application. My query generates an error like 'syntax error near Where'.
SqlCommand cmd = new SqlCommand("select COLUMN_NAME from INFORMATION_SCHEMA.COLUMNS where TABLE_NAME='Splanning_RoomData2' where COLUMN_NAME NOT IN (SELECT ATTRIBUTENAME FROM SPLANNING_RESTRICTED_ATTRIBUTES)", con);//get only column names
Note, if I remove the second Where clause then I do get a list of columns but that is not what I desire. I have done some searches but none met my needs.
***Edit: As of now, none of the answers are working. I am going to try to change the splanning_restricted_attributes table's to allow one restricted attributed per row--instead of comma separated one. Thanks all. **
You should not store lists as comma separated values. They should be in a separate table, called a junction table with one row per column and table.
Sometimes, though, you are stuck with such a structure. Here is one method for getting what you need:
select c.COLUMN_NAME
from INFORMATION_SCHEMA.COLUMNS c LEFT JOIN
SPLANNING_RESTRICTED_ATTRIBUTES ra
ON ',' + lower(ra.ATTRIBUTENAME) + ',' LIKE '%,' + lower(c.column_name) + ',%'
where TABLE_NAME = 'Splanning_RoomData2' and ra.ATTRIBUTENAME is null;
Use AND instead of WHERE twice
SqlCommand cmd = new SqlCommand("SELECT COLUMN_NAME
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME='Splanning_RoomData2'
AND COLUMN_NAME NOT IN (SELECT ATTRIBUTENAME
FROM SPLANNING_RESTRICTED_ATTRIBUTES)", con);
I am just posting the updated answer of #Gordon Linoff answer try the query as,
select c.COLUMN_NAME
from INFORMATION_SCHEMA.COLUMNS c JOIN
SPLANNING_RESTRICTED_ATTRIBUTES ra
ON ',' + lower(ra.ATTRIBUTENAME) + ',' NOT LIKE '%,' + lower(c.column_name) + ',%'
where c.TABLE_NAME = 'Splanning_RoomData2';
I want to get the count of rows of a table.
cmd.CommandText = "SELECT COUNT(*) FROM (SELECT * FROM (SELECT * FROM EMPLOYEE ))";
Records = int.Parse(cmd.ExecuteScalar().ToString());
even if there is 1 row in employee table, Records always have value 0.
why ExecuteScalar() returns 0?
What you are trying to calculate is the number of rows in the EMPLOYEE table, therefore Pranav and Vinod are right. But assuming you try to simplify your situation just to point directly to your problem, I would say that you need to give names to your temporary tables like this:
cmd.CommandText = "SELECT COUNT(*) FROM (SELECT * FROM (SELECT * FROM EMPLOYEE ) as T1) as T2";
Records = int.Parse(cmd.ExecuteScalar().ToString());
That should do the trick.
One additional suggestion is not to use count(0); instead use count(0). '*' brings all data in the view; which is an unnecessary performance decrement for count function.
It returns 0 because your SQL request isn't valid.
You should always try your requests directly against your database (for instance using SQL Server Management Studio if you are using SQL Server) before writing them in your code. You would have seen it doesn't return anything because it has a syntax error.
If you want to use nested SELECT * FROM as you wrote, here's the valid SQL request that will return what you expected:
SELECT COUNT(*) FROM (SELECT * FROM (SELECT * FROM EMPLOYEE ) AS Foo1 ) AS Foo2
In any case, as all other answers have noted, this doesn't make much sense, and must be replaced by:
SELECT COUNT(*) FROM EMPLOYEE
Try this:
cmd.CommandText = "SELECT COUNT(*) FROM EMPLOYEE";
int Records = (int)cmd.ExecuteScalar();
That should return the number of rows in the EMPLOYEE table.
To double check, use the SQL management studio to execute the same code against your SQL server and see what the result is. Is it possible that your application is configured to point a different database to the one that you are viewing?
Just say:
cmd.CommandText = "SELECT COUNT(*) FROM EMPLOYEE";
Records = int.Parse(cmd.ExecuteScalar().ToString());
Try this:
cmd.CommandText = "SELECT COUNT(*) FROM EMPLOYEE"
Int record=(Int32)cmd.Executescalar();
I would like to know something .
I try to retrieve 2 files .
One is register for a group 2 , and one for a group 10 .
So the field is Files.Group .
One user is register to the group 1 and the group 10.
This is the query I use to retrieve files .
SELECT Files.Id, Files.Name, Files.Date, Files.Path, Files.[Group] FROM Files WHERE Files.[Group] = " + param + "ORDER BY Files.Id DESC"
Param is a cookie who get the group, creating a chain like this 2|10 .
This doesn't work actually.. And i don't know how can I pass in the query the two groups. Should I separate them by a coma ? like Files.Group = 2,10 ?
Or is it something else ? To pass 2 parameters ?
Baseline Structure
I don't have your entire structure so I have created the following simplified version of it:
CREATE TABLE [dbo].[Files]
(
[ID] INT IDENTITY(1,1) NOT NULL PRIMARY KEY,
[Name] NVARCHAR(64) NOT NULL,
[Group] INT NOT NULL -- Probably have a non-unique index over this.
);
GO
INSERT INTO [dbo].[Files] ([Name], [Group]) VALUES (N'My File 1', 1);
INSERT INTO [dbo].[Files] ([Name], [Group]) VALUES (N'My File 2', 2);
INSERT INTO [dbo].[Files] ([Name], [Group]) VALUES (N'My File 3', 3);
INSERT INTO [dbo].[Files] ([Name], [Group]) VALUES (N'My File 4', 2);
INSERT INTO [dbo].[Files] ([Name], [Group]) VALUES (N'My File 5', 3);
INSERT INTO [dbo].[Files] ([Name], [Group]) VALUES (N'My File 6', 5);
Temp Table
You can insert the split values into a temp table and use a WHERE EXISTS against it - probably yielding decent performance.
-- This would be passed in from C#.
DECLARE #GroupsParam NVARCHAR(64) = N'2|3';
-- This is your SQL command, possibly a SPROC.
DECLARE #GroupsXML XML = N'<split><s>' + REPLACE(#GroupsParam, N'|', N'</s><s>') + '</s></split>';
-- Create an in-memory temp table to hold the temp data.
DECLARE #Groups TABLE
(
[ID] INT PRIMARY KEY
);
-- Insert the records into the temp table.
INSERT INTO #Groups ([ID])
SELECT x.value('.', 'INT')
FROM #GroupsXML.nodes('/split/s') as records(x);
-- Use a WHERE EXISTS; which should have extremely good performance.
SELECT [F].[Name], [F].[Group] FROM [dbo].[Files] AS [F]
WHERE EXISTS (SELECT 1 FROM #Groups AS [G] WHERE [G].[ID] = [F].[Group]);
Table-Values Parameters (SQL 2008+ Only)
SQL 2008 has a neat feature where you can send tables as parameters to the database. Clearly this will only work if you are using SqlCommands correctly (Executing Parameterized SQL Statements), unlike your example (appending user-created values to a SQL string is extremely bad practice - learn how to use parameters) - as you need to pass in a DataTable which you can't do with a simple string value.
In order to use this you first need to create the value type:
CREATE TYPE [dbo].[IntList] AS TABLE
([Value] INT);
GO
Next we will do things properly and used a stored procedure - as this is a static query and there are some performance implications of using a sproc (query plan caching).
CREATE PROCEDURE [dbo].[GetFiles]
#Groups [dbo].[IntList] READONLY
AS BEGIN
SET NOCOUNT ON;
SET XACT_ABORT ON;
SELECT [F].[Name], [F].[Group] FROM [dbo].[Files] AS [F]
WHERE EXISTS (SELECT 1 FROM #Groups AS [G] WHERE [G].[Value] = [F].[Group]);
END
GO
Next we need to hit this from C#, which is pretty straight-forward as we can create a table to do the call.
public static void GetFilesByGroups(string groupsQuery)
{
GetFilesByGroups(groupsQuery.Split('|').Select(x => int.Parse(x)));
}
public static void GetFilesByGroups(params int[] groups)
{
GetFilesByGroups((IEnumerable<int>)groups);
}
public static void GetFilesByGroups(IEnumerable<int> groups)
{
// Create the DataTable that will contain our groups values.
var table = new DataTable();
table.Columns.Add("Value", typeof(int));
foreach (var group in groups)
table.Rows.Add(group);
using (var connection = CreateConnection())
using (var command = connection.CreateCommand())
{
command.CommandType = CommandType.StoredProcedure;
command.CommandText = "[dbo].[GetFiles]";
// Add the table like any other parameter.
command.Parameters.AddWithValue("#Groups", table);
using (var reader = command.ExecuteReader())
{
// ...
}
}
}
Remember: Table-Valued Parameters are only supported on SQL 2008 and later.
Edit: I would like to point out that there is likely a cross-over point in terms of performance between dknaack's answer and the temp table approach. His will likely be faster for a small set of search-groups; where the temp table approach would probably be faster for a large set of search-groups. There is a possibility that table-valued parameters would nearly always be faster. This is all just theory based on what I know about how the SQL query engine works: temp table might do a merge or hash join where the TVP would hopefully do a nested loop. I haven't done any profiling (and haven't received enough upvotes to motivate me to do so) so I can't say for certain.
Description
You should use SqlParameter to prevent Sql injections. Use the IN Statetment to pass in a comma seperated list of you group ids.
Sample
// value from cookie
string groups = "2,10,99";
// Build where clause and params
List<string> where = new List<string>();
List<SqlParameter> param = new List<SqlParameter>();
foreach(string group in groups.Split(','))
{
int groupId = Int32.Parse(group);
string paramName = string.Format("#Group{0}", groupId);
where.Add(paramName);
param.Add(new SqlParameter(paramName, groupId));
}
// create command
SqlConnection myConnection = new SqlConnection("My ConnectionString");
SqlCommand command = new SqlCommand("SELECT Files.Id, Files.Name, Files.Date, " +
"Files.Path, Files.[Group] " +
"FROM Files " +
"WHERE Files.[Group] in (" + string.Join(",", param) + ")" +
"ORDER BY Files.Id DESC", myConnection);
command.Parameters.AddRange(param.ToArray());
More Information
MSDN - IN (Transact-SQL)
C# SqlParameter Example
You're probably (depending on your database) looking at using this:
IN (2, 10)
rather than an = operator.
Note that constructing SQL using string concatenation like this can expose your code to SQL injection vulnerabilities, and using a properly parameterised SQL query is generally better practice. However, in your case, where you have an indeterminate number of parameters, it is harder to achieve in practice.
You need to set Param in cookie to create a chain like 2,10.
Then, instead of using = you need to use in () like this:
SELECT Files.Id, Files.Name, Files.Date, Files.Path, Files.[Group] FROM Files WHERE Files.[Group] in (" + param + ") ORDER BY Files.Id DESC"
Another thing that you got wrong was missing a space in param + "ORDER part.