Not allowing multiple Null values in an Access 2010 multi-column Index - indexing

I'm trying to create a table in Access 2010 which will not allow duplicates in two fields, but will allow nulls in one of those fields providing there is only a single null value (so no duplication of value/null).
My table fields are as below with the ID field set as a Primary Key and the plan is to not allow duplicates in CostCode/TeamID but TeamID can be Null once for each instance of a CostCode.
The picture below shows that I can't add a CostCode and TeamID twice if they both have values, but I can add a CostCode twice with Null values in TeamID.
Is there anyway to achieve this?
I've read I could give TeamID a default value of an empty string (or 0 as that will never be a TeamID) but I'd like to use Null if possible as that is what the empty string or 0 would represent.
EDIT:
After the comment from JJ32 and a weekend to think it through I've gone with putting the TeamID value into a separate table.
I would then have a Many-2-Many join between tbl_BranchDetail and tbl_CostCodes and a Many-2-Many join between tbl_CostCodeM2MJoin and tbl_Teams.
This will remove Null values from occurring in either Many-2-Many table and my query will now read as:
SELECT M2M.BranchID
,M2M.CostCodeID
,TM2M.TeamID
,CC.CostCode
,TM.TeamName
FROM ((tbl_CostCodes CC INNER JOIN tbl_CostCodeM2MJoin M2M ON CC.ID = M2M.CostCodeID)
LEFT JOIN tbl_CostCodeToTeamM2MJoin TM2M ON (M2M.BranchID = TM2M.BranchID AND
M2M.CostCodeID = TM2M.CostCodeID))
LEFT JOIN tbl_Teams TM ON TM2M.TeamID = TM.ID

I don't believe it is possible to disallow duplicate nulls in a unique composite index since no two Nulls are ever considered equal.
So in your example above you'd have three unique rows, one with a combination of TBC/1 and two with a combination of TBC/null.
The only answer I know of, unfortunately, is to choose some non-null value to represent null in TeamID, and then display the result as empty within the application.

Related

Inserting values from another table into a column where values are null

I have a table A with ID's only and another table B with two columns, ID and Product Number. The ID column in table B has nulls and Product Number has Product Numbers. I would like to update table B with the ID's in column in no specific order just so that the Product Number has ID's.
I have tried to use update but that has not worked, have tried insert but it just adds the ID's in A to the bottom of the list in B. Would like to do this in Microsoft SQL.
SQL code tried:
IF OBJECT_ID('tempdb..#ProductNum') IS NOT NULL DROP TABLE #ProductNum
SELECT ID
INTO #ProductNum
FROM Products
UPDATE [ProductCatalogue] PC
SET
PC.ID = Pn.ID
FROM #ProductNum Pn
INNER JOIN
[ProductCatalogue] PC
ON Pc.ID = Pn.ID
WHERE Pc.ID IS NULL
It sounds a lot like you would be better off having the ID-Column Autoincrement, instead of giving it the IDs from table A. This is already explained in this answer.
In case you actually need the specific IDs from table A, this SO thread might help you.
Solved the issue by creating auto increment columns on each table and called it Row_ID. Then I used Row_ID to join the tables together with some logic provided by #Chris above.

Can we join two parts of two composite primary keys together?

I have to two tables, both have a composite primary key:
OrderNr + CustNr
OrderNr + ItemNr
Can I join both tables with the OrderNr and OrderNr which is each a part of a composite primary key?
Yes, but you may find you get rows from each table that repeat as they combine to make a unique combination. This is called a Cartesian product
Table A
OrderNr, CustNr
1,C1
1,C2
2,C1
2,C2
TableB
OrderNr,ItemNr
1,i1
1,i2
SELECT * FROM a JOIN b ON a.OrderNr = b.OrderNr
1,C1,1,i1
1,C1,1,i2
1,C2,1,i1
1,C2,1,i2
This happens because composite primary keys can contain repeated elements so long as the combination of elements is unique. Joining on only one part of the PK, and that part being an element that is repeated (my custnr 1 repeats twice in each table, even though the itemnr and CustNr mean the rows are unique) results in a multiplied resultset - 2 rows from A that are custnr 1, multiplied by 2 rows from B that are custnr 1, gives 4 rows in total
Does it work with the normal/naturla join too?
Normal joins (INNER, LEFT OUTER, RIGHT OUTER, FULL OUTER) will join the rows from two tables or subqueries when the ON condition is valid. The clause in the ON is like a WHERE clause, yes - in that it represents a statement that is true or false (a predicate). If the statement is true, the rows are joined. You don't even have to make it about data from the tables - you can even say a JOIN b ON 1=1 and every rows from A will get joined to every row from B. As commented, primary keys aren't involved in JOINS at all, though primary keys often rely on indexes and those indexes may be used to speed up a join, but they aren't vital to it.
Other joins (CROSS, NATURAL..) exist; a CROSS join is like the 1=1 example above, you don't specify an ON, every row from A is joined to every row from B, by design. NATURAL JOIN is one to avoid using, IMHO - the database will look for column names that are the same in both tables and join on them. The problem is that things can stop working in future if someone adds a column with the same name but different content/meaning to the two tables. No serious production system I've ever come across has used NATURAL join. You can get away with some typing if your columns to join on are named the same, with USING - SELECT * FROM a JOIN b USING (col) - here both A and B have a column called col. USING has some advantages, especially over NATURAL join, in that it doesn't fall apart if another column of the same name as an existing one but it has some detractors too - you can't say USING(col) AND .... Most people just stick to writing ON, and forget USING
NATURAL join also does NOT use primary keys. There is no join style (that I know of) that will look at a foreign key relationship between two tables and use that as the join condition
And then is it true that if I try to join Primary key and foreign key of two tables, that it works like a "where" command?
Hard to understand what you mean by this, but if you mean that A JOIN B ON A.primarykey = B.primary_key_in_a then it'll work out, sure. If you mean A CROSS JOIN B WHERE A.primarykey = B.primary_key_in_a then that will also work, but it's something I'd definitely avoid - no one writes SQLs this way, and the general favoring is to drop use of WHERE to create joining conditions (you do still see people writing the old school way of FROM a,b WHERE a.col=b.col but it's also heavily discouraged), and put them in the ON instead
So, in summary:
SELECT * FROM a JOIN b ON a.col1 = b.col2
Joins all rows from a with all rows from b, where the values in col1 equal the values in col2. No primary keys are needed for any of this to work out
You can join any table if there is/are logical relationship between them
select *
from t1
JOIN t2
on t1.ORderNr = t2.OrderNr
Although if OrderNr cannot provide unicity between tables by itself, your data will be multiplied.
Lets say that you have 2 OrderNr with value 1 on t1 and 5 OrderNr with value 1 on t2, when you join them, you will get 2 x 5 = 10 records.
Your data model is similar to a problem commonly referred to as a "fan trap". (If you had an "order" table keyed solely by OrderNr if would exactly be a fan trap).
Either way, it's the same problem -- the relationship between Order/Customers and Order/Items is ambiguous. You cannot tell which customers ordered which items.
It is technically possible to join these tables -- you can join on any columns regardless of whether they are key columns or not. The problem is that your results will probably not make sense, unless you have more conditions and other tables that you are not telling us about.
For example, a simple join just on t1.OrderNr = t2.OrderNr will return rows indicating every customer related to the order has ordered every item related to the order. If that is what you want, you have no problem here.

Database design: table with NULL keys

I'm designing a table that will hold numeric values for 2-3 situations of data:
Situation 1: has Age and Sex, along with the numeric value
Situation 2: has only Age, along with the numeric value
Situation 3: has only Sex, along with the numeric value
I don't want to create 3 different tables. Instead, only one table, with the following columns:
AgeID (references a table that contains information about the Age)
SexID (references a table that contains information about the Age)
Value (the numeric value itself)
AgeID and SexID as Foreign Keys and linked to the appropriate tables.
The problem is: my query is always doing a INNER JOIN with Age and Sex tables. For Situation 1 it works well because values are present. For Situations 2 and 3 I don't get any data, because either AgeID or SexID is null.
What solution is the correct one?
Change something in the table design?
Use Entity-Attribute-Value table to be more generic?
Use LEFT JOIN instead of INNER JOIN for all queries involving the nullable columns??
Any other idea?
Could someone clarify?
Thanks!
Yes an outer Join, Left or right, the Inner join is meant to filter out everything that doesn't have a match in both tables.
Use a conditional INNER JOIN, like
INNER JOIN Table x ON
(AgeID IS NULL OR AgeID = x.AgeID)
AND (SexID IS NULL OR SexID = x.SexID)

Full outer join joining together every record multiple times

Query below:
select
cu.course_id as 'bb_course_id',
cu.user_id as 'bb_user_id',
cu.role as 'bb_role',
cu.available_ind as 'bb_available_ind',
CASE cu.row_status WHEN 0 THEN 'ENABLED' ELSE 'DISABLED' END AS 'bb_row_status',
eff.course_id as 'registrar_course_id',
eff.user_id as 'registrar_user_id',
eff.role as 'registrar_role',
eff.available_ind as 'registrar_available_ind',
CASE eff.row_status WHEN 'DISABLE' THEN 'DISABLED' END as 'registrar_row_status'
into enrollments_comparison_temp
from narrowed_users_enrollments cu
full outer join enrollments_feed_file eff on cu.course_id = eff.course_id
Quick background: I'm taking the data from a replicated table and selecting it into narrowed_users_enrollments based on some criteria. In a script I'm taking a text feed file, with enrollment data, and inserting it into enrollments_feed_file. The purpose is to compare the most recent enrollment data with enrollments already in the database.
However the issue is that joining these tables results in about 160,000 rows when I'm really only expecting about 22,000. The point of doing this comparison is so that I can look for nulled values on either side of the join. For example, if the table on the right contains a null, then disable the enrollment record. If the table on the left contains a null, then add this student's enrollment.
I know it's a little off because I'm not using PKs or FKs. This is what is selected into the table:
Here's a screenshot showing a select * from the enrollments table on the left and a feed file on the right.
http://i.imgur.com/0ZPZ9HS.png
Here's a screenshot showing the newly created table from the full outer join.
http://i.imgur.com/89ssAkS.png
As you can see even though there there's only one matching enrollment(the matching jmartinez12 columns), there's 4 extra rows created for the same record on the left for the enrollments on the right. What I'm trying to get is for it to be 5 rows, with the first being how it is in the screenshot(matching pre-existing enrollment and enrollment in the feed file), BUT, the next 4 rows with the bb_* columns should be NULL up to the registrar_course_id.
Am I overlooking something simple here? I've tried a select distinct and I've added a where clause specifying when the course_ids are equal however that ensures that I won't get null rows which I need. I have also joined the tables on the user_id however the results are still the same.
One quick suggestion is to add the DISTNCT clause. If the records you are setting are complete duplicates that may cut it down to what you are expecting.
The fix was to also join on:
ON cu.course_id = eff.course_id AND cu.user_id = eff.user_id

How can I compare two tables and delete on matching fields (not matching records)

Scenario: A sampling survey needs to be performed on membership of 20,000 individuals. Survey sample size is 3500 of the total 20000 members. All membership individuals are in table tblMember. Same survey was performed the previous year and members whom were surveyed are in tblSurvey08. Membership data can change over the year (e.g. new email address, etc.) but the MemberID data stays the same.
How do I remove the MemberID/records contained tblSurvey08 from tblMember to create a new table of potential members to be surveyed (lets call it tblPotentialSurvey09). Again the record for a individual member may not match from the different tables but the MemberID field will remain constant.
I am fairly new at this stuff but I seem to be having a problem Googling a solution - I could use the EXCEPT function but the records for the individuals members are not necessarily the same from one table to next - just the MemberID may be the same.
Thanks
SELECT
* (replace with column list)
FROM
member m
LEFT JOIN
tblSurvey08 s08
ON m.member_id = s08.member_id
WHERE
s08.member_id IS NULL
will give you only members not in the 08 survey. This join is more efficient than a NOT IN construct.
A new table is not such a great idea, since you are duplicating data. A view with the above query would be a better choice.
I apologize in advance if I didn't understand your question but I think this is what you're asking for. You can use the insert into statement.
insert into tblPotentialSurvey09
select your_criteria from tblMember where tblMember.MemberId not in (
select MemberId from tblSurvey08
)
First of all, I wouldn't create a new table just for selecting potential members. Instead, I would create a new true/false (1/0) field telling if they are eligible.
However, if you'd still want to copy data to the new table, here's how you can do it:
INSERT INTO tblSurvey00 (MemberID)
SELECT MemberID
FROM tblMember m
WHERE NOT EXISTS (SELECT 1 FROM tblSurvey09 s WHERE s.MemberID = m.MemberID)
If you just want to create a new field as I suggested, a similar query would do the job.
An outer join should do:
select m_09.MemberID
from tblMembers m_09 left outer join
tblSurvey08 m_08 on m_09.MemberID = m_08.MemberID
where
m_08.MemberID is null