SQL Server Show Results when Record not found - c#

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.

Related

How to get a MAX value from 3 columns in SQL Server stored procedures?

I'm working on a project with C# and a SQL Server database. Right now I have to write a stored procedure, which needs to get a MAX value from 3 different columns.
My code looks like this:
CREATE PROCEDURE [dbo].[sp_GetClass]
#surname nvarchar(50),
#maxClass int OUT
AS
SELECT #maxClass = MAX
FROM (VALUES (Programming), (Mathematics), (Physics))
FROM MissLess
WHERE Surname LIKE '%' + #surname + '%'
GO
I get an error for the second FROM and I'm not sure how to fix it.
The idea is to make it get the max value from those columns, when a surname is input. When no input is made(press enter) it just shows the max value from those 3 columns across all surnames.
Excuse me, if the formatting or the questions is not asked right, it's my first post here.
EDIT: made an error in #name, it's supposed to be #surname; and Name is supposed to be Surname.
EDIT FINAL: Thanks for the awesome help. I'm an ultra newbie in all of this, but it all seems like great fun. Was so close to just giving up, but decided to give this a shot. Thanks for all the answers!!!
You will need to put these three values for the chosen row(s) into a table variable inside your stored procedure, in order to be able to apply the MAX to them.
Try something like this - I didn't know what datatype those values are - just assuming INT here, adapt as needed:
CREATE PROCEDURE [dbo].[GetMaxValue]
#surname NVARCHAR(50),
#maxClass INT OUTPUT
AS
DECLARE #IntTbl TABLE (AValue INT);
INSERT INTO #IntTbl (AValue)
SELECT Programming
FROM MissLess
WHERE Name LIKE '%' + #name + '%'
UNION ALL
SELECT Mathematics
FROM MissLess
WHERE Name LIKE '%' + #name + '%'
UNION ALL
SELECT Physics
FROM MissLess
WHERE Name LIKE '%' + #name + '%';
SELECT #maxClass = MAX(AValue)
FROM #IntTbl;
GO
Update - ok, you asked for it ;-)
Since your search criteria might match multiple rows (at least you probably can't rule that out entirely), you'd have to first get the MAX() for each column, based on the search criteria:
CREATE PROCEDURE [dbo].[GetMaxValue]
#surname NVARCHAR(50),
#maxClass INT OUTPUT
AS
DECLARE #MaxMath INT, #MaxPhys INT, #MaxProgr INT;
SELECT
#MaxMath = MAX(Mathematics),
#MaxPhys = MAX(Physics),
#MaxProgr = MAX(Programming)
FROM
MissLess
WHERE
Name LIKE '%' + #name + '%';
IF #MaxMath > #MaxPhys
BEGIN
IF #MaxMath > #MaxProgr
-- #MaxMath is bigger than both #MaxPhys AND #MaxProgr --> it's the overall MAX()
SET #maxClass = #MaxMath;
ELSE
-- #MaxMath is bigger than #MaxPhys, but smaller than #MaxProgr
SET #maxClass = #MaxProgr;
END
ELSE BEGIN
IF #MaxPhys > #MaxProgr
-- #MaxPhys is bigger than both #MaxMath AND #MaxProgr --> it's the overall MAX()
SET #maxClass = #MaxPhys;
ELSE
-- #MaxPhys is bigger than #MaxMath, but smaller than #MaxProgr
SET #maxClass = #MaxProgr;
END;
SELECT #maxClass = MAX(AValue)
FROM #IntTbl;
GO
Update #2: for a much nicer, cleaner solution for updating without using a table variable - see #AndrewCorrigan's answer further down, using a CASE expression.
You can do (Fiddle)
SELECT MAX(s)
FROM MissLess
CROSS APPLY (VALUES (Programming), (Mathematics), (Physics)) V(s)
WHERE Name LIKE '%' + #name + '%'
If the #name predicate matches multiple rows you will have no indication of which row it came from though
As an alternative solution using nested CASE statements:
SELECT #maxClass = MAX(CASE
WHEN Programming >= Mathematics
AND Programming >= Physics
THEN Programming
WHEN Mathematics >= Physics
AND Mathematics >= Programming
THEN Mathematics
ELSE Physics
END)
FROM MissLess
WHERE Surname LIKE '%' + #surname + '%';

How to pass in a string into dynamic sql inside a stored procedure

As of now I have a SQL statement like this:
SELECT
y.ID, y.STATUS, y.CONTROLID, y.TRCDE
FROM
YTDTRNI AS y
LEFT JOIN
VND AS v ON y.COMNO = v.COMNO
WHERE
TRCDE ='RC'
ORDER BY
ID DESC
I am trying to use this query inside a stored procedure using dynamic SQL. So far, my stored procedure looks like this:
SET #query = N'SELECT y.ID, y.STATUS, y.CONTROLID, y.TRCDE
FROM YTDTRNI AS y LEFT JOIN VND AS v ON y.COMNO = v.COMNO
WHERE TRCDE = ' + #searchtrtype + '
ORDER BY ' + #orderbycondition
The variables #searchtrtype and #orderbycondition are of type nvarchar.
I am using a ASP.NET/C# program to call the stored procedure. However, it breaks with an exception:
An expression of non boolean type in a context where a condition is expected near 'ORDER'
I think I am getting error because the string values are not properly concatenated or formatted in inside the #query variable.
Open to any advice.
EDIT: my stored procedure looks like this at the moment:
When I execute the stored procedure, it returns the result set that I want but it also shows an error message :
Must declare the scalar variable "#dsearchtrtype".
I tried declaring it inside the BEGIN body and declaring it as part of a parameter for the stored procedure but it still shows that same message.
Learn to use parameters with sp_executesql. You can do this for #searchtrtype, but not #orderbycondition:
SET #query = N'
SELECT y.ID, y.STATUS,y.CONTROLID,y.TRCDE
FROM YTDTRNI y LEFT JOIN
VND v
ON y.COMNO = v.COMNO
WHERE TRCDE = #searchtrtype
ORDER BY #orderbycondition';
SET #query = REPLACE(#query, '#orderbycondition', #orderbycondition);
EXEC sp_executesql #query,
N'#searchtrtype NVARCHAR(255)', -- or whatever the right type is
#searchtrtype=#searchtrtype;
You cannot pass in identifiers, only values, so this doesn't work for #orderbycondition.
You need to parametrise your SQL. Concatenating string for SQL is an awful idea; as it leaves you open to injection. The way you want to do it would be:
DECLARE #query nvarchar(MAX);
SET #query = N'SELECT y.ID,' + NCHAR(10) +
N' y.STATUS,' + NCHAR(10) +
N' y.CONTROLID,' + NCHAR(10) +
N' y.TRCDE' + NCHAR(10) +
N'FROM YTDTRNI AS y' + NCHAR(10) +
N' LEFT JOIN VND AS v ON y.COMNO = v.COMNO ' + NCHAR(10) +
N'WHERE TRCDE = #dsearchtrtype' + NCHAR(10) +
N'ORDER BY ' + QUOTENAME(#orderbycondition) + N';';
PRINT #SQL;
EXEC sp_executesql #query, N'#dsearchtrtype nvarchar(100)', #dsearchtrtype = #searchtrtype;
This, however, probably isn't going to work, due to your variable #orderbycondition. I don't know, however, what type of value that has, however, if it's something like 'ID desc', this it would become ORDER BY [ID desc];.
If this assumption is correct, I would suggest using 2 variables; one for the sort column and one for the direction, and replace the final line of the dynamic SQl with:
N'ORDER BY ' + QUOTENAME(#orderbycolumn) + N' ' + CASE WHEN #orderbydirection NOT IN(N'ASC',N'DESC') THEN N'' ELSE #orderbydirection END + N';';
If your value of #orderbycondition can be more complex than that, post a comment to let me know (and update your question with more detail), and I'll be happy to explain how to create a more dynamic ORDER BY clause using a table type parameter, along with adding it to your dynamic SQL with use of STUFF and FOR XML PATH.
Based on the example provided it appears that the OP simply wants to filter on a single column and perform a sort on a user specified column. This can be achieved with a parametrized query without the need of dynamic SQL.
Within the where clause the TRCDE field can be directly assigned to the #searchtrtype parameter. The dynamic sorting requirement is a little trickier but can be achieved by using a CASE statement within the ORDER BY clause. An additional parameter will have to be included to indicate if the sorting should be ascending or descending. The following query demonstrates:
DECLARE #orderByColumn NVARCHAR(50) = 'ID'; -- The column to sort by
DECLARE #sortAscending BIT = 1; -- Indicate sorting order 1 == Ascending, 0 == Descending
DECLARE #searchtrtype NVARCHAR(10) = 'RC'
IF #sortAscending = 1 -- Sort data in ascending order
BEGIN
SELECT y.[ID]
,y.[STATUS]
,y.[CONTROLID]
,y.[TRCDE]
FROM YTDTRNI AS y
LEFT JOIN VND AS v ON y.[COMNO] = v.[COMNO]
WHERE TRCDE = #searchtrtype
ORDER BY (
-- Create case statement for every column user may want to filter on
CASE
WHEN #orderByColumn = 'ID' THEN [ID]
ELSE NULL -- Simply return null if we are not ordering by this column, effectively ignore the column.
END
),
(
CASE
WHEN #orderByColumn = 'STATUS' THEN [STATUS]
ELSE NULL -- Simply return null if we are not ordering by this column, effectively ignore the column.
END
),
(
CASE
WHEN #orderByColumn = 'CONTROLID' THEN [CONTROLID]
ELSE NULL -- Simply return null if we are not ordering by this column, effectively ignore the column.
END
) ASC;
END
ELSE
BEGIN
SELECT y.[ID]
,y.[STATUS]
,y.[CONTROLID]
,y.[TRCDE]
FROM YTDTRNI AS y
LEFT JOIN VND AS v ON y.[COMNO] = v.[COMNO]
WHERE TRCDE = #searchtrtype
ORDER BY (
CASE
WHEN #orderByColumn = 'ID' THEN [ID]
ELSE NULL -- Simply return null if we are not ordering by this column, effectively ignore the column.
END
),
(
CASE
WHEN #orderByColumn = 'STATUS' THEN [STATUS]
ELSE NULL -- Simply return null if we are not ordering by this column, effectively ignore the column.
END
),
(
CASE
WHEN #orderByColumn = 'CONTROLID' THEN [CONTROLID]
ELSE NULL -- Simply return null if we are not ordering by this column, effectively ignore the column.
END
) DESC;
END
This method is more work but will be easier to troubleshoot, maintain and will protect you from SQL Injection.
do you just need to include escaped apostrophes. if #searchtrtype can contain apostrophes, you might have to escape them with REPLACE(#searchtrtype, '''', '''''')
SET #query = N'SELECT y.ID, y.STATUS,y.CONTROLID,y.TRCDE
FROM YTDTRNI AS y LEFT JOIN VND AS v ON y.COMNO = v.COMNO
WHERE TRCDE = ''' + #searchtrtype + '''
ORDER BY ' + #orderbycondition

Multiple table updates - avoiding multiple WHERE clauses

I looked around for a similar question (I'm sure there is one somewhere) but could not find one.
I have a list of IDs that for each of the IDs I need to update another column of that IDs row to the same string.
Essentially, I want something like this:
List<int> uniqueIDs;
UPDATE my_table
SET certainColumn = "foo bar"
WHERE uniqueID = uniqueIDs[0]
OR uniqueID = uniqueIDs[1]
...
OR uniqueID = uniqueID[uniqueIDs.Length-1]
I know this could be achieved by surrounding this in a for/foreach-loop, but I was wondering if there is a better way to get this done, possibly in one database connection?
Any help is greatly appreciated.
Well, you could use a TVP. First, create this on your server:
CREATE TYPE dbo.UniqueIDs AS TABLE(ID INT PRIMARY KEY);
Then a stored procedure:
CREATE PROCEDURE dbo.UpdateByID
#tvp dbo.UniqueIDs READONLY
AS
BEGIN
SET NOCOUNT ON;
UPDATE t SET certainColumn = 'foo bar'
FROM dbo.my_table AS t
INNER JOIN #tvp AS tvp
ON t.uniqueID = tvp.ID;
END
Or:
CREATE PROCEDURE dbo.UpdateByID
#tvp dbo.UniqueIDs READONLY
AS
BEGIN
SET NOCOUNT ON;
UPDATE t SET certainColumn = 'foo bar'
FROM dbo.my_table AS t
WHERE EXISTS (SELECT 1 FROM #tvp
WHERE ID = t.uniqueID);
END
Then instead of a List use a DataTable to hold your IDs in your C# application, and call the stored procedure, passing #tvp as a Structured parameter. I have simple examples of the C# side posted all over this site:
How to pass an array into a SQL Server stored procedure
Insert entire DataTable into database at once instead of row by row?
Parameters to the EXISTS clause in a stored procedure
var query = "UPDATE my_table
SET certainColumn = 'foo bar'
WHERE uniqueID in (" + String.Join(",", uniqueIDs) + ")"
I guess you could do something like this. Get your List of Ids in some TempTable or table variable and use IN operator in your update statement. something like this .....
UPDATE my_table
SET certainColumn = 'foo bar'
WHERE uniqueID IN (SELECT uniqueID
FROM #List_Table)

sql create string variable with column names

i'm trying to create a dynamic sql statement either thru sql code or c# code. For example, I'd like to generate a result set based on a string containing a table name. Currently, I am doing this thru c# code.
My current issue is I'd like to generate a search similar to following
select * from customers
where ContactName+City like '%Berlin%'
so I'm thinking given a table name as a string parameter I need to somehow produce a string variable 'ContactName+City+etc' to build part of the search
I'm open to any other ideas as well.
var sql = string.Format(#"
select * from {0}
where {1} like '%criteria%'"
, variable_table
, "column1+column2+columnX"); //need function here to produce this string based on variable table?
Basically, how would I create a string that concatenates a variable number of columns together ('ContactName+City+etc') based on a variable_table?
Why not simply this:
select * from variable_table_name
WHERE column1+column2+columnX like '%criteria%'
You can do this purely in SQL as well. But as you have already done this in C# and you need only to get the list of columns based on the table name, try this.
Create a SQL udf as below.
CREATE FUNCTION funcReturnAllColumns
(
#tableName VARCHAR(50)
)
RETURNS VARCHAR(500)
BEGIN
DECLARE #ID INT
DECLARE #ALLColumns VARCHAR(500)
SET #ALLColumns = ''
SELECT #ID = id
FROM sys.sysobjects
WHERE name = #tableName
SELECT #ALLColumns = #ALLColumns + '+' + name
FROM sys.syscolumns
WHERE id = #ID
RETURN SUBSTRING(#ALLColumns,2,LEN(#ALLColumns))
END
SELECT dbo.funcReturnAllColumns('table_name')
OUTPUT:
Column1 + Column2 + ..... + ColumN
You may have to adjust varchar limits, validations as required.

Select highest number from table when number stored as string?

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

Categories

Resources