I'm now using the sqldiff tool to calculate the difference between my database before a transaction was executed and after it was executed. I'm doing this as follows:
Copy the entire sqlite file to a tmp location (old-database.sqlite)
Perform the transaction (for ex. INSERT, SCHEMA CHANGE, ...)
Than sqldiff --primary-key old-database.sqlite database.sqlite
This outputs the changes that the transaction has done. Although this works perfectly because my database isn't that large, a few megabytes maximum. I think this could be done more efficiently.
Is there any mechanism that I can use to get the same output without copying the database. Maybe something with the journal?
Kind regards,
Daan
Perhaps a little awkward to implement and perhaps at some stage (size wise) inefficient, but you could use a combination of TRIGGERS (4 per monitored table is the awkward part) and a before after snapshot of sqlite_master.
That is you could, per table to be monitored, have a copy of that table (structure wise) appended with two columns. 1 to indicate the action (inserted, before update, after update or deletion), the 2nd (optional) to record the timestamp.
Then for each table to be monitored have AFTER INSERT, BEFORE DELETE and AFTER and BEFORE UPDATE TRIGGERS to copy the add an entry to the table's log table, which would be emptied before the transaction (if wanted, if not then timestamp would probably be a required column).
For schema changes again you could have a copy of sqlite_master before and then compare it against sqlite_master after the transaction (you can't have TRIGGERS applied to system tables).
Example, consider the following example/demo:-
CREATE TABLE IF NOT EXISTS mytable (id INTEGER PRIMARY KEY, mycolumn TEXT);
CREATE TABLE IF NOT EXISTS mytablelog AS SELECT * FROM mytable WHERE 0 = 1;
ALTER TABLE mytablelog ADD COLUMN logtype INTEGER;
ALTER TABLE mytablelog ADD COLUMN timestamp INTEGER TEXT;
CREATE TABLE IF NOT EXISTS schema_log AS SELECT * FROM sqlite_master WHERE 0=1;
CREATE TABLE IF NOT EXISTS pretrans_schema AS SELECT * FROM sqlite_master WHERE 0=1;
ALTER TABLE schema_log ADD COLUMN logtype INTEGER;
ALTER TABLE schema_log ADD COLUMN timestamp INTEGER TEXT;
CREATE TRIGGER IF NOT EXISTS mytable_inserts AFTER INSERT ON mytable
BEGIN
INSERT INTO mytablelog SELECT *,0,strftime('%s','now') FROM mytable WHERE id = new.id;
END
;
CREATE TRIGGER IF NOT EXISTS mytable_deletions BEFORE DELETE ON mytable
BEGIN
INSERT INTO mytablelog SELECT *,1,strftime('%s','now') FROM mytable WHERE id = old.id;
END
;
CREATE TRIGGER IF NOT EXISTS mytable_preupdates BEFORE UPDATE ON mytable
BEGIN
INSERT INTO mytablelog SELECT *,2,strftime('%s','now') FROM mytable WHERE id = old.id;
END
;
CREATE TRIGGER IF NOT EXISTS mytable_postupdates AFTER UPDATE ON mytable
BEGIN
INSERT INTO mytablelog SELECT *,3,strftime('%s','now') FROM mytable WHERE id = new.id;
END
;
-- SELECT * FROM sqlite_master WHERE name LIKE 'sqlite_%';
/* BEFORE TRANSACTION PREPATION */
DELETE FROM mytablelog;
DELETE FROM schema_log;
DELETE FROM pretrans_schema;
INSERT INTO pretrans_schema SELECT * FROM sqlite_master;
/* DO SOMETHING AKA THE TRANSACTIONS */
CREATE TABLE IF NOT EXISTS newtable (id INTEGER PRIMARY KEY, acolumn TEXT);
INSERT INTO mytable (mycolumn) VALUES ('Mary')
-- ,('Fred'),('Jane'),('Anne'),('Alfred'),('George'),('Alan')
,('Susan'),('Betty'),('Catherine'),('John')
,(100),(200)
;
UPDATE mytable SET mycolumn = mycolumn||' has the detected letter.' WHERE mycolumn LIKE '%n%';
DELETE FROM mytable WHERE CAST(mycolumn AS INTEGER) > 0;
/* AFTER TRANSACTION */
SELECT rowid,'sm',* FROM sqlite_master UNION ALL SELECT rowid,'pt',* FROM pretrans_schema ORDER BY type,name; /* FOR DEMO/TESTING */
/* Get items added to the schema */
INSERT INTO schema_log SELECT *,4,strftime('%s','now') FROM sqlite_master WHERE name NOT IN (SELECT name FROM pretrans_schema);
/* Get items deleted from the schema */
INSERT INTO schema_log SELECT *,5,strftime('%s','now') FROM pretrans_schema WHERE name NOT IN (SELECT name FROM sqlite_master);
/* get original schema if schema updates */
INSERT INTO schema_log SELECT *,6,strftime('%s','now') FROM sqlite_master AS s
WHERE (sql <> (SELECT sql FROM pretrans_schema WHERE type = s.type AND name = s.name )) AND type IS NOT NULL /* AND NAME <> 'pretrans_schema' optional */
;
/* get current schema if schema updates */
INSERT INTO schema_log SELECT *,7,strftime('%s','now') FROM pretrans_schema AS s
WHERE (sql <> (SELECT sql FROM sqlite_master WHERE type = s.type AND name = s.name)) AND type IS NOT NULL /* AND NAME <> 'pretrans_schema' */
;
SELECT * FROM schema_log;
SELECT * FROM mytablelog;
Results
1 pre and post schema (part of)
Note the highlighted line, there is no pt entry, showing that the table has been added (no sm entry then it would have been deleted).
2 - The schema changes log (schema_log table)
4 indicates new item in the schema (5 deleted, 6 before update, 7 after update)
So the table newtable has been added as part of the transaction.
3 - The mytable transactions;
O's are insertions, 1's are deleted 2 and 3 (paired) pre and post update respectively.
Thank you for thinking along and especially #MikeT for the long and thourough answer. I have tried your answer and while it works it seemed not elegant to me and have a lot of overhead.
After a lot of search I have found that the solution is baked into SQLite but it must be enabled at compile time. It is called 'The Session Extension' and is exacly what I needed:
https://www.sqlite.org/sessionintro.html
Related
I'm trying to create a trigger to copy newly added rows from one table on a different database but on the same server to another table and increment a column which only exists on the table where the new rows are being added. I'm not sure if the code is accurate and would like any feedback on how it can be improved.
CREATE TRIGGER AddReTncyTransStatement
ON [DBAdmin].[dbo].[ReTncyTransStatement]
AFTER UPDATE, INSERT
AS
BEGIN
INSERT INTO [DBAdmin].[dbo].[ReTncyTransStatement]
([ORG-CODE], [TNCY-SYS-REF], [TRANS-NO], [PROGRESS-RECID])
SELECT
[ORG-CODE],
[TNCY-SYS-REF],
[TRANS-NO],
[ANALYSIS-CODE] ``,
(SELECT MAX([PROGRESS-RECID])
FROM [DBAdmin].[dbo].[ReTncyTransStatement]) + 1 AS RECID
FROM
[SQLViewsPro2EOD].[dbo].[RE-TNCY-TRANS]
END;
I imagine that you have a table called [RE-TNCY-TRANS] which is in [SQLViewsPro2EOD] database and the other table which is [ReTncyTransStatement] that resides inside the [DBAdmin] database.
I also imagine that you want to insert a record into [ReTncyTransStatement] each time a record inserted into [RE-TNCY-TRANS]. So to achieve this you need to rewrite your trigger as below:
CREATE TRIGGER AddReTncyTransStatement
ON [SQLViewsPro2EOD].[dbo].[RE-TNCY-TRANS]
AFTER UPDATE, INSERT
AS
BEGIN
INSERT INTO [DBAdmin].[dbo].[ReTncyTransStatement]
(
[ORG-CODE],
[TNCY-SYS-REF],
[TRANS-NO],
[PROGRESS-RECID]
)
SELECT [ORG-CODE],
[TNCY-SYS-REF],
[TRANS-NO],
ISNULL((
SELECT MAX([PROGRESS-RECID]) FROM [DBAdmin].[dbo].[ReTncyTransStatement]
),0) + 1 AS RECID
FROM Inserted;
END;
Update
Why I used the ISNULL function?
Because at the first time, there is no record in [DBAdmin].[dbo].[ReTncyTransStatement] table, so the MAX([PROGRESS-RECID]) will be NULL. I used ISNULL to handle this situation.
Why I used the inserted?
According to the Microsoft docs:
The inserted table stores copies of the affected rows during INSERT
and UPDATE statements. During an insert or update transaction, new
rows are added to both the inserted table and the trigger table. The
rows in the inserted table are copies of the new rows in the trigger
table.
Read more here:https://learn.microsoft.com/en-us/sql/relational-databases/triggers/use-the-inserted-and-deleted-tables?view=sql-server-2017
I want to create a trigger in my database which should run when table 'Valve' is updated.
As part of the trigger following must happen:
ME table should be created or updated as per the Schema of Valve table [PnPID, Position X/Y/Z, LineNumberTag, Tag, Spec.]
All information from existing table Valve, to be copied/updated into the table ME
The changes, should happen automatically in the table ME as per the source Valve without the need for any manual intervention.
Please help me create such a Trigger? I am new in SQL.
On my table ME.Valve I want to add other columns with new information that I do not want to appear in the Valve table (do not corrupt it). I attach a picture with existing table Valve.
So, what I want is to copy from tabel "Valve_PNP" in my tabel SQL_P3D_Test_ME for example only column "LineNumberTag" , "Tag", but when value from tabel "Valve_PNP" column "LineNumberTag" , "Tag" are changing, to change and into my tabel SQL_P3D_Test_ME automatically.
And in my tabel SQL_P3D_Test_ME add new column for exampe Made By in witch I insert information manualy in row in front of each valve.
It is possible?
This is what I want, in table "dbo.Source Tabel" you have one row in that you have information in columns "TextColumn", "ValueColumn" etc. For start this information will be copied in table [me].[Destination Table], but when information into tabel"dbo.Source Tabel" for example in column "TextColumn" change from "test insert and update with no data change" with "Marius", to update in tabel [me].[Destination Table] only this information and not add new row. And so on for each column.
I use Microsoft SQL Server Management Studio
I suggest adding the trigger to the "Valve" table that contains the update to the "ME.Valve" table. Let me know if I should write a sample of the syntax.
Here's an example of my solution..
USE [SpecifyYourDatabaseHere]
CREATE TABLE dbo.SourceTable
(
SourcePrimaryKeyID INT IDENTITY PRIMARY KEY,
TextColumn VARCHAR(2048),
ValueColumn DECIMAL(18,3),
NumberColumn INT
)
CREATE TABLE [me].[DestinationTable]
(
DestinationPrimaryKeyID INT IDENTITY PRIMARY KEY,
SourcePrimaryKeyID INT,
TextColumn VARCHAR(2048),
ValueColumn DECIMAL(18,3),
NumberColumn INT,
ActionType VARCHAR(100),
CreatedDate AS GETDATE(), -- Default to current date
CreatedDatabaseUser AS SUSER_SNAME()
)
GO
-- You will need an insert, update and delete trigger.
-- INSERT TRIGGER
-- This trigger will insert any new records into the destination table.
CREATE TRIGGER [dbo].[SourceTable_Insert]
ON [dbo].[SourceTable]
FOR INSERT
AS
INSERT INTO [me].[DestinationTable]
(
SourcePrimaryKeyID,
TextColumn,
ValueColumn,
NumberColumn,
ActionType
)
SELECT INSERTED.SourcePrimaryKeyID,
INSERTED.TextColumn,
INSERTED.ValueColumn,
INSERTED.NumberColumn,
'Insert' AS ActionType
FROM INSERTED
GO
-- UPDATE TRIGGER
-- Conditional Update Trigger : This trigger will only insert data in destination of any of the values in the data columns has changed (Saves space).
CREATE TRIGGER [dbo].[SourceTable_Update]
ON [dbo].[SourceTable]
FOR UPDATE
AS
INSERT INTO [me].[DestinationTable]
(
SourcePrimaryKeyID,
TextColumn,
ValueColumn,
NumberColumn,
ActionType
)
SELECT INSERTED.SourcePrimaryKeyID,
INSERTED.TextColumn,
INSERTED.ValueColumn,
INSERTED.NumberColumn,
'Update' AS ActionType
FROM INSERTED
INNER JOIN
(
SELECT SourcePrimaryKeyID,TextColumn,ValueColumn,NumberColumn
FROM [me].[DestinationTable]
INNER JOIN
(
SELECT MAX(DestinationPrimaryKeyID) MaxDestinationPrimaryKeyID,
COUNT(1) DestinationRecordCount
FROM [me].[DestinationTable]
INNER JOIN
INSERTED ON
[me].[DestinationTable].SourcePrimaryKeyID = INSERTED.SourcePrimaryKeyID
GROUP BY
[me].[DestinationTable].SourcePrimaryKeyID
) MaxDestinationPrimaryKey ON
MaxDestinationPrimaryKey.MaxDestinationPrimaryKeyID = [me].[DestinationTable].DestinationPrimaryKeyID
) DestinationData ON
DestinationData.SourcePrimaryKeyID = INSERTED.SourcePrimaryKeyID
AND (
ISNULL(DestinationData.TextColumn,'') != ISNULL(INSERTED.TextColumn,'') OR
ISNULL(DestinationData.ValueColumn,0) != ISNULL(INSERTED.ValueColumn,0) OR
ISNULL(DestinationData.NumberColumn,0) != ISNULL(INSERTED.NumberColumn,0)
)
GO
-- DELETE TRIGGER
-- This trigger will insert any deleted records into the destination table.
CREATE TRIGGER [dbo].[SourceTable_Delete]
ON [dbo].[SourceTable]
FOR DELETE
AS
INSERT INTO [me].[DestinationTable]
(
SourcePrimaryKeyID,
TextColumn,
ValueColumn,
NumberColumn,
ActionType
)
SELECT INSERTED.SourcePrimaryKeyID,
INSERTED.TextColumn,
INSERTED.ValueColumn,
INSERTED.NumberColumn,
'Delete' AS ActionType
FROM INSERTED
GO
-- Test the code..
-- Insert trigger
INSERT [dbo].[SourceTable]
SELECT 'test insert and update with no data change',
123.456,
1
INSERT [dbo].[SourceTable]
SELECT 'test insert and update with data changed',
123.456,
1
INSERT [dbo].[SourceTable]
SELECT 'test delete',
123.456,
1
-- Update trigger test 1 - no data changed (Must not add record in destination)
UPDATE [dbo].[SourceTable]
SET NumberColumn = 1
WHERE SourcePrimaryKeyID = 1
-- Update trigger test 2 - Data changed (Must add record in destination)
UPDATE [dbo].[SourceTable]
SET NumberColumn = NumberColumn + 1
WHERE SourcePrimaryKeyID = 2
-- Deleted trigger test
DELETE FROM [dbo].[SourceTable]
WHERE SourcePrimaryKeyID = 3
SELECT *
FROM dbo.SourceTable
SELECT *
FROM me.DestinationTable
ORDER BY
SourcePrimaryKeyID,
DestinationPrimaryKeyID
I'm trying to write an idempotent db migration script, which, among other things, needs to shuffle some data. Later in the script, one of the columns I'm selecting from is removed (the purpose of the migration is to move data from that column into a new place), so I have something like this (generated by EF Core):
IF NOT EXISTS (SELECT * FROM [__EFMigrationsHistory] WHERE [MigrationId] = N'AName')
BEGIN
INSERT INTO Foos (A, B)
SELECT OldA, OldB FROM Bars
END
-- a little later in the script:
IF NOT EXISTS (SELECT * FROM [__EFMigrationsHistory] WHERE [MigrationId] = N'AnotherName')
BEGIN
ALTER TABLE [Bars] DROP COLUMN [OldB];
END
However, this isn't as idempotent as I'd hoped it would be; the second time I run the script, it fails with an error on the first INSERT statement, since the OldB column doesn't exist on Bars anymore.
However, the guard clause above will always be false if OldB has been dropped, because in the same go as dropping OldB, we also insert that row into the migrations history (and yes, I've checked that this is true now too; the row exists). So the INSERT should never run without all columns it cares about existing.
How can I write an idempotent INSERT like the one above, that doesn't validate existence of all columns until it's actually run?
You could check if all columns exist:
IF NOT EXISTS (SELECT * FROM [__EFMigrationsHistory]
WHERE [MigrationId] = N'AName')
BEGIN
IF (SELECT COUNT(*)
FROM sys.columns
WHERE [object_id] = OBJECT_ID('Bars')
AND name IN ('OldA', 'OldB')) = 2
BEGIN
EXEC('INSERT INTO Foos (A, B)
SELECT OldA, OldB FROM Bars');
END
END
-- a little later in the script:
IF NOT EXISTS (SELECT * FROM [__EFMigrationsHistory]
WHERE [MigrationId] = N'AnotherName')
BEGIN
EXEC('ALTER TABLE [Bars] DROP COLUMN [OldB]');
END
The results of a union select statement in SQL Server 2008 is including records that are not found in either source table. Example:
theid is an integer ID value within each source table. Neither table includes theid value of 277741.
Select *
From DataTable1
WHere theid = 277741
-- 0 records returned
Select *
From DataTable2
Where theid = 277741
-- 0 records returned
However, when you run the Union Select statement below, a record is generated for theid.
Select *
Into ConjoinedData
From DataTable1
Union
Select *
From DataTable2
Where theid Not In (Select theid From DataTable1)
Select
From ConjoinedData
Where theid = 27741
-- 1 record returned
The theid field is not an identity field in either source table. Ultimately, the data for DataTable1 and DataTable2 came from the same parent, whose content includes an unrelated record with ID 277741. However, there are no foreign key relationships to it or any other table on either of the source tables. I have tried changing to a Union All query with no success. I have created an index on theid in both source tables and the 'created' record appears. I have dropped and recreated the source tables numerous times to no avail. Why is SQL Server getting the unrelated record from the disconnected parent table (source talbes were both cereated from the same parent using Select..Into statements and no foreign key relationship to either table back to the parent)?
277741 is different from 27741. The following script reproduces exactly what you describe:
create table Table1 (id int);
create table Table2 (id int);
go
insert into Table1(id) values (277742);
insert into Table2(id) values (27741);
go
Select * From Table1 WHere id = 277741;
Select * From Table2 Where id = 277741;
go
Select *
Into ConjoinedData
From Table1
Union
Select *
From Table2
Where id Not In (Select id From Table1);
Select *
From ConjoinedData
Where id = 27741;
I'm trying to build a mapping table to associate the IDs of new rows in a table with those that they're copied from. The OUTPUT INTO clause seems perfect for that, but it doesn't seem to behave according to the documentation.
My code:
DECLARE #Missing TABLE (SrcContentID INT PRIMARY KEY )
INSERT INTO #Missing
( SrcContentID )
SELECT cshadow.ContentID
FROM Private.Content AS cshadow
LEFT JOIN Private.Content AS cglobal ON cshadow.Tag = cglobal.Tag
WHERE cglobal.ContentID IS NULL
PRINT 'Adding new content headers'
DECLARE #Inserted TABLE (SrcContentID INT PRIMARY KEY, TgtContentID INT )
INSERT INTO Private.Content
( Tag, Description, ContentDate, DateActivate, DateDeactivate, SortOrder, CreatedOn, IsDeleted, ContentClassCode, ContentGroupID, OrgUnitID )
OUTPUT cglobal.ContentID, INSERTED.ContentID INTO #Inserted (SrcContentID, TgtContentID)
SELECT Tag, Description, ContentDate, DateActivate, DateDeactivate, SortOrder, CreatedOn, IsDeleted, ContentClassCode, ContentGroupID, NULL
FROM Private.Content AS cglobal
INNER JOIN #Missing AS m ON cglobal.ContentID = m.SrcContentID
Results in the error message:
Msg 207, Level 16, State 1, Line 34
Invalid column name 'SrcContentID'.
(line 34 being the one with the OUTPUT INTO)
Experimentation suggests that only rows that are actually present in the target of the INSERT can be selected in the OUTPUT INTO. But this contradicts the docs in the books online. The article on OUTPUT Clause has example E that describes a similar usage:
The OUTPUT INTO clause returns values
from the table being updated
(WorkOrder) and also from the Product
table. The Product table is used in
the FROM clause to specify the rows to
update.
Has anyone worked with this feature?
(In the meantime I've rewritten my code to do the job using a cursor loop, but that's ugly and I'm still curious)
You can do this with a MERGE in Sql Server 2008. Example code below:
--drop table A
create table A (a int primary key identity(1, 1))
insert into A default values
insert into A default values
delete from A where a>=3
-- insert two values into A and get the new primary keys
MERGE a USING (SELECT a FROM A) AS B(a)
ON (1 = 0) -- ignore the values, NOT MATCHED will always be true
WHEN NOT MATCHED THEN INSERT DEFAULT VALUES -- always insert here for this example
OUTPUT $action, inserted.*, deleted.*, B.a; -- show the new primary key and source data
Result is
INSERT, 3, NULL, 1
INSERT, 4, NULL, 2
i.e. for each row the new primary key (3, 4) and the old one (1, 2). Creating a table called e.g. #OUTPUT and adding " INTO #OUTPUT;" at the end of the OUTPUT clause would save the records.
I've verified that the problem is that you can only use INSERTED columns. The documentation seems to indicate that you can use from_table_name, but I can't seem to get it to work (The multi-part identifier "m.ContentID" could not be bound.):
TRUNCATE TABLE main
SELECT *
FROM incoming
SELECT *
FROM main
DECLARE #Missing TABLE (ContentID INT PRIMARY KEY)
INSERT INTO #Missing(ContentID)
SELECT incoming.ContentID
FROM incoming
LEFT JOIN main
ON main.ContentID = incoming.ContentID
WHERE main.ContentID IS NULL
SELECT *
FROM #Missing
DECLARE #Inserted TABLE (ContentID INT PRIMARY KEY, [Content] varchar(50))
INSERT INTO main(ContentID, [Content])
OUTPUT INSERTED.ContentID /* incoming doesn't work, m doesn't work */, INSERTED.[Content] INTO #Inserted (ContentID, [Content])
SELECT incoming.ContentID, incoming.[Content]
FROM incoming
INNER JOIN #Missing AS m
ON m.ContentID = incoming.ContentID
SELECT *
FROM #Inserted
SELECT *
FROM incoming
SELECT *
FROM main
Apparently the from_table_name prefix is only allowed on DELETE or UPDATE (or MERGE in 2008) - I'm not sure why:
from_table_name
Is a column prefix that specifies a table included in the FROM clause of a DELETE or UPDATE statement that is used to specify the rows to update or delete.
If the table being modified is also specified in the FROM clause, any reference to columns in that table must be qualified with the INSERTED or DELETED prefix.
I'm running into EXACTLY the same problem as you are, I feel your pain...
As far as I've been able to find out there's no way to use the from_table_name prefix with an INSERT statement.
I'm sure there's a viable technical reason for this, and I'd love to know exactly what it is.
Ok, found it, here's a forum post on why it doesn't work:
MSDN forums
I think I found a solution to this problem, it sadly involves a temporary table, but at least it'll prevent the creation of a dreaded cursor :)
What you need to do is add an extra column to the table you're duplicating records from and give it a 'uniqueidentifer' type.
then declare a temporary table:
DECLARE #tmptable TABLE (uniqueid uniqueidentifier, original_id int, new_id int)
insert the the data into your temp table like this:
insert into #tmptable
(uniqueid,original_id,new_id)
select NewId(),id,0 from OriginalTable
the go ahead and do the real insert into the original table:
insert into OriginalTable
(uniqueid)
select uniqueid from #tmptable
Now to add the newly created identity values to your temp table:
update #tmptable
set new_id = o.id
from OriginalTable o inner join #tmptable tmp on tmp.uniqueid = o.uniqueid
Now you have a lookup table that holds the new id and original id in one record, for your using pleasure :)
I hope this helps somebody...
(MS) If the table being modified is also specified in the FROM clause, any reference to columns in that table must be qualified with the INSERTED or DELETED prefix.
In your example, you can't use cglobal table in the OUTPUT unless it's INSERTED.column_name or DELETED.column_name:
INSERT INTO Private.Content
(Tag)
OUTPUT cglobal.ContentID, INSERTED.ContentID
INTO #Inserted (SrcContentID, TgtContentID)
SELECT Tag
FROM Private.Content AS cglobal
INNER JOIN #Missing AS m ON cglobal.ContentID = m.SrcContentID
What worked for me was a simple alias table, like this:
INSERT INTO con1
(Tag)
OUTPUT **con2**.ContentID, INSERTED.ContentID
INTO #Inserted (SrcContentID, TgtContentID)
SELECT Tag
FROM Private.Content con1
**INNER JOIN Private.Content con2 ON con1.id=con2.id**
INNER JOIN #Missing AS m ON con1.ContentID = m.SrcContentID