How do I test for DBNull so as not to crash with "Column .... does not belong to table row"? - vb.net

I am reading 2 tables in T-SQL like so:
Select r.UID,c.Forename,c.Surname,c.DOB From c LEFT OUTER JOIN r on........
Then in VB.NET I loop through the dataset like so:
For Each drR In dsR.Tables(0).Rows......Next
However when I test like so:
If Convert.IsDBNull(drR("r.UID")) Then
Or
String.IsNullOrEmpty(drR("r.UID"))
Convert.IsDBNull(r.UID))
I crash with
Column 'UID' does not belong to table
row
when the second table r has no record.
I did try both r.UID and UID.
To recoup: All is fine when I have a record in the second table, but what must I do when I do not? How do I test for DBNull so as not to crash with "Column .... does not belong to table row"?
P.S. Regardng the 2 answers below: I have to test for UID so as to know whether there is a record in the 2nd table or not.

You need to do a few thing to get your code flying.
First consider side stepping your problem by using an INNER JOIN instead of the LEFT OUTER JOIN.
OR Split the problem and work with two sets.
A set with UIDs:
select r.Uid, c.Forename, c.Surname, c.DOB
from c
inner join r
on c.forename=r.forename and c.surname=r.surname
A set without UIDs:
select c.Forename, c.Surname, c.DOB
from c
left outer join r
on c.forename=r.forename and c.surname=r.surname
where r.Uid is null
But if you need to do it in one go with all the rows from the LEFT Table then you will need to check for DBNull and here is how to do that:
If Not IsDBNull(drR("UID")) Then
'Success
Debug.Print(drR("UID"))
Else
'Failure
End If
This is another way to do the same check:
If Not TypeOf drR("UID") Is DBNull Then
'Success
Debug.Print(drR("UID"))
Else
'Failure
End If
also if you are in a long tight loop you may gain performance by indexing the column:
Dim dt as DataTable = DAL.GetYourDataTable()
Dim ixUID As Integer = dt.Columns.IndexOf("UID")
For Each dr As DataRow In dt.Rows
If Not IsDBNull(dr(ixUID)) Then
'Success
Debug.Print(dr(ixUID))
Else
'Failure
End If
Next

change your query to:
Select isnull(r.UID,''),c.Forename,c.Surname,c.DOB From c LEFT OUTER JOIN r on

You could use the AS alias to specify the column name if needed combined with lvo's suggestion
SELECT ISNULL(r.UID,'') AS 'UID' ...etc...
In that way the column has a name and therefore would eliminate the test for checking the column name UID if it is present on another table.
Hope this helps,
Best regards,
Tom.

Put a break after the dataset is filled. Then in your watch window add the following to see what the column name is (assuming r.UID is the first column then columns(0) )
- i.e. columns(columnnumber - 1)
dsR.Tables(0).columns(0).ColumnName

Related

Should I use an SQL full outer join for this?

Consider the following tables:
Table A:
DOC_NUM
DOC_TYPE
RELATED_DOC_NUM
NEXT_STATUS
...
Table B:
DOC_NUM
DOC_TYPE
RELATED_DOC_NUM
NEXT_STATUS
...
The DOC_TYPE and NEXT_STATUS columns have different meanings between the two tables, although a NEXT_STATUS = 999 means "closed" in both. Also, under certain conditions, there will be a record in each table, with a reference to a corresponding entry in the other table (i.e. the RELATED_DOC_NUM columns).
I am trying to create a query that will get data from both tables that meet the following conditions:
A.RELATED_DOC_NUM = B.DOC_NUM
A.DOC_TYPE = "ST"
B.DOC_TYPE = "OT"
A.NEXT_STATUS < 999 OR B.NEXT_STATUS < 999
A.DOC_TYPE = "ST" represents a transfer order to transfer inventory from one plant to another. B.DOC_TYPE = "OT" represents a corresponding receipt of the transferred inventory at the receiving plant.
We want to get records from either table where there is an ST/OT pair where either or both entries are not closed (i.e. NEXT_STATUS < 999).
I am assuming that I need to use a FULL OUTER join to accomplish this. If this is the wrong assumption, please let me know what I should be doing instead.
UPDATE (11/30/2021):
I believe that #Caius Jard is correct in that this does not need to be an outer join. There should always be an ST/OT pair.
With that I have written my query as follows:
SELECT <columns>
FROM A LEFT JOIN B
ON
A.RELATED_DOC_NUM = B.DOC_NUM
WHERE
A.DOC_TYPE IN ('ST') AND
B.DOC_TYPE IN ('OT') AND
(A.NEXT_STATUS < 999 OR B.NEXT_STATUS < 999)
Does this make sense?
UPDATE 2 (11/30/2021):
The reality is that these are DB2 database tables being used by the JD Edwards ERP application. The only way I know of to see the table definitions is by using the web site http://www.jdetables.com/, entering the table ID and hitting return to run the search. It comes back with a ton of information about the table and its columns.
Table A is really F4211 and table B is really F4311.
Right now, I've simplified the query to keep it simple and keep variables to a minimum. This is what I have currently:
SELECT CAST(F4211.SDDOCO AS VARCHAR(8)) AS SO_NUM,
F4211.SDRORN AS RELATED_PO,
F4211.SDDCTO AS SO_DOC_TYPE,
F4211.SDNXTR AS SO_NEXT_STATUS,
CAST(F4311.PDDOCO AS VARCHAR(8)) AS PO_NUM,
F4311.PDRORN AS RELATED_SO,
F4311.PDDCTO AS PO_DOC_TYPE,
F4311.PDNXTR AS PO_NEXT_STATUS
FROM PROD2DTA.F4211 AS F4211
INNER JOIN PROD2DTA.F4311 AS F4311
ON F4211.SDRORN = CAST(F4311.PDDOCO AS VARCHAR(8))
WHERE F4211.SDDCTO IN ( 'ST' )
AND F4311.PDDCTO IN ( 'OT' )
The other part of the story is that I'm using a reporting package that allows you to define "virtual" views of the data. Virtual views allow the report developer to specify the SQL to use. This is the application where I am using the SQL. When I set up the SQL, there is a validation step that must be performed. It will return a limited set of results if the SQL is validated.
When I enter the query above and validate it, it says that there are no results, which makes no sense. I'm guessing the data casting is causing the issue, but not sure.
UPDATE 3 (11/30/2021):
One more twist to the story. The related doc number is not only defined as a string value, but it contains leading zeros. This is true in both tables. The main doc number (in both tables) is defined as a numeric value and therefore has no leading zeros. I have no idea why those who developed JDE would have done this, but that is what is there.
So, there are matching records between the two tables that meet the criteria, but I think I'm getting no results because when I convert the numeric to a string, it does not match, because one value is, say "12345", while the other is "00012345".
Can I pad the numeric -> string value with zeros before doing the equals check?
UPDATE 4 (12/2/2021):
Was able to finally get the query to work by converting the numeric doc num to a left zero padded string.
SELECT <columns>
FROM PROD2DTA.F4211 AS F4211
INNER JOIN PROD2DTA.F4311 AS F4311
ON F4211.SDRORN = RIGHT(CONCAT('00000000', CAST(F4311.PDDOCO AS VARCHAR(8))), 8)
WHERE F4211.SDDCTO IN ( 'ST' )
AND F4311.PDDCTO IN ( 'OT' )
AND ( F4211.SDNXTR < 999
OR F4311.PDNXTR < 999 )
You should write your query as follows:
SELECT <columns>
FROM A INNER JOIN B
ON
A.RELATED_DOC_NUM = B.DOC_NUM
WHERE
A.DOC_TYPE IN ('ST') AND
B.DOC_TYPE IN ('OT') AND
(A.NEXT_STATUS < 999 OR B.NEXT_STATUS < 999)
LEFT join is a type of OUTER join; LEFT JOIN is typically a contraction of LEFT OUTER JOIN). OUTER means "one side might have nulls in every column because there was no match". Most critically, the code as posted in the question (with a LEFT JOIN, but then has WHERE some_column_from_the_right_table = some_value) runs as an INNER join, because any NULLs inserted by the LEFT OUTER process, are then quashed by the WHERE clause
See Update 4 for details of how I resolved the "data conversion or mapping" error.

Joining to the same table multiple times to retrieve different data each time using a filter

I have a query which looks something like this (the actual table and column names are MUCH longer, it's a mess, and there are actually SIX fields I need to retrieve from the FieldSummary table but I haven't written out the whole query yet!)
select
rh.RequirementDate,
rt.RequirementType,
f1s.Summary as F1,
f2s.Summary as F2,
f3s.Summary as F3
from
RequirementHistory rh
join RequirementTypeLU rt on rh.RequirementTypeLUID = rt.RequirementTypeLUID
left join FieldSummary fs1 on rh.RequirementHistoryID = fs1.RequirementHistoryID -- all these identical joins, there's gotta be a better way...
left join Field f1 on fs1.FieldID = f1.FieldID
left join FieldSummary fs2 on rh.RequirementHistoryID = fs2.RequirementHistoryID
left join Field f2 on fs2.FieldID = f2.FieldID
left join FieldSummary fs3 on rh.RequirementHistoryID = fs3.RequirementHistoryID
left join Field f3 on fs3.FieldID = f3.FieldID
where
rh.LinkedEntityID = 3
and f1.ScreenDescription = 'Field 1' -- this ScreenDescription lookup looks really brittle...
and f2.ScreenDescription = 'Field 2'
and f3.ScreenDescription = 'Field 3'
So I'm taking the RequirementHistory and RequirementTypeLU (lookup) tables, and joining them to the FieldSummary (actually data entered in the fields, not sure why it's called a summary) and Field (the field definitions) tables to retrieve the field data, once for each field (the actual field names are specified in the where clause as ScreenDescription). It's some sort of weird modular thing where system administrators can define their own "additional fields" to link to whatever entity types they want in the system. I know it looks horrible, I didn't design it! 😉
Now my problem is I'm not getting any results. I'm sure something is wrong with my joins, but I'm not sure what. I also tried placing the ScreenDescription comparisons in the joins to Field, but that resulted in a ridiculous number of duplicate results, expanding with each pair of joins I added! What can I do to make this work properly? Ideally I want something like this as my results:
RequirementDate RequirementType F1 F2 F3
10/07/1983 Someone's birthday some data here
09/11/2001 A disaster more data here
01/20/2021 Recovery! At last! still more data
Where RequirementDate and RequirementType are pulled from their respective tables, and F1-3 are pulled from the FieldSummary table by looking up the appropriate field from Field using the ScreenDescription supplied in the where clause.
I know this is a huge mess but I'm trying to do my best with the database structure I have! 🙂
Use CASE logic to conditionally select the 'Summary' column values based on 'ScreenDescription'. Also, there were some minor issues with the table aliases being inconsistently applied which I corrected. Something like this.
select
rh.RequirementDate,
rt.RequirementType,
case when f1.ScreenDescription='Field 1' then fs1.Summary else null end as F1,
case when f1.ScreenDescription='Field 2' then fs1.Summary else null end as F2,
case when f1.ScreenDescription='Field 3' then fs1.Summary else null end as F3
from
RequirementHistory rh
join RequirementTypeLU rt on rh.RequirementTypeLUID = rt.RequirementTypeLUID
left join FieldSummary fs1 on rh.RequirementHistoryID = fs1.RequirementHistoryID
left join Field f1 on fs1.FieldID = f1.FieldID
where
rh.LinkedEntityID=3;
#TheImpaler's comments gave the correct hint to the answer - this is fleshing out the issues.
Issue 1: WHERE clause filtering
(Note that this becomes somewhat irrelevant with Issue 2 solution below. However, it's good to understand what's happening here as it is the thing blocking your current attempt.)
Just looking at the FROM Clause, you have, say
left join FieldSummary fs1 on rh.RequirementHistoryID = fs1.RequirementHistoryID
left join Field f1 on fs1.FieldID = f1.FieldID
and then in the WHERE clause you have
and f1.ScreenDescription = 'Field 1'
The issue is that the query will do the LEFT JOIN onto fs1 and f1, and as a LEFT JOIN it will have NULLs in fields that don't match.
However, you then filter by a field in F1 which then excludes those rows with NULLs. It effectively changes the LEFT JOINs into INNER JOINs.
Instead, you need to move the WHERE clauses on LEFT JOINed tables to be part of the join e.g.,
left join FieldSummary fs1 on rh.RequirementHistoryID = fs1.RequirementHistoryID
left join Field f1 on fs1.FieldID = f1.FieldID
and f1.ScreenDescription = 'Field 1'
Note that the above will report values from fs1 even if f1 doesn't match, as they're LEFT JOINed.
If you only want the rows from fs1 that also match the filtering on f1, then you need something like the following where both fs1 and f1 are filtering by f1's description.
left join
(FieldSummary fs1
INNER join Field f1 on fs1.FieldID = f1.FieldID
and f1.ScreenDescription = 'Field 1'
) on rh.RequirementHistoryID = fs1.RequirementHistoryID
Issue 2: 'There's gotta be a better way'
There is - also included as one of #TheImpaler's comments.
You don't actually need all those joins - you only need to join them once e.g.,
select
rh.RequirementDate,
rt.RequirementType,
fs.Summary,
f.ScreenDescription
from
RequirementHistory rh
join RequirementTypeLU rt on rh.RequirementTypeLUID = rt.RequirementTypeLUID
join FieldSummary fs on rh.RequirementHistoryID = fs.RequirementHistoryID
join Field f on fs.FieldID = f.FieldId
That will give you a table with (say) up to 6 rows per rh/rt combination, where each row includes the FieldSummary and Field 'ScreenDescription'.
From there, you just need to convert the rows to columns - via a PIVOT command, or MAX(CASE()), or similar. Search for these e.g.,
https://learn.microsoft.com/en-us/sql/t-sql/queries/from-using-pivot-and-unpivot?view=sql-server-ver15
https://www.sqlservertutorial.net/sql-server-basics/sql-server-pivot/
Convert Rows to columns using 'Pivot' in SQL Server
Efficiently convert rows to columns in sql server

Query does execute without errors, but returns no output

My query here has a sub-query in it but it returns no output, but in reality it has to give some output because I manually checked and output exists.I have posted the query below.
select mac.mac_id,mac.mac1,mac.mac_type,record.soc_id
from mso_charter.mac
join record on mac.record_id = record.record_id
where mac.mac_type='ethB' and record.soc_id IN (select soc from d);
Sample data is below
mac_id mac1 mac_type record_id--- for table mac
1 6142 ethA 1
2 6412 ethB 1
3 2313 ethC 1
record_id soc_id ---- for table record
1 Qu132
1 as432
1 342aq
soc --- for table d
a12w2
23we
qw12
mso_charter is the schema name mac,d and record is the table name.
Note that your subquery is actually still a join and can be written that way:
select mac.mac_id,mac.mac1,mac.mac_type,record.soc_id
from mso_charter.mac
join record using(record_id)
join d on record.soc_id = d.soc
where mac.mac_type='ethB';
As per the comment we still need a data set to reproduce and help.
Should be select soc_id from d instead of select select soc from d
According to your sample data, d has a column soc_id. That should be used for the comparison:
select m.mac_id, m.mac1, m.mac_type, r.soc_id
from mso_charter.mac m join
record r
on m.record_id = r.record_id
where m.mac_type = 'ethB' and
r.soc_id in (select d.soc_id from d);
It is possible that ids look the same but are not, because of international characters, hidden characters, spaces in the wrong place, and so on.
If this doesn't work then try the following:
Remove the soc_id condition and see if any rows match the first condition and join.
If that still returns nothing, remove the entire where clause to see if anything matches the join.
None of your record.soc_id match any of your d.soc_id. So you get no row.
Also, you write select soc from d. soc, not soc_id. Typo or error?
So, thanks to all who tried helping me in this situation. I actually had did a very silly mistake.The reason I am posting the right answer is probably because if someone else in future get stuck in such a issue or something similar it would be helpful to them.
select m.mac_id,m.mac,m.mac_type,r.soc_id
from mso_charter.mac m
join mso_charter.record r on m.record_id = r.record_id
where m.mac_type = 'ethB' and r.soc_id IN (select d.soc_id from d);
Mistake was I had not mentioned the schema name while performing join and there were multiple tables named record in other schema's, it was just out of frustration we tend to forget small things which costed me few hours to work over.

If this string appears in my column, then overwrite the information in my other column

I am dealing with this issue at the moment. Let's consider we have 2 columns (column A and column B). In column A I have a reference number and in Column B I have a string.
In column B, some rows will contain the term 'co-load' and next to it will have a reference number similar to the reference number in column A (but not identical).
Can I then copy the reference number for all occurrences with 'co-load' (10% of the data) by using a function in SQL Server and "paste" it onto the reference number in column A which is not longer valid?
As you can see in the image there is still some information left that is being copied into the new column. How can I avoid this?
select MM.MovementRef, ME.MovementReference, replace(MU.Number, 'co-load', '') as change, MU.Number as trailername
from dbo.MALExport AS MEME.MovementReference
INNER JOIN dbo.movConLink AS MCL ON ME.ConsignmentReference = MCL.ConsignmentReference
INNER JOIN dbo.cgtRoute AS CR ON RouteID = CMRRouteID
INNER JOIN dbo.movMovement AS MM ON MM.MovementRef =
LEFT JOIN dbo.movUnit AS MU ON MU.UnitID = MM.TrailerID
where MU.Number like '%co-load%'
If I understand correctly, you want to do an update. As you describe it, you can use string operations, such as:
update t
set a = replace(b, 'co-load', '')
where b like '%co-load%';

SQL Join Query VB (Pretty Sumple, I'm just learning)

I'm trying to make a query which has the same field in table A & table B. Then table B has the same field as table C. I want to left join all tables on table A. Is this possible? If yes, how close is my code to doing it?
Try
objConn = DBAccess.GetConnection
strBlder.Append("SELECT ")
strBlder.Append("FLD_NM, DATA_TYPE_CD, XML_PATH_TX, UPDT_USER_ID_NR, DOC_TMPL_FLD_ID_NR ")
strBlder.Append("FROM ")
strBlder.Append("LLC.[LLCW10_DCTMPFLD_TB] LEFT JOIN LLC.[DataMapTool_FieldMapping] ")
strBlder.Append("ON LLC.[LLCW10_DCTMPFLD_TB].DOC_TMPL_FLD_ID_NR = LLC.[DataMapTool_FieldMapping].DocumentTemplateFieldID ")
strBlder.Append("& LEFT JOIN LLC.[DataMapTool_FieldMapping] ")
strBlder.Append("ON LLC.[DataMapTool_FieldMapping].FieldMappingStatusID = LLC.[DataMapTool_FieldMappingStatus].FieldMappingStatusID ")
strBlder.Append("ORDER BY FLD_NM ;")
dsData = DBAccess.ExecuteDataTable(objConn, CommandType.Text, strBlder.ToString())
'execute non query - takes sp name
The error you are getting(after removing the &) is because LLC.[DataMapTool_FieldMapping] is repeated and thats a problem. Instead of your from clause try something like:
FROM (LLC.[LLCW10_DCTMPFLD_TB] LEFT JOIN LLC.[DataMapTool_FieldMapping] ON LLC.[LLCW10_DCTMPFLD_TB].DOC_TMPL_FLD_ID_NR = LLC.[DataMapTool_FieldMapping].DocumentTemplateFieldID)
LEFT JOIN LLC.[DataMapTool_FieldMappingStatus] ON LLC.[DataMapTool_FieldMappingStatus].FieldMappingStatusID = LLC.[DataMapTool_FieldMapping].FieldMappingStatusID
Note that the LLC.[DataMapTool_FieldMapping] is referenced in the first join and then only the .FieldMappingStatusID field is referenced in the second join. Your initial query would have been trying to add the same table to the query twice without giving it an alias which confuses the query analyzer.
Basically the new from clause will attach the first two tables together then attach the third table onto the result of the first join.
Looks good to me, but there is that strange & before the LEFT JOIN LLC.[DataMapTool_FieldMapping] ... take that out.