Comparing records of two tables and discarding matches - asp.net

I am trying to implement an idea where I have two sql tables in a database.
Table Info which has a field Nationality and the other Table Exclusion which has a field Keyword.
`Info.Nationality` `Exclusion.Keyword`
|British| |France|
|resteraunt de France| |Spanish|
|German|
|Flag Italian|
|Spanish rice|
|Italian pasta
|Irish beef|
In my web application I am creating a GridView4 and through a DataTable and SqlDataAdapter I am populating that GridView4 with the SQL command:
SELECT DISTINCT Info.Nationality WHERE Exclusion.Keyword NOT LIKE '%Spanish%'
That SQL statement retrieves all the distinct records in Info.Nationality which do not contain the word spanish.
What I am currently doing is that in the web app which is in vb.net I am adding two different GridViews, each have the data of each table which means that GridView2 has DISTINCT Info.Nationality and GridView3 has Exclusion.Keyword and then adding another GridView4 to display the results of the above SQL command.
The idea is to retrieve all the distinct records from Info.Nationlity which are not suppressed by the keyword constraints in Exclusion.keyword. So from the above mentioned Sql command the GridView4 will retrieve all the records which do not have the word "Spanish".
I am doing all of this in a nested for loop where in the first loop it takes each record (one by one) from Info.Nationality e.g.for each row As DataRow in Me.GridView2.Rows() and compare it with the second for loop which goes till the end of the Exclusion.Keyword which would be like For i=0 To Gridview3 - 1.
The problem is that in that sql statement I have to explicitly specify the word to compare. I tried adding the records of Exclusion.Keyword in a String and then replacing the Spanish Keyword In between the NOT LIKE with the name of the String which is Keywords and then assigning the name a parameter with cmd.parameter.addwithvalue(#String, Keywords). However this is not working, it is only comparing with the last record in the string and ignoring the first.
The idea behind all of this is to display all the records of Info.Nationality in GridView4 which do not contain the keywords in Exclusion.Keyword.
Is there an easier or more effecient way to do this? I was thinking of an Inner Join with a Like command but that is not my problem. My problem is that how can I compare each record one by one of Info.Nationlity with all the records in Exclusion.keyword and then retrieving the ones that do not match and discarding the ones that match.
Then in Gridview4 how can I edit the records without reflecting those changes or affecting in Info.Nationality but rather only Inserting to Exclusion.Keyword the changes.
SOLVED by adding ToString() after Text
In my asp.net web app, I tried this, but didn't work: (SOLVED)
`SELECT DISTINCT Nationality
FROM Info Where NOT EXISTS(SELECT * FROM Exclusion WHERE Info.Nationality LIKE '%' + #GridView +'%')`
`cmd.parameters.AddwithValue("#GridView", GridView3.Rows(i).Cells(0).Text.ToString())`
GridView3 Here has the Exclusion.Keywords data.
Would really appreciate your suggestions and thoughts around this.

You do not need to do this one-by-one, or "Row by agonizing row" as some DBAs are fond of describing this type of approach. There are lots of ways to write a query to only return the records from Info.nationality that do not match the exclusion keywords as a single expression.
My preference is to use the EXISTS clause and a correlated subquery:
SELECT Nationality
FROM Info I
WHERE NOT EXISTS(SELECT * FROM Exclusion WHERE I.Nationality LIKE '%' + Keyword + '%')
You can also express this as a left join.
SELECT I.Nationality
FROM Info I
LEFT OUTER JOIN Exclusion E
ON I.Nationality LIKE '%' + E.Keyword + '%'
WHERE E.Keyword IS NULL
The left join will return all the rows from info and insert nulls in the columns for Exclusion except where the join criteria matches. By filtering for only where those values are null, you can avoid the matches.

Related

SQLite: treat non-existent column as NULL

I have a query like this (simplified and anonymised):
SELECT
Department.id,
Department.name,
Department.manager_id,
Employee.name AS manager_name
FROM
Department
LEFT OUTER JOIN Employee
ON Department.manager_id = Employee.id;
The field Department.manager_id may be NULL. If it is non-NULL then it is guaranteed to be a valid id for precisely one row in the Employee table, so the OUTER JOIN is there just for the rows in the Department table where it is NULL.
Here is the problem: old instances of the database do not have this Department.manager_id column at all. In those cases, I would like the query to act as if the field did exist but was always NULL, so e.g. the manager_name field is returned as NULL. If the query only used the Department table then I could just use SELECT * and check for the column in my application, but the JOIN seems to make this impossible. I would prefer not to modify the database, partly so that I can load the database in read only mode. Can this be done just by clever adjustment of the query?
For completeness, here is an answer that does not require munging both possible schemas into one query (but still doesn't need you to actually do the schema migration):
Check for the schema version, and use that to determine which SELECT query to issue (i.e. with or without the manager_id column and JOIN) as a separate step. Here are a few possibilities to determine the schema version:
The ideal situation is that you already keep track of the schema by assigning version numbers to the schema and recording them in the database. Commonly this is done with either:
The user_version pragma.
A table called "Schema" or similar with one row containing the schema version number.
You can directly determine whether the column is present in the table. Two possibilities:
Use the table_info pragma to determine the list of columns in the table.
Use a simple SELECT * FROM Table LIMIT 1 and look at what columns are returned (this is probably better as it is independent of the database engine).
This seems to work:
SELECT
Dept.id,
Dept.name,
Dept.manager_id,
Employee.name AS manager_name
FROM
(SELECT *, NULL AS manager_id FROM Department) AS Dept
LEFT OUTER JOIN Employee
ON Dept.manager_id = Employee.id;
If the manager_id column is present in Department then it is used for the join, whereas if it is not then Dept.manager_id and Employee.name are both NULL.
If I swap the column order in the subquery:
(SELECT NULL AS manager_id, * FROM Department) AS Dept
then the Dept.manager_id and Employee.name are both NULL even if the Department.manager_id column exists, so it seems that Dept.manager_id refers to the first column in the Dept subquery that has that name. It would be good to find a reference in the SQLite documentation saying that this behaviour is guaranteed (or explicitly saying that it is not), but I can't find anything (e.g. in the SELECT or expression pages).
I haven't tried this with other database systems so I don't know if it will work with anything other than SQLite.

Efficient insertion of row and foreign table row if it does not exist

Similar to this question and this solution for PostgreSQL (in particular "INSERT missing FK rows at the same time"):
Suppose I am making an address book with a "Groups" table and a "Contact" table. When I create a new Contact, I may want to place them into a Group at the same time. So I could do:
INSERT INTO Contact VALUES (
"Bob",
(SELECT group_id FROM Groups WHERE name = "Friends")
)
But what if the "Friends" Group doesn't exist yet? Can we insert this new Group efficiently?
The obvious thing is to do a SELECT to test if the Group exists already; if not do an INSERT. Then do an INSERT into Contacts with the sub-SELECT above.
Or I can constrain Group.name to be UNIQUE, do an INSERT OR IGNORE, then INSERT into Contacts with the sub-SELECT.
I can also keep my own cache of which Groups exist, but that seems like I'm duplicating functionality of the database in the first place.
My guess is that there is no way to do this in one query, since INSERT does not return anything and cannot be used in a subquery. Is that intuition correct? What is the best practice here?
My guess is that there is no way to do this in one query, since INSERT
does not return anything and cannot be used in a subquery. Is that
intuition correct?
You could use a Trigger and a little modification of the tables and then you could do it with a single query.
For example consider the folowing
Purely for convenience of producing the demo:-
DROP TRIGGER IF EXISTS add_group_if_not_exists;
DROP TABLE IF EXISTS contact;
DROP TABLE IF EXISTS groups;
One-time setup SQL :-
CREATE TABLE IF NOT EXISTS groups (id INTEGER PRIMARY KEY, group_name TEXT UNIQUE);
INSERT INTO groups VALUES(-1,'NOTASSIGNED');
CREATE TABLE IF NOT EXISTS contact (id INTEGER PRIMARY KEY, contact TEXT, group_to_use TEXT, group_reference TEXT DEFAULT -1 REFERENCES groups(id));
CREATE TRIGGER IF NOT EXISTS add_group_if_not_exists
AFTER INSERT ON contact
BEGIN
INSERT OR IGNORE INTO groups (group_name) VALUES(new.group_to_use);
UPDATE contact SET group_reference = (SELECT id FROM groups WHERE group_name = new.group_to_use), group_to_use = NULL WHERE id = new.id;
END;
SQL that would be used on an ongoing basis :-
INSERT INTO contact (contact,group_to_use) VALUES
('Fred','Friends'),
('Mary','Family'),
('Ivan','Enemies'),
('Sue','Work colleagues'),
('Arthur','Fellow Rulers'),
('Amy','Work colleagues'),
('Henry','Fellow Rulers'),
('Canute','Fellow Ruler')
;
The number of values and the actual values would vary.
SQL Just for demonstration of the result
SELECT * FROM groups;
SELECT contact,group_name FROM contact JOIN groups ON group_reference = groups.id;
Results
This results in :-
1) The groups (noting that the group "NOTASSIGNED", is intrinsic to the working of the above and hence added initially) :-
have to be careful regard mistakes like (Fellow Ruler instead of Fellow Rulers)
-1 used because it would not be a normal value automatically generated.
2) The contacts with the respective group :-
Efficient insertion
That could likely be debated from here to eternity so I leave it for the fence sitters/destroyers to decide :). However, some considerations:-
It works and appears to do what is wanted.
It's a little wasteful due to the additional wasted column.
It tries to minimise the waste by changing the column to an empty string (NULL may be even more efficient, but for some can be confusing)
There will obviously be an overhead BUT in comparison to the alternatives probably negligible (perhaps important if you were extracting every Facebook user) but if it's user input driven likely irrelevant.
What is the best practice here?
Fences again. :)
Note Hopefully obvious, but the DROP statements are purely for convenience and that all other SQL up until the INSERT is run once
to setup the tables and triggers in preparation for the single INSERT
that adds a group if necessary.

PLSQL: How to update and Insert together when match found between source and target table

I have a source and a target table. I need to make update and insert together in final table with following conditions:
1) When SourceTable.ProductName = TargetTable.ProductName and SourceTable.Amount = TargetTable.Amount Then
Update TargetTable set TargetTable.ValidFrom = sysdate
Also i need to insert like this,
Insert Into TargetTable(ProductName,Amount,ValidFrom,Version) Values(TargetTable.ProductName,TargetTable.Amount, sysdate, TargetTable.Version + 1)
This is to maintain version.
2) When there is no match then i need to insert directly like this,
Insert Into TargetTable(ProductName,Amount,ValidFrom,Version) Values(SourceTable.ProductName,SourceTable.Amount, sysdate,1)
As of now i am thinking of capturing these matching records in a Cursor and perform Update and Insert together in cursor loop. But i am willing to avoid loops a i have approx 10 millions of matching records.
We also have Merge statement, but I believe Merge do not support Update and Insert together in single "WHEN MATCHED" Clause. Please correct me if i am wrong?
Kindly suggest the best way to achieve this behavior.

Query one for column headers, query 2 for row data

I would like to know how to use asp.net vb to create a gridview that has one query be the column headers then use those column headers to populate the table. I have accomplished this in asp classic but would like to know how to to it in .net. The following are my queries that accomplish this in asp classic.
Select DOA,GroupName From Groups Where Doa is not null Order by zDOA
I use that query to populate the top of my table, then use this to get the users and their groups.
SELECT DISTINCT TM.PersonID, GroupMembers.FullNm, TM.zUpdatedOn, Location.LocationName FROM GroupMembers INNER JOIN TM ON GroupMembers.PersonID = TM.PersonID Inner JOin Location ON Location.LocationID = GroupMembers.LocationID INNER JOIN Groups ON GroupMembers.GroupsID= Groups.ID WHERE (GroupMembers.zIsActive = 1) AND GroupMembers.LocationID = "&LocationID&" AND Groups.DOA IS NOT NULL order by FullNm
That query gets people's name, their location(from StringQuery) and then finds out which groups they are in. When it cycles through which groups they are in, it compares their groups to all of the groups. When they match I put a check box in the correct spot. I have attached a image of this. The RED is from the first Query and the BLUE is from the second query. My question is how to replicate this in .net. I am unable to put the first query as the columns and fill in the rest with the second query.
![Example]
The answer is to use the sql pivot function. http://www.codeproject.com/Tips/500811/Simple-Way-To-Use-Pivot-In-SQL-Query

Column ' ' in where clause is ambiguous Error in mysql

SELECT tbl_user.userid,
tbl_user.firstname,
tbl_user.lastname,
tbl_user.email,
tbl_user.created,
tbl_user.createdby,
tbl_organisation.organisationname
FROM tbl_user
INNER JOIN tbl_organisation
ON tbl_user.organisationid = tbl_organisation.organisationid
WHERE organisationid = #OrganisationID;
I am using this statement to do a databind. I am getting a error here.
Column 'OrganisationID' in where clause is ambiguous
What should I do is it wrong to name the OrganisationID in tbl_user same as tbl_organisation.
OrganisationID is a foreign key from tbl_Organisation
Since you have two columns with the same name on two different tables (and that's not a problem, it's even recommended on many cases), you must inform MySQL which one you want to filter by.
Add the table name (or alias, if you were using table aliases) before the column name. In your case, either
WHERE tbl_user.OrganisationID
or
WHERE tbl_Organisation.OrganisationID
should work.
You just need to indicate which table you are targeting with that statement, like "tbl_user.OrganisationID". Otherwise the engine doesn't know which OrganisationID you meant.
It is not wrong the have the same column names in two tables. In many (even most) cases, it is actually perferred.

Resources