Sql Trigger Error explaination? - sql

I have been working on this trigger for a while and seem to be stuck. I feel that my logic is somewhat correct but when I run the code, I get this error message:
Msg 213, Level 16, State 1, Procedure Message_Student_ForInsert, Line 9
Column name or number of supplied values does not match table definition.
Here is my code:
Create Trigger Message_Student_ForInsert
ON CourseEnrolled
For Insert
AS
Begin
Declare #Id int
Select #Id = StudentID from inserted
insert into CourseAudit
Values ('New Student with Id = ' + Cast(#Id as nvarchar(10)) + ' is added at ' + cast(GetDate() as nvarchar(30)))
END
I have created a table name CourseAudit as well in an attempt to try and solve this error. I thought the error meant that I am missing values inside of the tables - since I haven't inserted any yet. Any help would be appreciated, Thanks!

Your INSERT statement is incorrect. Learn more here; INSERT()
Based on your code, one assumes the table [CourseAudit] exists and has a nvarchar() column in it.
You need to specify the column name for the insert.
If you omit the column name, then you should suply a value for ALL columns, in the order on which they appear in the table definition.
Example:
insert into CourseAudit (SomeCulumnNameHere)
Values ('New Student with Id = ' + Cast(#Id as nvarchar(10))
+ ' is added at ' + cast(GetDate() as nvarchar(30)))

Related

The column "GeoLocation" cannot be modified because it is either a computed column or is the result of a UNION operator

Here's the insert code.
CREATE TRIGGER InsertedPoint
ON Points
instead of insert
AS
insert into Points(Route_Id,Title,Description,Latitude,Longitude,GeoLocation)
SELECT Route_Id,Title,Description,Latitude,Longitude,
geography::STPointFromText('POINT(' + CAST([Latitude] AS VARCHAR(20)) + ' ' + CAST([Longitude] AS VARCHAR(20)) + ')', 4326)
FROM inserted
The problem about valuing of geography. When i create trigger i get error The column "GeoLocation" cannot be modified because it is either a computed column or is the result of a UNION operator.. How can i fix problem ?
So it's Points table
You can't insert values in a computed column. The principle of computed columns is that you define a computation rule, and then your RDBMS automatically manages it, computing values from other columns as needed.
Without this colum, your trigger boils down to:
CREATE TRIGGER InsertedPoint
ON Points
INSTEAD OF INSERT
AS
INSERT INTO Points(Route_Id,Title,Description,Latitude,Longitude)
SELECT Route_Id,Title,Description,Latitude,Longitude
FROM inserted
As it is, this trigger is a no-operation. Unless it does something else that what you have showed us, you would better just remove it.

SQL Merge statement with input table name

I have a scenario to copy the data from one table to another table. While copying, I need to store the Identity values of table1 and table2.
create procedure procedure1
(#sourceClientNumber int,
#destinationClientNumber int,
#statusID varchar(10))
as
declare #scriptSql nvarchar(max);
declare #Temp_ClientScriptTable table
(newScriptID int,
oldScriptID int);
begin
SET #CreatedBy = 'Initaial Creation'
SET #CreatedByName= 'Initial Creation'
SET #CreatedDateTime = GETDATE()
SET #scriptSql = 'Merge into [dbo].[Client_'+#destinationClientNumber +'_Script] using
(select ScriptID,
ScriptName,
ScriptVersion,
Description,
FacilityID,
StatusID,
Shared,
ScriptTYpeID
from [dbo].[Client_'+#sourceClientNumber +'_Script]
where statusID = ' +#statusID + '
) scripts on 1 = 0
When not matched then
insert ([ScriptName],
[ScriptVersion],
[CreatedBy],
[CreatedByName],
[CreatedDateTime],
[Description],
[FacilityID],
[StatusID],
[Shared],
[ScriptTypeID])
values (scripts.ScriptName,
scripts.ScriptVersion,'
+ #CreatedBy + ','
+ #CreatedByName + ','
+ #CreatedDateTime + ',
scripts.Description,
scripts.FacilityID,
scripts.StatusID,
scripts.Shared,
scripts.ScriptTypeID)
output Inserted.ScriptID, scripts.ScriptID
into' + #Temp_ClientScriptTable + '(newScriptID, oldScriptID)'
EXECUTE sp_executesql #scriptSql
I am getting the error at #Temp_ClientScriptTable.
Could you please help me on this..
The main problem here is faulty database design.
The fact that you have multiple tables with the same structure, only different by a number inside the name - means you are mixing data (the number) and meta data (the table name).
Instead of having a different table for each client, you should add the client number as a column to a single table.
If you can do that, it will also eliminate the need for using dynamic SQL everywhere you need to address this table.
Root cause is, #Temp_ClientScriptTable is a table variable and cannot participate in string concatenation.
Your last line of #scriptSql should be -
into #Temp_ClientScriptTable(newScriptID, oldScriptID)'
instead of
into' + #Temp_ClientScriptTable + '(newScriptID, oldScriptID)'
Also you need to add single quotes around varchar variables while using them in string concatenation. Your final #scriptSql will be -
SET #scriptSql = 'Merge into [dbo].[Client_'+#destinationClientNumber +'_Script] using
(select ScriptID,
ScriptName,
ScriptVersion,
Description,
FacilityID,
StatusID,
Shared,
ScriptTYpeID
from [dbo].[Client_'+#sourceClientNumber +'_Script]
where statusID = ''' +#statusID + '''
) scripts on 1 = 0
When not matched then
insert ([ScriptName],
[ScriptVersion],
[CreatedBy],
[CreatedByName],
[CreatedDateTime],
[Description],
[FacilityID],
[StatusID],
[Shared],
[ScriptTypeID])
values (scripts.ScriptName,
scripts.ScriptVersion,'''
+ #CreatedBy + ''','''
+ #CreatedByName + ''','
+ #CreatedDateTime + ',
scripts.Description,
scripts.FacilityID,
scripts.StatusID,
scripts.Shared,
scripts.ScriptTypeID)
output Inserted.ScriptID, scripts.ScriptID
into #Temp_ClientScriptTable(newScriptID, oldScriptID)'
To debug these kind of issue, you should always print the the concatenated query and see how your query will look like -
print #scriptSql

Generating a unique batch id (SQL Server)

This is possible 2x questions in 1x. Sorry about that, but here goes:
PROBLEM
I am creating a unique batch id everytime a user uploads some data to SQL Server. Currently, I do this by looking at the last value of the 'Identity Specification' and add +1 to that.
Problem arises, as you might have guessed, if multiple users input data at the same, they both would get the same batch id...
Possible Solution
In order to mitigate this issue, I have come up with this method to generate 3 letter + random number; and the (last id value + 1):
DECLARE #tmp CHAR(3) = CHAR(CAST(RAND()*26 AS int)+65) + CHAR(CAST(RAND()*26 AS int)+65) + CHAR(CAST(RAND()*26 AS int)+65);
SELECT #tmp;
select cast(RAND()*9999 as int)
(1) I am not sure how to concatenate this into one line of string.
(2) The other question, is there a way to 100% guarantee every user is given a unique batch id every time they submit a request, regardless of how many are doing it simultaneously?
I would really appreciate your input in this.
1 - Concatenation part is very simple, you can do the following:
DECLARE #tmp VARCHAR(10);
SET #tmp = CHAR(CAST(RAND()*26 AS int)+65)
+ CHAR(CAST(RAND()*26 AS int)+65)
+ CHAR(CAST(RAND()*26 AS int)+65)
+ CAST(cast(RAND()*9999 as int) AS VARCHAR(4));
SELECT #tmp;
2 - I would suggest to populate a table with the Random values you would like to issue to users and then select from it, to avoid the race-condition.
Create a table called BatchNumbers with two Columns BatchNumber and Used.
Populate the batch number table and 0 as default value for Used Column.
Then everytime you need a batch number do the following.
CREATE PROC dbo.usp_Get_BatchNumber
#BatchNumber VARCHAR(10) OUTPUT
AS
BEGIN
SET NOCOUNT ON;
Declare #t TABLE (BN VARCHAR(10));
UPDATE TOP (1) BatchNumbers
SET Used = 1
OUTPUT inserted.BatchNumber INTO #t (BN )
WHERE Used = 0;
SELECT #BatchNumber = BN FROM #t;
END
You need an "Upload" table with a Bigint Identity column for the BatchID, then add a new row for every user upload.
The server will maintain the correct values and prevent collisions.
I would use the built in function for this:
select newid()
> 240CA878-135E-4176-AE57-0FA83FF74037
For the first problem, you can either create a variable for your random number as a char(4) and just simply concatenate the 2, or create it as an int and then CAST it as a VARCHAR while concatenating. Everything that is concatenated into a string must be a string.
DECLARE #tmp CHAR(3) = CHAR(CAST(RAND()*26 AS int)+65) + CHAR(CAST(RAND()*26 AS int)+65) + CHAR(CAST(RAND()*26 AS int)+65);
SELECT #tmp;
DECLARE #randNum VARCHAR(4) = CAST(RAND()*9999 AS INT)
-- OR DECLARE #randNum INT = CAST(Rand()*9999) AS INT)
SELECT #randNum
DECLARE #batchID VARCHAR(MAX) = #tmp + #randNum
-- OR DECLARE #batchID VARCHAR(MAX) = #tmp + CAST(#randNum AS VARCHAR)
SELECT #batchID
try the following:
1)
DECLARE #tmp CHAR(7) = CHAR(CAST(RAND()*26 AS int)+65) + CHAR(CAST(RAND()*26 AS int)+65) + CHAR(CAST(RAND()*26 AS int)+65) + cast(cast(RAND()*9999 as int) as varchar(4));
SELECT #tmp;
2) Yes, I think so.
I upvoted Terry Carmen's answer, but from his comments it sounds like he's suggesting something different from what I first thought, so here's a complete example. I think you want a table that has a key defined with the IDENTITY property, which will tell SQL Server that you want unique, sequential values in that column and you want the database to worry about the details of guaranteeing that this is so.
create table dbo.Import
(
-- identity(1, 1) means that SQL Server will automatically assign values for
-- this column when you insert a record, with 1 being the first value
-- assigned and each subsequent value incrementing by 1.
Identifier bigint not null identity(1, 1),
-- This column for illustration only; replace it with whatever data you need
-- to store.
YourStuffHere varchar(max)
);
-- Now simply use any INSERT or MERGE command against dbo.Import, and omit the
-- Identifier column from the list of columns whose values the command supplies.
-- Then you can use the SCOPE_IDENTITY() function or an OUTPUT clause to capture
-- the Identifier value that SQL Server has inserted.
-- Example 1: INSERT with explicit values and OUTPUT.
insert dbo.Import
(YourStuffHere)
output
inserted.Identifier
values
('Example 1');
-- Example 2: INSERT/SELECT with OUTPUT.
insert dbo.Import
(YourStuffHere)
output
inserted.Identifier
select
'Example 2';
-- Example 3: INSERT with SCOPE_IDENTITY().
insert dbo.Import
(YourStuffHere)
values
('Example 3');
select Identifier = convert(bigint, scope_identity());
-- Show table contents.
select * from dbo.Import;
The first INSERT statement above produces the following result:
Identifier
1
The second:
Identifier
2
The SELECT following the third INPUT gives:
Identifier
3
And the final SELECT shows you the contents of the table:
Identifier YourStuffHere
1 Example 1
2 Example 2
3 Example 3
This is the easiest way to go about this as it allows SQL Server to do all the real work for you. Please let me know if I've misunderstood your requirements.

SQL Server 2012. Copy row into single column on another table

I am working with SQL Server on the AdventureWorks2012 Database. I am working with triggers. I would like to copy any new inserted row into one single column in another table called AuditTable. Basically whenever I insert into the parson.address table, I would like to copy all of the rows into the AuditTable.prevValue column. I know how to insert etc, I am not sure how to write to one column.
Here is the general idea.
USE [AdventureWorks2012]
ALTER TRIGGER [Person].[sPerson] ON [Person].[Address]
FOR INSERT AS INSERT INTO AdventureWorks2012.HumanResources.AuditTable(PrevValue) select
AddressID,AddressLine1,AddressLine2,City, StateProvinceID, PostalCode, SpatialLocation, rowguid, ModifiedDate FROM Inserted
ERROR: The select list for the INSERT statement contains more items than the insert list. The number of SELECT values must match the number of INSERT columns.
Thank you for any assistance. I have searched loads but cannot find the exact solution anywhere.
The error message says it all - you can't insert 9 columns of different types into a single column. Assuming that your destination AuditTable.PrevValue column is NVARCHAR(), you could flatten your insert as follows, by concatenating the columns and casting non-char columns to n*char:
INSERT INTO AdventureWorks2012.HumanResources.AuditTable(PrevValue)
SELECT
N'ID : ' + CAST(AddressID AS NVARCHAR(20)) + N'Address: ' + AddressLine1 +
N', ' +AddressLine2 + ....
FROM Inserted
IMO keeping one long string like this makes the Audit table difficult to search, so you might consider adding SourceTable and possibly Source PK columns.
You could also consider converting your row to Xml and storing it as an Xml column, like this:
create table Audit
(
AuditXml xml
);
alter trigger [Person].[sPerson] ON [Person].[Address] for INSERT AS
begin
DECLARE #xml XML;
SET #xml =
(
SELECT *
FROM INSERTED
FOR XML PATH('Inserted')
);
insert into [MyAuditTable](AuditXml) VALUES (#xml);
end

Dynamic table design (common lookup table), need a nice query to get the values

sql2005
This is my simplified example:
(in reality there are 40+ tables in here, I only showed 2)
I got a table called tb_modules, with 3 columns (id, description, tablename as varchar):
1, UserType, tb_usertype
2, Religion, tb_religion
(Last column is actually the name of a different table)
I got an other table that looks like this:
tb_value (columns:id, tb_modules_ID, usertype_OR_religion_ID)
values:
1111, 1, 45
1112, 1, 55
1113, 2, 123
1114, 2, 234
so, I mean 45, 55, 123, 234 are usertype OR religion ID's
(45, 55 usertype, 123, 234 religion ID`s)
Don't judge, I didn't design the database
Question
How can I make a select, showing * from tb_value, plus one column
That one column would be TITLE from the tb_usertype or RELIGIONNAME from the tb_religion table
I would like to make a general thing.
Was thinking initially about maybe a SQL function that returns a string, but I think I would need dynamic SQL, which is not ok in a function.
Anyone a better idea ?
At the beginning we have this -- which is quite messy.
To clean-up a bit I add two views and a synonym:
create view v_Value as
select
ID as ValueID
, tb_modules_ID as ModuleID
, usertype_OR_religion_ID as RemoteID
from tb_value ;
go
create view v_Religion as
select
ID
, ReligionName as Title
from tb_religion ;
go
create synonym v_UserType for tb_UserType ;
go
And now the model looks like
It is easier now to write the query
;
with
q_mod as (
select
m.ID as ModuleID
, coalesce(x1.ID , x2.ID) as RemoteID
, coalesce(x1.Title , x2.Title) as Title
, m.Description as ModuleType
from tb_Modules as m
left join v_UserType as x1 on m.TableName = 'tb_UserType'
left join v_Religion as x2 on m.TableName = 'tb_Religion'
)
select
a.ModuleID
, v.ValueID
, a.RemoteID
, a.ModuleType
, a.Title
from q_mod as a
join v_Value as v on (v.ModuleID = a.ModuleID and v.RemoteID = a.RemoteID) ;
There is an obvious pattern in this query, so it can be created as dynamic sql if you have to add another module-type table. When adding another table, use ID and Title to avoid having to use a view.
EDIT
To build dynamic sql (or query on application level)
Modify lines 6 and 7, the x-index is tb_modules.id
coalesce(x1. , x2. , x3. ..)
Add lines to the left join (below line 11)
left join v_SomeName as x3 on m.TableName = 'tb_SomeName'
The SomeName is tb_modules.description and x-index is matching tb_modules.id
EDIT 2
The simplest would probably be to package the above query into a view and then each time the schema changes dynamically crate and run ALTER VIEW. This way the query would not change from the point of the application.
Since we're all agreed the design is flaky, I'll skip any comments on that. The pattern of the query is this:
-- Query 1
select tb_value.*,tb_religion.religion_name as ANY_DESCRIPTION
from tb_value
JOIN tb_religion on tb_value.ANY_KIND_OF_ID = tb_religion.id
WHERE tb_value.module_id = 2
-- combine it with...
UNION ALL
-- ...Query 2
select tb_value.*,tb_religion.title as ANY_DESCRIPTION
from tb_value
JOIN tb_userType on tb_value.ANY_KIND_OF_ID = tb_userType.id
WHERE tb_value.module_id = 1
-- combine it with...
UNION ALL
-- ...Query 3
select lather, rinse, repeat for 40 tables!
You can actually define a view that hardcodes all 40 cases, and then put filters onto queries for the particular modules you want.
To do this dynamically you need to be able to create a sql statement that looks like this
select tb_value.*, tb_usertype.title as Descr
from tb_value
inner join tb_usertype
on tb_value.extid = tb_usertype.id
where tb_value.tb_module_id = 1
union all
select tb_value.*, tb_religion.religionname as Descr
from tb_value
inner join tb_religion
on tb_value.extid = tb_religion.id
where tb_value.tb_module_id = 2
-- union 40 other tables
Currently you can not do that because you do not have any information in the db telling you which column to use from tb_religion and tb_usertype etc. You can add that as a new field in tb_module.
If you have fieldname to use in tb_module you can build a view that does what you want.
And you could add a trigger to table tb_modules that alters the view whenever tb_modules is modified. That way you do not need to use dynamic sql from the client when doing queries. The only thing you need to worry about is that the table needs to be created in the db before you add a new row to tb_modules
Edit 1
Of course the code in the trigger needs to dynamically build the alter view statement.
Edit 2 You also need to have a field with information about what column in tb_usertype and tb_religion etc. to join against tb_value.extid (usertype_OR_religion_ID). Or you can assume that the field will always be called id
Edit 3 Here is how you could build the trigger on tb_module that alters the view v_values. I have added fieldname as a column in tb_modules and I assume that the id field in the related tables is called id.
create trigger tb_modules_change on tb_modules after insert, delete, update
as
declare #sql nvarchar(max)
declare #moduleid int
declare #tablename varchar(50)
declare #fieldname varchar(50)
set #sql = 'alter view v_value as '
declare mcur cursor for
select id, tablename, fieldname
from tb_modules
open mcur
fetch next from mcur into #moduleid, #tablename, #fieldname
while ##FETCH_STATUS = 0
begin
set #sql = #sql + 'select tb_value.*, '+#tablename+'.'+#fieldname+' '+
'from tb_value '+
'inner join '+#tablename+' '+
'on tb_value.extid = '+#tablename+'.id '+
'where tb_value.tb_module_id = '+cast(#moduleid as varchar(10))
fetch next from mcur into #moduleid, #tablename, #fieldname
if ##FETCH_STATUS = 0
begin
set #sql = #sql + ' union all '
end
end
close mcur
deallocate mcur
exec sp_executesql #sql
Hm..there are probably better solutions available but here's my five cents:
SELECT
id,tb_modules_ID,usertype_OR_religion_ID,
COALESCE(
(SELECT TITLE FROM tb_usertype WHERE Id = usertype_OR_religion_ID),
(SELECT RELIGIONNAME FROM tb_religion WHERE Id = usertype_OR_religion_ID),
'N/A'
) AS SourceTable
FROM tb_valuehere
Note that I don't have the possibility to check the statement right now so I'm reserving myself for any syntax errors...
First, using your current design the only reasonable solution is dynamic SQL. You should write a module in your middle-tier that queries for the appropriate table names and builds the queries on the fly. Trying to accomplish that in T-SQL will be a nightmare. T-SQL was not designed for string construction.
The right solution is to build a new database designed properly, migrate the data and scrap the existing design. The problems you will encounter with your current design will simply grow. It will be harder for new developers to learn the new system. It will be prone to errors. There will be no data integrity (e.g. forcing the attribute "Start Date" to be parsable as a date). Custom queries will be a chore to write and so on. Eventually, you will hit the day when the types of information desired from the system are simply too difficult to extract given the current design.
First take the undesigner out the back and put them out of their misery. They are hurting people.
Due to their incompetence, every time you add a row to Module, you have to modify every query that uses it. Good for www.dailywtf.com.
You do not have Referential Integrity either, because you cannot define an FK on the this_or_that column. Your data is exposed, probably to "code" written by the same undesigner. No doubt you are aware that this is where the deadlocks are created.
That it is a "judgement", that is so that you understand the gravity of the undesign, and you can justify replacing it, to your managers.
SQL was designed for Relational Databases, that means Normalised. It is not good for mangled files. Sure, some queries may be better than others (just look at the answers), but there is no way to get around the undesign, any SQL query will be hamstrung, and need change whenever a Module row is added.
"Dynamic" is reserved for Databases, not possible for flat flies.
Two answers. One to stop the continuing idiocy of changing the existing queries every time a Module row is added (you're welcome); the second to answer your question.
Safe Future Queries
CREATE VIEW UserReligion_vw AS
SELECT [XxxxId] = id, -- replace Xxxx
[ReligionId] = usertype_OR_religion_ID
FROM tb_value
WHERE tb_modules_ID = 1
CREATE VIEW UserReligion_vw AS
SELECT [XxxxId] = id,
[ReligionId] = usertype_OR_religion_ID
FROM tb_value
WHERE tb_modules_ID = 2
From now on, make sure the all queries currently using the undesign, are modified to use the correct View instead. Do not use the Views for Update/Delete/Insert.
Answer
Ok, now for the main question. I can think of other approaches, but this one is the best. You have stated, you want the third column to also be an unnormalised piece of chicken excreta and the supply Title for [EITHER_Religion_OR_UserType_OR_This_OR_That]. Right, so you are teaching the user to be confused as well; when the no of modules grow, they will have great fun figuring out what the column contains. Yes a problem does always compound itself.
SELECT [XxxxId] = id,
[Whatever] = CASE tb_modules_ID
WHEN 1 THEN ( SELECT name -- title, whatever
FROM tb_religion
WHERE id = V.usertype_OR_religion_ID
)
WHEN 2 THEN ( SELECT name -- title, whatever
FROM tb_usertype
WHERE id = V.usertype_OR_religion_ID
)
ELSE "(UnknownModule)" -- do not remove the brackets
END
FROM tb_value V
WHERE conditions... -- you need something here
This is called a Correlated Scalar Subquery.
It works on any version of Sybase since 4.9.2 with no limitations. And SQL 2005 (last time I looked, anyway, Aug 2009). But on MS you will get a StackTrace if the volume of tb_value is large, so make sure the WHERE clause has some conditions on it.
But MS have broken the server with their "new" 2008 codeline, so it does not work in all circumstances (the worse your mangled files, the less likely it will work; the better your database design, the more likely it will work). That is why some MS people pray every day for the next Service pack, and others never attend church.
I guess you want something like this:
Adding tables and one row per table into tb_modules is straight forward.
SET NOCOUNT ON
if OBJECT_ID('tb_modules') > 0 drop table tb_modules;
if OBJECT_ID('tb_value') > 0 drop table tb_value;
if OBJECT_ID('tb_usertype') > 0 drop table tb_usertype;
if OBJECT_ID('tb_religion') > 0 drop table tb_religion;
go
create table dbo.tb_modules (
id int,
description varchar(20),
tablename varchar(255)
);
insert into tb_modules values ( 1, 'UserType', 'tb_usertype');
insert into tb_modules values ( 2, 'Religion', 'tb_religion');
create table dbo.tb_value(
id int,
tb_modules_ID int,
usertype_OR_religion_ID int
);
insert into tb_value values ( 1111, 1, 45);
insert into tb_value values ( 1112, 1, 55);
insert into tb_value values ( 1113, 2, 123);
insert into tb_value values ( 1114, 2, 234);
create table dbo.tb_usertype(
id int,
UserType varchar(30)
);
insert into tb_usertype values ( 45, 'User_type_45');
insert into tb_usertype values ( 55, 'User_type_55');
create table dbo.tb_religion(
id int,
Religion varchar(30)
);
insert into tb_religion values ( 123, 'Religion_123');
insert into tb_religion values ( 234, 'Religion_234');
-- start of query
declare #sql varchar(max) = null
Select #sql = case when #sql is null then ' ' else #sql + char(10) + 'union all ' end
+ 'Select ' + str(id) + ' type, id, ' + description + ' description from ' + tablename from tb_modules
set #sql = 'select v.id, tb_modules_ID , usertype_OR_religion_ID , t.description
from tb_value v
join ( ' + #sql + ') as t
on v.tb_modules_ID = t.type and v.usertype_OR_religion_ID = t.id
'
Print #sql
exec( #sql)
I think it's intended to be used with dynamic sql.
Maybe break out each tb_value.tb_modules_ID row into its own temp table, named with the tb_modules.tablename.
Then have an sp iterate through the temp tables matching your naming convention (by prefix or suffix) building the sql and doing your join.