Let's look at the famous Nortwind database. Say I run DELETE FROM Clients.
In MSAccess, when one runs a DELETE statement against a table with referential integrity constraints, Jet will delete the records it can delete, and leave the other ones. In this case, it would delete only the Clients for which there are no Orders.
In SQL Server, doing this seems to just fail, with a message stating that The DELETE statement conflicted with the REFERENCE constraint .....
My question therefore is: is there a simple way to let SQL Server delete just those records that can be deleted ? or do I have to add a WHERE ClientId NOT IN (SELECT Id FROM Clients) ?
In other words, can I make SQL Server DELETE work like Jet's DELETE ?
For info: I am not THAT lazy, but there are MANY constraints there and I'd like to keep my code simple...
Another approach is to delete in loop (row by row) using CURSOR and TRY .. CATCH block to ignore problems with deleting referenced rows.
In this approach you don't have to model your existing and future constraints.
Example:
SET NOCOUNT ON; -- use not to have "(N row(s) affected)" for each deleted row
DECLARE del_cursor CURSOR
FOR SELECT ClientID FROM Clients
DECLARE #CurrentClientID INT -- use your proper type
DECLARE #message VARCHAR(200) -- just for building messages
OPEN del_cursor
FETCH NEXT FROM del_cursor
INTO #CurrentClientID
WHILE ##FETCH_STATUS = 0
BEGIN
BEGIN TRY
DELETE FROM Clients WHERE CURRENT OF del_cursor
END TRY
BEGIN CATCH
SET #message = 'Row ' + CAST(#CurrentClientID AS VARCHAR) + ' cannot be deleted - skipping.'
PRINT #message
END CATCH
FETCH NEXT FROM del_cursor
INTO #CurrentClientID
END
CLOSE del_cursor
DEALLOCATE del_cursor
You may wrap above example with CREATE PROCEDURE DeleteClients and use EXEC DeleteClients instead of DELETE FROM Clients
Your options are:
Cascading Delete - Will delete all records that are dependent on
those clients.
Drop constraint (I don't recommend this :))
Check ahead of time that it can be delete and will not have a conflict
If you want to leave rows that have FK references, then there are only a couple of options, and none of them are pretty:
Check constraints before you do the delete
Modify the query to have the where clauses for the FK as you mentioned in your question
Change your logic to delete rows one at a time, commit each row and rollback the delete if it fails.
The 'least lousy' option really depends on how many FKs are there, how many rows you'll be deleting and the likelihood that a row has FK dependencies. If that is a relatively rare event, then option #3 may be best, although I would tend to lean towards the first two options.
I know it is a little bit complicated solution but you need to do it only once.
How about a trigger to prevent the deletions?
create table parent(
id int not null primary key
)
create table son(
id int not null primary key,
idparent int)
alter table son add foreign key(idparent) references parent(id)
insert into parent values(1)
insert into parent values(2)
insert into parent values(3)
insert into son values(1,1)
--select * from parent
--select * from son
create trigger preventDelete
on parent
instead of delete
as
begin
delete from parent where id not in (select idparent from son) and id in (select id from deleted)
end
delete from parent
records 2 and 3 will be deleted
Try this, it will generate your statements. Excuse the format I wrote it very quickly:
DECLARE #i INT, #SQL NVARCHAR(2000), #TABLENAME NVARCHAR(100)
SET #i = 1
SET #TABLENAME = 'informer_alert'
SET #SQL = ''
DECLARE #col VARCHAR(50), #basecol VARCHAR(50), #tname VARCHAR(50)
DECLARE #TABLE TABLE ([table] VARCHAR(50), col VARCHAR(50), basecol VARCHAR(50))
INSERT INTO #TABLE
SELECT t.name, sc.name, sc2.name
FROM sys.foreign_key_columns fk
INNER JOIN sys.tables t ON fk.parent_object_id = t.OBJECT_ID
INNER JOIN syscolumns sc ON fk.parent_column_id = sc.colorder
AND sc.id = fk.parent_object_id
INNER JOIN syscolumns sc2 ON fk.referenced_object_id = sc2.id
AND fk.constraint_column_id = sc2.colorder
WHERE fk.referenced_object_id = (SELECT OBJECT_ID
FROM sys.tables
WHERE name = #TABLENAME)
WHILE (#i <= (SELECT COUNT(*) FROM #TABLE))
BEGIN
SELECT #tname = [table], #col = col, #basecol = basecol
FROM (SELECT ROW_NUMBER() OVER(ORDER BY col) [N],
[table], col, basecol
FROM #TABLE) A
WHERE A.N = #i
SET #SQL = #SQL + ' DELETE FROM ' + #TABLENAME + ' WHERE ' + #basecol + ' NOT IN (SELECT ' + #col+ ' FROM ' + #tname + ')'
SET #i = #i + 1
END
SELECT #SQL
If you want to keep code simple (you said that) let you CREATE VIEW over your table.
Define WHERE clause to get only deletable rows.
Then you may just DELETE FROM ClientsDeletable.
Remember about DELETE permission for your new view.
Script example:
CREATE VIEW ClientsDeletable
AS
SELECT *
FROM Clients
WHERE
ClientID NOT IN (SELECT CliID FROM ForeignTab1)
AND
ClientID NOT IN (SELECT CliID FROM ForeignTab2)
Notice, that FROM cannot contains JOINs - other way you'll get error:
Msg 4405, Level 16, State 1, Line 1
View or function 'ClientsDeletable' is not updatable because the modification affects multiple base tables.
Related
I'm a SQL novice, and usually figure things out via Google and SO, but I can't wrap my head around the SQL required for this.
My question is similar to Delete sql rows where IDs do not have a match from another table, but in my case I have a middle table that I have to query, so here's the scenario:
We have this INSTANCES table that basically lists all the occurrences of files sent to the database, but have to join with CROSS_REF so our reporting application knows which table to query for the report, and we just have orphaned INSTANCES rows I want to clean out. Each DETAIL table contains different fields from the other ones.
I want to delete all single records from INSTANCES if there are no records for that Instance ID in any DETAIL table. The DETAIL table got regularly cleaned of old files, but the Instance record wasn't cleaned up, so we have a lot of INSTANCE records that don't have any associated DETAIL data. The thing is, I have to select the Table Name from CROSS_REF to know which DETAIL_X table to look up the Instance ID.
In the below example then, since DETAIL_1 doesn't have a record with Instance ID = 1001, I want to delete the 1001 record from INSTANCES.
INSTANCES
Instance ID
Detail ID
1000
123
1001
123
1002
234
CROSS_REF
Detail ID
Table Name
123
DETAIL_1
124
DETAIL_2
125
DETAIL_3
DETAIL_1
Instance ID
1000
1000
2999
Storing table names or column names in a database is almost always a sign for a bad database design. You may want to change this and thus get rid of this problem.
However, when knowing the possible table names, the task is not too difficult.
delete from instances i
where not exists
(
select null
from cross_ref cr
left join detail_1 d1 on d1.instance_id = i.instance_id and cr.table_name = 'DETAIL_1'
left join detail_2 d2 on d2.instance_id = i.instance_id and cr.table_name = 'DETAIL_2'
left join detail_3 d3 on d3.instance_id = i.instance_id and cr.table_name = 'DETAIL_3'
where cr.detail_id = i.detail_id
and
(
d1.instance_id is not null or
d2.instance_id is not null or
d3.instance_id is not null
)
);
(You can replace is not null by = i.instance_id, if you find that more readable. In that case you could even remove these criteria from the ON clauses.)
Much thanks to #DougCoats, this is what I ended up with.
So here's what I ended up with (#Doug, if you want to update your answer, I'll mark yours correct).
DECLARE #Count INT, #Sql VARCHAR(MAX), #Max INT;
SET #Count = (SELECT MIN(DetailID) FROM CROSS_REF)
SET #Max = (SELECT MAX(DetailID) FROM CROSS_REF)
WHILE #Count <= #Max
BEGIN
IF (select count(*) from CROSS_REF where file_id = #count) <> 0
BEGIN
SET #sql ='DELETE i
FROM Instances i
WHERE NOT EXISTS
(
SELECT InstanceID
FROM '+(SELECT TableName FROM Cross_Ref WHERE DetailID=#Count)+' d
WHERE d.InstanceId=i.InstanceID
AND i.detailID ='+ cast(#Count as varchar) +'
)
AND i.detailID ='+ cast(#Count as varchar)
EXEC(#sql);
SET #Count=#Count+1
END
END
this answer assumes you have sequential data in the CROSS_REF table. If you do not, you'll need to alter this to account it (as it will bomb due to missing object reference).
However, this should give you an idea. It also could probably be written to do a more set based approach, but my answer is to demonstrate dynamic sql use. Be careful when using dynamic SQL though.
DECLARE #Count INT, #Sql VARCHAR(MAX), #Max INT;
SET #Count = (SELECT MIN(DetailID) FROM CROSS_REF)
SET #Max = (SELECT MAX(DetailID) FROM CROSS_REF)
WHILE #Count <= #Max
BEGIN
IF (select count(*) from CROSS_REF where file_id = #count) <> 0
BEGIN
SET #sql ='DELETE i
FROM Instances i
WHERE NOT EXISTS
(
SELECT InstanceID
FROM '+(SELECT TableName FROM Cross_Ref WHERE DetailID=#Count)+' d
WHERE d.InstanceId=i.InstanceID
AND i.detailID ='+ cast(#Count as varchar) +'
)
AND i.detailID ='+ cast(#Count as varchar)
EXEC(#sql);
SET #Count=#Count+1
END
END
I'm trying to figure out a way to update a record without having to list every column name that needs to be updated.
For instance, it would be nice if I could use something similar to the following:
// the parts inside braces are what I am trying to figure out
UPDATE Employee
SET {all columns, without listing each of them}
WITH {this record with id of '111' from other table}
WHERE employee_id = '100'
If this can be done, what would be the most straightforward/efficient way of writing such a query?
It's not possible.
What you're trying to do is not part of SQL specification and is not supported by any database vendor. See the specifications of SQL UPDATE statements for MySQL, Postgresql, MSSQL, Oracle, Firebird, Teradata. Every one of those supports only below syntax:
UPDATE table_reference
SET column1 = {expression} [, column2 = {expression}] ...
[WHERE ...]
This is not posible, but..
you can doit:
begin tran
delete from table where CONDITION
insert into table select * from EqualDesingTabletoTable where CONDITION
commit tran
be carefoul with identity fields.
Here's a hardcore way to do it with SQL SERVER. Carefully consider security and integrity before you try it, though.
This uses schema to get the names of all the columns and then puts together a big update statement to update all columns except ID column, which it uses to join the tables.
This only works for a single column key, not composites.
usage: EXEC UPDATE_ALL 'source_table','destination_table','id_column'
CREATE PROCEDURE UPDATE_ALL
#SOURCE VARCHAR(100),
#DEST VARCHAR(100),
#ID VARCHAR(100)
AS
DECLARE #SQL VARCHAR(MAX) =
'UPDATE D SET ' +
-- Google 'for xml path stuff' This gets the rows from query results and
-- turns into comma separated list.
STUFF((SELECT ', D.'+ COLUMN_NAME + ' = S.' + COLUMN_NAME
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = #DEST
AND COLUMN_NAME <> #ID
FOR XML PATH('')),1,1,'')
+ ' FROM ' + #SOURCE + ' S JOIN ' + #DEST + ' D ON S.' + #ID + ' = D.' + #ID
--SELECT #SQL
EXEC (#SQL)
In Oracle PL/SQL, you can use the following syntax:
DECLARE
r my_table%ROWTYPE;
BEGIN
r.a := 1;
r.b := 2;
...
UPDATE my_table
SET ROW = r
WHERE id = r.id;
END;
Of course that just moves the burden from the UPDATE statement to the record construction, but you might already have fetched the record from somewhere.
How about using Merge?
https://technet.microsoft.com/en-us/library/bb522522(v=sql.105).aspx
It gives you the ability to run Insert, Update, and Delete. One other piece of advice is if you're going to be updating a large data set with indexes, and the source subset is smaller than your target but both tables are very large, move the changes to a temporary table first. I tried to merge two tables that were nearly two million rows each and 20 records took 22 minutes. Once I moved the deltas over to a temp table, it took seconds.
If you are using Oracle, you can use rowtype
declare
var_x TABLE_A%ROWTYPE;
Begin
select * into var_x
from TABLE_B where rownum = 1;
update TABLE_A set row = var_x
where ID = var_x.ID;
end;
/
given that TABLE_A and TABLE_B are of same schema
It is possible. Like npe said it's not a standard practice. But if you really have to:
1. First a scalar function
CREATE FUNCTION [dte].[getCleanUpdateQuery] (#pTableName varchar(40), #pQueryFirstPart VARCHAR(200) = '', #pQueryLastPart VARCHAR(200) = '', #pIncludeCurVal BIT = 1)
RETURNS VARCHAR(8000) AS
BEGIN
DECLARE #pQuery VARCHAR(8000);
WITH cte_Temp
AS
(
SELECT
C.name
FROM SYS.COLUMNS AS C
INNER JOIN SYS.TABLES AS T ON T.object_id = C.object_id
WHERE T.name = #pTableName
)
SELECT #pQuery = (
CASE #pIncludeCurVal
WHEN 0 THEN
(
STUFF(
(SELECT ', ' + name + ' = ' + #pQueryFirstPart + #pQueryLastPart FROM cte_Temp FOR XML PATH('')), 1, 2, ''
)
)
ELSE
(
STUFF(
(SELECT ', ' + name + ' = ' + #pQueryFirstPart + name + #pQueryLastPart FROM cte_Temp FOR XML PATH('')), 1, 2, ''
)
) END)
RETURN 'UPDATE ' + #pTableName + ' SET ' + #pQuery
END
2. Use it like this
DECLARE #pQuery VARCHAR(8000) = dte.getCleanUpdateQuery(<your table name>, <query part before current value>, <query part after current value>, <1 if current value is used. 0 if updating everything to a static value>);
EXEC (#pQuery)
Example 1: make all employees columns 'Unknown' (you need to make sure column type matches the intended value:
DECLARE #pQuery VARCHAR(8000) = dte.getCleanUpdateQuery('employee', '', 'Unknown', 0);
EXEC (#pQuery)
Example 2: Remove an undesired text qualifier (e.g. #)
DECLARE #pQuery VARCHAR(8000) = dte.getCleanUpdateQuery('employee', 'REPLACE(', ', ''#'', '''')', 1);
EXEC (#pQuery)
This query can be improved. This is just the one I saved and sometime I use. You get the idea.
Similar to an upsert, you could check if the item exists on the table, if so, delete it and insert it with the new values (technically updating it) but you would lose your rowid if that's something sensitive to keep in your case.
Behold, the updelsert
IF NOT EXISTS (SELECT * FROM Employee WHERE ID = #SomeID)
INSERT INTO Employee VALUES(#SomeID, #Your, #Vals, #Here)
ELSE
DELETE FROM Employee WHERE ID = #SomeID
INSERT INTO Employee VALUES(#SomeID, #Your, #Vals, #Here)
you could do it by deleting the column in the table and adding the column back in and adding a default value of whatever you needed it to be. then saving this will require to rebuild the table
I'm trying to create a simple script to dump the results of a complex view out into a table for reporting. I have used synonyms to simplify tweaking the view and table names.
The idea is that the user of the script can put the name of the view they want to use as the source, and the name of the target reporting table in at the start and away they go. If the table doesn't exist then the script should create it. If the table already exists then the script should only copy the records from the view which aren't already in the table over.
The script below covers all those requirements, but I can't find a nice way to check if the table behind the synonym already exists:
CREATE SYNONYM SourceView FOR my_view
CREATE SYNONYM TargetReportingTable FOR my_table
-- Here's where I'm having trouble, how do I check if the underlying table exists?
IF (SELECT COUNT(*) FROM information_schema.tables WHERE table_name = TargetReportingTable) = 0
BEGIN
-- Table does not exists, so insert into.
SELECT * INTO TargetReportingTable FROM SourceView
END
ELSE
BEGIN
-- Table already exists so work out the last record which was copied over
-- and insert only the newer records.
DECLARE #LastReportedRecordId INT;
SET #LastReportedRecordId = (SELECT MAX(RecordId) FROM TargetReportingTable)
INSERT INTO TargetReportingTable SELECT * FROM SourceView WHERE RecordId > #LastReportedRecordId
END
DROP SYNONYM SourceView
DROP SYNONYM TargetReportingTable
I know I could just get the user of the script to copy the table name into the 'information_schema' line as well as into the synonym at the top, but that leaves scope for error.
I also know I could do something filthy like put the table name into a variable and blat the SQL out as a string, but that makes me feel a bit sick!
Is there a nice elegant SQL way for me to check if the table behind the synonym exists? Or a totally different way to solve to problem?
Not the most elegant of solutions, but you could join the sys.synonyms table to the sys.tables table to check whether the table exists.
If the table does not exist, the join will fail and you will get 0 rows (hence IF EXISTS will be false). If the table does exist, the join will success and you will get 1 row (and true):
IF EXISTS( SELECT *
FROM sys.synonyms s
INNER JOIN sys.tables t ON REPLACE(REPLACE(s.base_object_name, '[', ''), ']', '') = t.name
WHERE s.name = 'TargetReportingTable')
BEGIN
-- Does exist
END
ELSE
BEGIN
-- Does not exist
END
Replace 'TargetReportingTable' with whichever synonym you wish to check.
The above solutions did not work for me if the synonym referenced another database. I recently discovered the function [fn_my_permissions] which is useful for showing permissions for a specific database object, so I figure this could be used as follows:
IF EXISTS
(
select *
from sys.synonyms sy
cross apply fn_my_permissions(sy.base_object_name, 'OBJECT')
WHERE sy.name = 'TargetReportingTable'
)
print 'yes - I exist!'
Late to the party, I have created a query to test out the existence of Synonyms and share with you.
DECLARE #Synonyms table
(
ID int identity(1,1),
SynonymsDatabaseName sysname,
SynonymsSchemaName sysname,
SynonymsName sysname,
DatabaseName nvarchar(128),
SchemaName nvarchar(128),
ObjectName nvarchar(128),
Remark nvarchar(max),
IsExists bit default(0)
)
INSERT #Synonyms (SynonymsDatabaseName, SynonymsSchemaName, SynonymsName, DatabaseName, SchemaName, ObjectName)
SELECT
DB_NAME() AS SynonymsDatabaseName,
SCHEMA_NAME(schema_id) AS SynonymsSchemaName,
name AS SynonymsName,
PARSENAME(base_object_name,3) AS DatabaseName,
PARSENAME(base_object_name,2) AS SchemaName,
PARSENAME(base_object_name,1) AS ObjectName
FROM sys.synonyms
SET NOCOUNT ON
DECLARE #ID int = 1, #Query nvarchar(max), #Remark nvarchar(max)
WHILE EXISTS(SELECT * FROM #Synonyms WHERE ID = #ID)
BEGIN
SELECT
#Query = 'SELECT #Remark = o.type_desc FROM [' + DatabaseName + '].sys.objects o INNER JOIN sys.schemas s ON o.schema_id = s.schema_id WHERE s.name = ''' + SchemaName + ''' AND o.name = ''' + ObjectName + ''''
FROM #Synonyms WHERE ID = #ID
EXEC sp_executesql #Query, N'#Remark nvarchar(max) OUTPUT', #Remark OUTPUT;
UPDATE #Synonyms SET IsExists = CASE WHEN #Remark IS NULL THEN 0 ELSE 1 END, Remark = #Remark WHERE ID = #ID
SELECT #ID += 1, #Remark = NULL
END
SELECT * FROM #Synonyms
You can do this with dynamic SQL:
-- create synonym a for information_schema.tables
create synonym a for b
declare #exists int = 1;
begin try
exec('select top 0 * from a');
end try
begin catch
set #exists = 0;
end catch
select #exists;
This doesn't work with non-dynamic SQL, because the synonym reference is caught at compile-time. That means that the code just fails with a message and is not caught by the try/catch block. With dynamic SQL, the block catches the error.
You can test if Synonym exists in your database using the Object_Id function avaliable in SQL Server
IF OBJECT_ID('YourDatabaseName..YourSynonymName') IS NOT NULL
PRINT 'Exist SYNONYM'
ELSE
PRINT 'Not Exist SYNONYM'
Another simpler solution:
IF (EXISTS (SELECT * FROM sys.synonyms WHERE NAME ='mySynonymName'))
BEGIN
UPDATE mySynonymName
SET [Win] = 1
END
In this case, I do database setup first. I drop all Synonyms in my database (database1) first, then run a SPROC to create synonyms for all tables in the destination database(database2).
Some SPROCS in database1 call on tables in DB2. If table doesnt exist in DB2 the SPROC fails. If table doesnt exist in DB2, the synonmy is not automatically created on database setup. So I just use the above to check if the Synonym exist, and skip that part of the SPROC if the Synonym is not present.
I've been working on a project and came across some interesting behavior when using SELECT INTO. If I have a table with a column defined as int identity(1,1) not null and use SELECT INTO to copy it, the new table will retain the IDENTITY property unless there is a join involved. If there is a join, then the same column on the new table is defined simply as int not null.
Here is a script that you can run to reproduce the behavior:
CREATE TABLE People (Id INT IDENTITY(1,1) not null, Name VARCHAR(10))
CREATE TABLE ReverseNames (Name varchar(10), ReverseName varchar(10))
INSERT INTO People (Name)
VALUES ('John'), ('Jamie'), ('Joe'), ('Jenna')
INSERT INTO ReverseNames (Name, ReverseName)
VALUES ('John','nhoJ'), ('Jamie','eimaJ'), ('Joe','eoJ'), ('Jenna','anneJ')
--------
SELECT Id, Name
INTO People_ExactCopy
FROM People
SELECT Id, ReverseName as Name
INTO People_WithJoin
FROM People
JOIN ReverseNames
ON People.Name = ReverseNames.Name
SELECT Id, (SELECT ReverseName FROM ReverseNames WHERE Name = People.Name) as Name
INTO People_WithSubSelect
FROM People
--------
SELECT OBJECT_NAME(c.object_id) as [Table],
c.is_identity as [Id Column Retained Identity]
FROM sys.columns c
where
OBJECT_NAME(c.object_id) IN ('People_ExactCopy','People_WithJoin','People_WithSubSelect')
AND c.name = 'Id'
--------
DROP TABLE People
DROP TABLE People_ExactCopy
DROP TABLE People_WithJoin
DROP TABLE People_WithSubSelect
DROP TABLE ReverseNames
I noticed that the execution plans for both the WithJoin and WithSubSelect queries contained one join operator. I'm not sure if one will be significantly better on performance if we were dealing with a larger set of rows.
Can anyone shed any light on this and tell me if there is a way to utilize SELECT INTO with joins and still preserve the IDENTITY property?
From Microsoft:
When an existing identity column is
selected into a new table, the new
column inherits the IDENTITY property,
unless one of the following conditions
is true:
The SELECT statement contains a join, GROUP BY clause, or aggregate function.
Multiple SELECT statements are joined by using UNION.
The identity column is listed more than one time in the select list.
The identity column is part of an expression.
The identity column is from a remote data source.
If any one of these conditions is
true, the column is created NOT NULL
instead of inheriting the IDENTITY
property. If an identity column is
required in the new table but such a
column is not available, or you want a
seed or increment value that is
different than the source identity
column, define the column in the
select list using the IDENTITY
function.
You could use the IDENTITY function as they suggest and omit the IDENTITY column, but then you would lose the values, as the IDENTITY function would generate new values and I don't think that those are easily determinable, even with ORDER BY.
I don't believe there is much you can do, except build your CREATE TABLE statements manually, SET IDENTITY_INSERT ON, insert the existing values, then SET IDENTITY_INSERT OFF. Yes you lose the benefits of SELECT INTO, but unless your tables are huge and you are doing this a lot, [shrug]. This is not fun of course, and it's not as pretty or simple as SELECT INTO, but you can do it somewhat programmatically, assuming two tables, one having a simple identity (1,1), and a simple INNER JOIN:
SET NOCOUNT ON;
DECLARE
#NewTable SYSNAME = N'dbo.People_ExactCopy',
#JoinCondition NVARCHAR(255) = N' ON p.Name = r.Name';
DECLARE
#cols TABLE(t SYSNAME, c SYSNAME, p CHAR(1));
INSERT #cols SELECT N'dbo.People', N'Id', 'p'
UNION ALL SELECT N'dbo.ReverseNames', N'Name', 'r';
DECLARE #sql NVARCHAR(MAX) = N'CREATE TABLE ' + #NewTable + '
(
';
SELECT #sql += c.name + ' ' + t.name
+ CASE WHEN t.name LIKE '%char' THEN
'(' + CASE WHEN c.max_length = -1
THEN 'MAX' ELSE RTRIM(c.max_length/
(CASE WHEN t.name LIKE 'n%' THEN 2 ELSE 1 END)) END
+ ')' ELSE '' END
+ CASE c.is_identity
WHEN 1 THEN ' IDENTITY(1,1)'
ELSE ' ' END + ',
'
FROM sys.columns AS c
INNER JOIN #cols AS cols
ON c.object_id = OBJECT_ID(cols.t)
INNER JOIN sys.types AS t
ON c.system_type_id = t.system_type_id
AND c.name = cols.c;
SET #sql = LEFT(#sql, LEN(#sql)-1) + '
);
SET IDENTITY_INSERT ' + #NewTable + ' ON;
INSERT ' + #NewTable + '(';
SELECT #sql += c + ',' FROM #cols;
SET #sql = LEFT(#sql, LEN(#sql)-1) + ')
SELECT ';
SELECT #sql += p + '.' + c + ',' FROM #cols;
SET #sql = LEFT(#sql, LEN(#sql)-1) + '
FROM ';
SELECT #sql += t + ' AS ' + p + '
INNER JOIN ' FROM (SELECT DISTINCT
t,p FROM #cols) AS x;
SET #sql = LEFT(#sql, LEN(#sql)-10)
+ #JoinCondition + ';
SET IDENTITY_INSERT ' + #NewTable + ' OFF;';
PRINT #sql;
With the tables given above, this produces the following, which you could pass to EXEC sp_executeSQL instead of PRINT:
CREATE TABLE dbo.People_ExactCopy
(
Id int IDENTITY(1,1),
Name varchar(10)
);
SET IDENTITY_INSERT dbo.People_ExactCopy ON;
INSERT dbo.People_ExactCopy(Id,Name)
SELECT p.Id,r.Name
FROM dbo.People AS p
INNER JOIN dbo.ReverseNames AS r
ON p.Name = r.Name;
SET IDENTITY_INSERT dbo.People_ExactCopy OFF;
I did not deal with other complexities such as DECIMAL columns or other columns that have parameters such as max_length, nor did I deal with nullability, but these things wouldn't be hard to add it if you need greater flexibility.
In the next version of SQL Server (code-named "Denali") you should be able to construct a CREATE TABLE statement much easier using the new metadata discovery functions - which do much of the grunt work for you in terms of specifying precision/scale/length, dealing with MAX, etc. You still have to manually create indexes and constraints; but you don't get those with SELECT INTO either.
What we really need is DDL that allows you to say something like "CREATE TABLE a IDENTICAL TO b;" or "CREATE TABLE a BASED ON b;"... it's been asked for here, but has been rejected (this is about copying a table to another schema, but the same concept could apply to a new table in the same schema with a different table name). http://connect.microsoft.com/SQLServer/feedback/details/632689
I realize this is a really late response but whoever is still looking for this solution, like I was until I found this solution:
You can't use the JOIN operator for the IDENTITY column property to be inherited.
What you can do is use a WHERE clause like this:
SELECT a.*
INTO NewTable
FROM
MyTable a
WHERE
EXISTS (SELECT 1 FROM SecondTable b WHERE b.ID = a.ID)
This works.
This is the query I'm using:
DELETE TB1.*, TB2.*
FROM TB1
INNER JOIN TB2 ON TB1.PersonID = TB2.PersonID
WHERE (TB1.PersonID)='2'
It's working fine in MS Access but getting error (Incorrect syntax near ','.) in SQL Server Express 2005.
How to solve it? Please help.
You cannot DELETE from multiple tables with a single expression in SQL 2005 - or any other standard SQL for that matter. Access is the exception here.
The best method to get this effect is to specify FOREIGN KEYS between the table with an ON DELETE trigger.
Why you don't use a DELETE CASCADE FK ?
This cannot be done in one statement. You will have to use 2 statements
DELETE FROM TB1 WHERE PersonID = '2';
DELETE FROM TB2 WHERE PersonID = '2';
As i know, you can't do it in a sentence.
But you can build an stored procedure that do the deletes you want in whatever table in a transaction, what is almost the same.
I don't think you can delete from multiple tables at once (though I'm not certain).
It sounds to me, however, that you would be best to achieve this effect with a relationship that cascades deletes. If you did this you would be able to delete the record from one table and the records in the other would be automatically deleted.
As an example, say the two tables represent a customer, and the customer's orders. If you setup the relationship to cascade deletes, you could simply delete record in the customer table, and the orders would get deleted automatically.
See the MSDN doc on cascading referential integrity constraints.
Specify foreign key for the details tables which references to the primary key of master and set Delete rule = Cascade .
Now when u delete a record from the master table all other details table record based on the deleting rows primary key value, will be deleted automatically.
So in that case a single delete query of master table can delete master tables data as well as child tables data.
Use this in procedure
declare cu cursor for SELECT [name] FROM sys.Tables where [name] like 'tbl_%'
declare #table varchar(100)
declare #sql nvarchar(1000)
OPEN cu
FETCH NEXT FROM cu INTO #table
WHILE ##FETCH_STATUS = 0
BEGIN
set #sql = N'delete from ' + #table
EXEC sp_executesql #sql
FETCH NEXT FROM cu INTO #table
END
CLOSE cu;
DEALLOCATE cu;
I use this for cleaning up data in test/development databases. You can filter by table name and record count.
DECLARE #sqlCommand VARCHAR(3000);
DECLARE #tableList TABLE(Value NVARCHAR(128));
DECLARE #TableName VARCHAR(128);
DECLARE #RecordCount INT;
-- get a cursor with a list of table names and their record counts
DECLARE MyCursor CURSOR FAST_FORWARD
FOR SELECT t.name TableName,
i.rows Records
FROM sysobjects t,
sysindexes i
WHERE
t.xtype = 'U' -- only User tables
AND i.id = t.id
AND i.indid IN(0, 1) -- 0=Heap, 1=Clustered Index
AND i.rows < 10 -- Filter by number of records in the table
AND t.name LIKE 'Test_%'; -- Filter tables by name. You could also provide a list:
-- AND t.name IN ('MyTable1', 'MyTable2', 'MyTable3');
-- or a list of tables to exclude:
-- AND t.name NOT IN ('MySpecialTable', ... );
OPEN MyCursor;
FETCH NEXT FROM MyCursor INTO #TableName, #RecordCount;
-- for each table name in the cursor, delete all records from that table:
WHILE ##FETCH_STATUS = 0
BEGIN
SET #sqlCommand = 'DELETE FROM ' + #TableName;
EXEC (#sqlCommand);
FETCH NEXT FROM MyCursor INTO #TableName, #RecordCount;
END;
CLOSE MyCursor;
DEALLOCATE MyCursor;
Reference info:
sysobjects
sysindexes
SQL Server Cursors
Generally I do deletions from multiple tables with one query.
It works correct with PostgreSQL, but doesn't work for MSSQL, and that's why I got here.
With Postgres (I don't know about other DBs) you can do:
WITH obsolete_ids AS (
SELECT pr_r."ID" AS ids
FROM "PULL_REQUEST" pr_r
WHERE 1=1
AND pr_r."STATUS" IN (1)
) , del_commit_junc AS (
DELETE
FROM "PR_TO_COMMIT"
WHERE "REQUEST_ID" IN (SELECT ids FROM obsolete_ids)
)
DELETE
FROM "PULL_REQUEST" pr_out
WHERE pr_out ."ID" IN (SELECT ids FROM obsolete_ids)
Actually In my original Query I delete foreign keys from 2 more tables, but here I just paste an example.
That way I solved problem with Foreign Keys in "PR_TO_COMMIT" table.
The way I am using to Delete rows from multiple tables in SQL Server?
The most important is to use on delete cascade when creating a foreign key in the table
#Table 1 Create:#
create table Customer_tbl
(
C_id int primary key,
C_Name varchar(50),
C_Address varchar(max),
City varchar(50)
);
#Table 2: Create with Foreign Key Constraints#
create table [order]
(
Ord_Id int primary key,
Item varchar(50),
Quantity int,
Price_Of_1 int,
C_id int foreign key references Customer_tbl(C_id)
on delete cascade
);
delete from Customer_tbl where C_id = 2;
You can use something like the following:
DECLARE db_cursor CURSOR FOR
SELECT name
FROM master.dbo.sysdatabases
WHERE name IN ("TB2","TB1") -- use these databases
OPEN db_cursor
FETCH NEXT FROM db_cursor INTO #name
WHILE ##FETCH_STATUS = 0
BEGIN
DELETE FROM #name WHERE PersonID ='2'
FETCH NEXT FROM db_cursor INTO #name
END
CREATE PROCEDURE sp_deleteUserDetails
#Email varchar(255)
AS
declare #tempRegId as int
Delete UserRegistration where Email=#Email
set #tempRegId = (select Id from UserRegistration where Email = #Email)
Delete UserProfile where RegID=#tempRegId
RETURN 0
Try this query
DELETE TB1, TB2 FROM TB1 INNER JOIN TB2
WHERE TB1.PersonID = TB2.PersonID and TB1.PersonID = '2'
$qry = "DELETE lg., l. FROM lessons_game lg RIGHT JOIN lessons l ON lg.lesson_id = l.id WHERE l.id = ?";
lessons is Main table and
lessons_game is subtable so Right Join
DELETE TB1, TB2
FROM customer_details
LEFT JOIN customer_booking on TB1.cust_id = TB2.fk_cust_id
WHERE TB1.cust_id = $id