NullReferenceException using COALESCE in SQL query - c#

Perhaps I'm misunderstanding COALESCE, but in essence, what I'm trying to do is run a select query that if it returns NULL instead of an int, instead return 0 for the purposes of ExecuteScalar().
SQL Server query:
SELECT TOP 1 COALESCE(SeqNo,0)
FROM tblProjectChangeOrder
WHERE ProjectID = XXXXX
ORDER BY SeqNo DESC
If the supplied ProjectID exists in the Change Order table, it returns the expected highest SeqNo. However, if the supplied ProjectID has no existing Change Orders (thus returns NULL for SeqNo), rather than the COALESCE returning 0, I am still getting NULL.
Am I just getting the syntax wrong or is what I want to do possible with COALESCE? The other option I see is to have my ExecuteScalar() pass to a nullable int, then follow that with a ?? to coalesce in my C# codebehind.

As john has mentioned in the comments, COALESCE operates at row level. If a table contains no rows, or a statement returns no rows, then no rows will be returned. Take the simple example below:
CREATE TABLE #Sample (ID int);
SELECT COALESCE(ID, 0)
FROM #Sample;
DROP TABLE #Sample;
Notice that nothing is returned.
Instead, one method is to use a subquery. For your query, that would result in:
SELECT COALESCE(SELECT TOP 1 SeqNo
FROM tblProjectChangeOrder
WHERE ProjectID = XXXXX
ORDER BY SeqNo DESC),0) AS SeqNo;
This also assumes that Seqno has a data type of int; otherwise you're likely to get a conversion error.

My guess is that the null reference exception occures on the code and has nothing to do with the sql query. It might just be that your code is not handling that you return no rows (or no scalar in your case) but you might be trying to access it somewhere in c#.
Show us the line of code that is throwing this exception in c# so we might be able to confirm this.
regards
Edit : From this similar topic
In your c# code you might want to try ("cmd" being your "SqlCommand" object):
int result = 0;
int.TryParse(cmd.ExecuteScalar(), out result);
or in one line
int.TryParse(cmd.ExecuteScalar(), out int result);
I don't know if it is the most suitable solution for you but I hope it is a start.

As covered null and no row are not the same.
This sample covers it.
set nocount on;
select isnull(1, 0)
where 1 = 1;
select isnull(1, 0)
where 1 = 2;
select isnull(null, 0)
where 1 = 1;
select isnull(null, 0)
where 1 = 2;
-----------
1
-----------
-----------
0
-----------
this should work
select top 1 isnull(seq, 0)
from (select null as seq
union all
select max(seq) from tblProjectChangeOrder where ProjectID = XXXXX
) tt
order by seq desc

Related

Meaning of a linq statement

I saw this code and trying to understand the ling. feater.groupId = 30
var myData = await _source.GetDataByIdNumber(staffId);
if(!myData.select(x=>x.Id).contains(feater.groupId))
{
status.IsValid = false;
}
How do I interprete this line in sql or english?
if(!myData.select(x=>x.Id).contains(feater.groupId))
"If" check is satisfied if none of the myData entities have an Id value of 30
Assuming standard C# Linq operators as per comments!
if(!myData.Select(x=>x.Id).Contains(feater.groupEntityId));
Selects the Id property of all entities. Determines if value 30 is contained within the selected sequence.
Direct T-SQL translation is:
SELECT CASE
WHEN 30 IN (
SELECT Id
FROM <YourTable>
)
THEN CAST(1 AS BIT) ELSE CAST(0 AS BIT)
END
So, if the supplied value is equal to any of the Ids returned by the select sub-query, return true, else return false.

C# linq-sql checking for null string

In my database table I have 54 rows, of those 53 are NULL in the Code column which is defined as a varchar(100). (I confirmed that those fields are null by performing queries directly on the database using my Microsoft SQL Management Studio) I have tried to use the following LINQ code to return all of the rows where the field is null:
public IQueryable<LocationHeader> LocationHeaders
{
return from p in Context.LocationTypes
where p.Code == null
select new LocationHeader
{
ID = p.ID,
Description = p.Description,
Code = p.Code
};
}
I have also found that if Iremove the where clause and then call ToList() on my LocationHeaders property and then query against it, it returns only the 53 rows I expect.
var test = LocationHeaders.ToList().Where(x => x.Code == null);
I've also tried p.Code.Equals(null) and object.Equals(p.Code, null) as suggested in related questions on this website and elsewhere. It always return an empty collection. If I change it to p.Code != null it returns all 54 rows (53 of which have a null Code column, and 1 which does not) but I view those objects the Code property has been set to null.
I also tried to null coalesce my code property into an empty string that I could check later:
Code = p.Code ?? string.Empty
But that changed exactly nothing, when I viewed the items after the query was performed, the Code property of my objects was still set to null.
Does anyone know why this might be and what I can do to fix it? I am using EF 6 Code-First, if that matters.
EDIT: I've permanently changed my code to read this way:
public IQueryable<LocationHeader> LocationHeaders
{
return from p in Context.LocationTypes
where object.Equals(p.Code, null)
select new LocationHeader
{
ID = p.ID,
Description = p.Description,
Code = p.Code
};
}
and I finally thought to check the query using SQL Server profiler. It's STILL writing the query like this:
exec sp_executesql N'SELECT
[Extent1].[ID] AS [ID],
[Extent1].[Description] AS [Description],
[Extent1].[Code] AS [Code]
FROM [dbo].[LocationType] AS [Extent1]
WHERE [Extent1].[Code] = #p__linq__0',N'#p__linq__0 nvarchar(4000)',#p__linq__0=NULL
Update your entity models to not allow NULL values in the first place... you will have to set all the fields which are currently NULL to an empty string prior to attempting this, (you can set them through SSMS)...
After you've set all the null values to an empty string.
Add this attribute to the Code property.
[Required(AllowEmptyStrings = true)]
public string Code { get; set; }
And migrate those changes over.
From here on you can just do Foo.Code == string.Empty

How to insert a dynamic id to table in sql?

I want to add to a table in my DB ("InjuryScenario") a dynamic id (because i work with visual studio c#) and i tried this:
declare #InjuryScenarioTMPp int;
set #InjuryScenarioTMPp = (select MAX (InjuryScenario_id) from InjuryScenario) +1;
print #InjuryScenarioTMPp;
when i printed it, it doesn't show me anything.
when i tried to add a row to the table and i tried the 3 rows (up) again it does print "2".
maybe when i don't have any rows in the table it doesn't know how to do (NULL+1)?
does anyone have an idea why?
It depends on what kind of database would you like to use. For example, in MySQL you have to use AUTO INCREMENT on one field. But in Postgres or in Oracle, you should create first a sequence and later than add the next value of this sequence to new record.
A NULLvalue is an unknown value, and the result of arithmetic on unknown values is also unknown. What you can do is to use either theCOALESCEor theISNULLfunction to replace the NULLvalue with 0when you use it:
declare #InjuryScenarioTMPp int;
set #InjuryScenarioTMPp = (select ISNULL(MAX(InjuryScenario_id),0) from InjuryScenario) + 1;
print #InjuryScenarioTMPp;
See MSDN: COALESCE and ISNULL
For an example:
declare #i int
set #i = null
select #i+1, coalesce(#i, 0)+1, isnull(#i, 0)+1
set #i = 1
select #i+1, coalesce(#i, 0)+1, isnull(#i, 0)+1
Output:
----------- ----------- -----------
NULL 1 1
(1 row(s) affected)
----------- ----------- -----------
2 2 2
(1 row(s) affected)

What should I use instead of SQL "OR" statements

I have a gridview that can be filtered by 6 drop down boxes, so when writing the sql the easiest thing would be to use an 'or' statement if dropdown has selection or null etc.
However I have read on here and other sites that using sql or statements are a bad idea, can anyone offer any other suggestions i could use rather than me writing variations on whether each ddl selection is null? Below is an example of the first query, with every ddl returning a value
#ruleID int = null,
#engagementStatusID int = null,
#areaOfWorkID int = null,
#registered bit = null,
#staffGroupID int = null,
#assignmentTypeID int = NULL
AS
SET NOCOUNT ON
IF (#ruleID IS NOT NULL and #engagementStatusID IS NOT NULL and #areaOfWorkID IS NOT NULL and
#registered IS NOT NULL and #staffGroupID IS NOT NULL and #assignmentTypeID IS NOT NULL)
BEGIN
SELECT r.dbRuleId AS RuleID,r.dbEngagementStatusId AS EngagementStatusID,
r.dbIsAllStaffGroups AS AllStaffGroups,r.dbIsAllAssignments AS AllAssignments,
r.dbIsAllRegistered AS AllRegistered,r.dbIsAllUnregistered AS AllUnregistered,
r.dbSoftDelete AS Softdelete, es.dbName AS EngagementName,
sgc.dbName AS StaffGroupName, aow.dbName AS AreaOfWorkName,
at.dbName AS AssignmentName, at.dbIsRegistered AS Registered,sgc.dbStaffGroupCodeId AS StaffGroupCodeID,
at.dbAssignmentTypeId AS AssignmentID, aow.dbAreaOfWorkId AS AreaOfWorkID
FROM dbo.tbRule r INNER JOIN
dbo.EngagementStatus es ON r.dbEngagementStatusId = es.dbEngagementStatusId INNER JOIN
dbo.RuleStaffGroup rsg ON r.dbRuleId = rsg.dbRuleId INNER JOIN
dbo.StaffGroupCode sgc ON rsg.dbStaffGroupId = sgc.dbStaffGroupCodeId INNER JOIN
dbo.RuleAssignmentCode rac ON r.dbRuleId = rac.dbRuleId INNER JOIN
dbo.AssignmentCode ac ON
rac.dbAssignmentCodeId = ac.dbAssignmentCodeId INNER JOIN
dbo.AssignmentType at ON ac.dbAssignmentId = at.dbAssignmentTypeId INNER JOIN
dbo.AreaOfWork aow ON ac.dbAreaOfWorkId = aow.dbAreaOfWorkId
WHERE ((r.dbRuleId = #ruleID) and (r.dbEngagementStatusId = #engagementStatusID) and (aow.dbAreaOfWorkId = #areaOfWorkID) and
(at.dbIsRegistered = #registered) and (sgc.dbStaffGroupCodeId = #staffGroupID) and (at.dbAssignmentTypeId = #assignmentTypeID))
Any advice on this would be great
Update
I feel i should clarify something about my code, when i say null, this is the value i have assigned to the "all" selection of a drop down list, so for example imn most cases i do something like this to get the value that needs to be passed to the DB
int? Type = (this.ddlType.SelectedValue.ToString() == "All") ? (int?)null : Convert.ToInt32(this.ddlType.SelectedValue.ToString());
so if the user has selected all the Db recieves 'null' which i can then use on the 'if #blah IS NOT NULL' etc. I realise this is probably not the best way to do this
It seems you are executing this stored procedure and then validating the user input at the database level. You should not call the database stored procedure if the drop down list values are null, you can handle this at the client side (or server side).
Client side (JavaScript) would be better for the users experience, you can then invoke the stored procedure if the user has selected all appropriate drop down list values.
The problem comes when you do things like:
WHERE (r.dbRuleId = #ruleID or #ruleID is null)
and (r.dbEngagementStatusId = #engagementStatusID
or #engagementStatusID is null)
-- ... lots more
which quickly degrades to really bad query plans. The trick, then, is to have TSQL that matches your exact set of query parameters.
The hard to maintain way to fix this is to write DML for every single possibility, and branch into the correct one - but this is really ugly, and confuses a lot of tools.
The easiest way to do this is to build the TSQL appropriately at the caller - but if your system demands that you use a stored procedure (the benefits for which, these days, are dubious at best - btw), then the simplest choice is dynamic SQL. Obviously, you need to be careful here - you still don't want to concatenate inputs (for both injection and query-plan reasons), but - you can do something like:
declare #sql nvarchar(4000) = N'...start of query...';
if(#ruleID is not null)
set #sql = #sql + N' and r.dbRuleId = #ruleID';
if(#engagementStatusID is not null)
set #sql = #sql + N' and r.dbEngagementStatusId = #engagementStatusID';
You then need to execute that with sp_executesql, declaring the parameters:
exec 'sp_executesql', #sql,
N'#ruleID int, #engagementStatusID int',
#ruleID, #engagementStatusID
I'm not sure I understood your question, but if you're looking at avoiding repetition of OR operators, consider using IN('x','y','z') - listing the possible values. This would be easier to read than [something] = 'x' OR [something] = 'y' OR [something] = 'z'.

special select query

I have 3 tables in my sql database like these :
Documents : (DocID, FileName) //list of all docs that were attached to items
Items : (ItemID, ...) //list of all items
DocumentRelation : (DocID, ItemID) //the relation between docs and items
In my winform application I have showed all records of Items table in a grid view and let user to select several rows of it and then if he press EditAll button another grid view should fill by file name of documents that are related to these selected items but not all of them,
Just each of documents which have relation with ALL selected items
Is there any query (sql or linq) to select these documents?
Try something like:
string query;
foreach (Item in SelectedItems)
{
query += "select DocID from DocumentRelation where ItemID =" + Item.Id;
query += "INTERSECT";
}
query -= "INTERSECT";
And exec the Query;
Take one string and keep on adding itemid comma separated in that,like 1,2,3 and then write query like
declare ItemID varchar(50);
set ItemID='1,2,3';
select FileName
from documents
Left Join DocumentRelation on Documents.DocId = DocumentRelation.DocId
where
DocumentRelation.ItemID in (select * from > dbo.SplitString(ItemID))
and then make one function in database like below
ALTER FUNCTION [dbo].[SplitString] (#OrderList varchar(1000))
RETURNS #ParsedList table (OrderID varchar(1000) )
AS BEGIN
IF #OrderList = ''
BEGIN
set #OrderList='Null'
end
DECLARE #OrderID varchar(1000), #Pos int
SET #OrderList = LTRIM(RTRIM(#OrderList))+ ','
SET #Pos = CHARINDEX(',', #OrderList, 1)
IF REPLACE(#OrderList, ',', '') <''
BEGIN
WHILE #Pos 0
BEGIN
SET #OrderID = LTRIM(RTRIM(LEFT(#OrderList, #Pos - 1)))
IF #OrderID < ''
BEGIN
INSERT INTO #ParsedList (OrderID)
VALUES (CAST(#OrderID AS varchar(1000)))
--Use Appropriate conversion
END
SET #OrderList = RIGHT(#OrderList, LEN(#OrderList) - #Pos)
SET #Pos = CHARINDEX(',', #OrderList, 1)
END
END
RETURN
END
Linq
var td =
from s in Items
join r in DocumentRelation on s.ItemID equals r.ItemID
join k in Documents on k.DocID equals r.DocID
where Coll.Contains (s.ItemID) //Here Coll is the collection of ItemID which you can store when the users click on the grid view row
select new
{
FileName=k.FileName,
DocumentID= k.DocId
};
You can loop through td collection and bind to your grid view
SQL
create a stored proc to get the relevant documents for the itemID selected from the grid view and paramterize your in clause
select k.FileName,k.DocId from Items as s inner join
DocumentRelation as r on
s.ItemID=r.ItemID and r.ItemId in (pass the above coll containing selected ItemIds as an input the SP)
inner join Documents as k
on k.DocId=r.DocIk
You can get the information on how to parametrize your sql query
Here's one approach. I'll let you figure out how you want to supply the list of items as arguments. And I also assume that (DocID, ItemID) is a primary key in the relations table. The having condition is what enforces your requirement that all select items are related to the list of documents you're seeking.
;with ItemsSelected as (
select i.ItemID
from Items as i
where i.ItemID in (<list of selected ItemIDs>)
)
select dr.DocID
from DocumentRelation as dr
where dr.ItemID in (select ItemID from ItemsSelected)
group by dr.DocID
having count(dr.ItemID) = (select count(*) from ItemsSelected);
EDIT
As far as I can tell, the accepted answer is equivalent to the solution here despite OP's comment below.
I did some quick tests with a very long series of intersect queries and confirmed that you can indeed expect that approach to become gradually slower with an increasing number of selected items. But a much worse problem was the time taken just to compile the queries. I tried this on a very fast server and found that that step took about eight seconds when roughly one hundred intersects were concatenated.
SQL Fiddle didn't let me do anywhere near as many before producing this error (and taking more than ten seconds in the process): The query processor ran out of internal resources and could not produce a query plan. This is a rare event and only expected for extremely complex queries or queries that reference a very large number of tables or partitions. Please simplify the query. If you believe you have received this message in error, contact Customer Support Services for more information.
There are several possible methods of passing a list of arguments to SQL Server. Assuming that you prefer the dynamic query solution I'd argue that this version is still better while also noting that there is a SQL Server limit on the number of values inside the in.
There are plenty of ways to have this stuff blow up.

Categories

Resources