Я пытаюсь сравнить две таблицы SQL Server, чтобы проверить некоторые данные. Я хочу вернуть все строки из обеих таблиц, где данные находятся либо в одной, либо в другой. По сути, хочу показать все неточности. При этом мне нужно проверить три части данных: FirstName, LastName и Product.
Я новичок в SQL, и кажется, что многие решения, которые я нахожу, слишком усложняют. Мне не нужно беспокоиться о NULL.
Я начал с чего-то вроде этого:
SELECT DISTINCT [First Name], [Last Name], [Product Name] FROM [Temp Test Data]
WHERE ([First Name] NOT IN (SELECT [First Name]
FROM [Real Data]))
Однако у меня проблемы с этим.
Благодарность!
РЕДАКТИРОВАТЬ:
Основываясь на ответе @treaschf, я пытался использовать вариант следующего запроса:
SELECT td.[First Name], td.[Last Name], td.[Product Name]
FROM [Temp Test Data] td FULL OUTER JOIN [Data] AS d
ON td.[First Name] = d.[First Name] AND td.[Last Name] = d.[Last Name]
WHERE (d.[First Name] = NULL) AND (d.[Last Name] = NULL)
Но я продолжаю получать 0 результатов, когда знаю, что в td есть хотя бы 1 строка, которой нет в d.
РЕДАКТИРОВАТЬ:
Хорошо, думаю, я понял это. По крайней мере, за несколько минут тестирования он работает достаточно хорошо.
SELECT [First Name], [Last Name]
FROM [Temp Test Data] AS td
WHERE (NOT EXISTS
(SELECT [First Name], [Last Name]
FROM [Data] AS d
WHERE ([First Name] = td.[First Name]) OR ([Last Name] = td.[Last Name])))
Это в основном скажет мне, что есть в моих тестовых данных, чего нет в моих реальных данных. Что совершенно нормально для того, что мне нужно делать.
Ответы:
ЕСЛИ у вас есть таблицы,
A
иB
оба с столбцомC
, вот записи, которые присутствуют в таблице,A
но не вB
:SELECT A.* FROM A LEFT JOIN B ON (A.C = B.C) WHERE B.C IS NULL
Чтобы получить все различия с помощью одного запроса, необходимо использовать полное соединение, например:
SELECT A.*, B.* FROM A FULL JOIN B ON (A.C = B.C) WHERE A.C IS NULL OR B.C IS NULL
Что вам нужно знать в этом случае, так это то, что когда запись может быть найдена в
A
, но не вB
, то столбцы, из которых поступают,B
будут NULL, и аналогично для тех, которые присутствуют,B
а не вA
, столбцы изA
будут быть нулевым.источник
The objects "a.dbo.student" and "b.dbo.student" in the FROM clause have the same exposed names. Use correlation names to distinguish them.
Я получаю сообщение об ошибке, если имена таблиц совпадают и вы извлекаете их из двух разных баз данных.a.dbo.student as
аb.dbo.student bs
затем ссылаться на таблицы с помощьюas
иbs
( SELECT * FROM table1 EXCEPT SELECT * FROM table2) UNION ALL ( SELECT * FROM table2 EXCEPT SELECT * FROM table1)
источник
SELECT *
работает, если это так для всех столбцов; если нет, просто выберите какое-нибудь подмножество.Я знаю, что это может быть не популярный ответ, но я согласен с @Randy Minder на использование стороннего инструмента, когда требуется более сложное сравнение.
Этот конкретный случай здесь прост, и для этого случая такие инструменты не нужны, но это может легко усложниться, если вы добавите больше столбцов, базы данных на двух серверах, более сложные критерии сравнения и т.
Существует множество таких инструментов, как ApexSQL Data Diff или Quest Toad, и вы всегда можете использовать их в пробном режиме, чтобы выполнить свою работу.
источник
tablediff
упомянутая здесь утилита командной строки SQL Server .Чтобы получить все различия между двумя таблицами, вы можете использовать, как я, этот запрос SQL:
SELECT 'TABLE1-ONLY' AS SRC, T1.* FROM ( SELECT * FROM Table1 EXCEPT SELECT * FROM Table2 ) AS T1 UNION ALL SELECT 'TABLE2-ONLY' AS SRC, T2.* FROM ( SELECT * FROM Table2 EXCEPT SELECT * FROM Table1 ) AS T2 ;
источник
Простой вариант ответа @erikkallen, который показывает, в какой таблице присутствует строка:
( SELECT 'table1' as source, * FROM table1 EXCEPT SELECT * FROM table2) UNION ALL ( SELECT 'table2' as source, * FROM table2 EXCEPT SELECT * FROM table1)
Если вы получите ошибку
тогда это может помочь добавить
( SELECT 'table1' as source, * FROM table1 EXCEPT SELECT 'table1' as source, * FROM table2) UNION ALL ( SELECT 'table2' as source, * FROM table2 EXCEPT SELECT 'table2' as source, * FROM table1)
источник
All queries combined using a UNION, INTERSECT or EXCEPT operator must have an equal number of expressions in their target lists.
с этим выбором. Мне пришлось добавить'table1' as source,
часть также в Except-часть. - Я отредактировал ответ по этому поводу.Если вы хотите узнать, какие значения столбцов отличаются, вы можете использовать модель Entity-Attribute-Value:
declare @Data1 xml, @Data2 xml select @Data1 = ( select * from (select * from Test1 except select * from Test2) as a for xml raw('Data') ) select @Data2 = ( select * from (select * from Test2 except select * from Test1) as a for xml raw('Data') ) ;with CTE1 as ( select T.C.value('../@ID', 'bigint') as ID, T.C.value('local-name(.)', 'nvarchar(128)') as Name, T.C.value('.', 'nvarchar(max)') as Value from @Data1.nodes('Data/@*') as T(C) ), CTE2 as ( select T.C.value('../@ID', 'bigint') as ID, T.C.value('local-name(.)', 'nvarchar(128)') as Name, T.C.value('.', 'nvarchar(max)') as Value from @Data2.nodes('Data/@*') as T(C) ) select isnull(C1.ID, C2.ID) as ID, isnull(C1.Name, C2.Name) as Name, C1.Value as Value1, C2.Value as Value2 from CTE1 as C1 full outer join CTE2 as C2 on C2.ID = C1.ID and C2.Name = C1.Name where not ( C1.Value is null and C2.Value is null or C1.Value is not null and C2.Value is not null and C1.Value = C2.Value )
ПРИМЕР SQL FIDDLE
источник
Попробуй это :
SELECT [First Name], [Last Name] FROM [Temp Test Data] AS td EXCEPTION JOIN [Data] AS d ON (d.[First Name] = td.[First Name] OR d.[Last Name] = td.[Last Name])
Гораздо проще читать.
источник
Это поможет, как и в решении Tiago , также вернет «исходную» таблицу.
select [First name], [Last name], max(_tabloc) as _tabloc from ( select [First Name], [Last name], 't1' as _tabloc from table1 union all select [First name], [Last name], 't2' as _tabloc from table2 ) v group by [Fist Name], [Last name] having count(1)=1
результат будет содержать различия между таблицами, в столбце _tabloc будет ссылка на таблицу.
источник
Представляем Cadillac of Diffs как SP. См. Основной шаблон, основанный на ответе @erikkallen. Он поддерживает
Применение:
exec Common.usp_DiffTableRows '#t1', '#t2'; exec Common.usp_DiffTableRows @pTable0 = 'ydb.ysh.table1', @pTable1 = 'xdb.xsh.table2', @pOrderByCsvOpt = null, -- Order the results @pOnlyCsvOpt = null, -- Only compare these columns @pIgnoreCsvOpt = null; -- Ignore these columns (ignored if @pOnlyCsvOpt is specified)
Код:
alter proc [Common].[usp_DiffTableRows] @pTable0 varchar(300), @pTable1 varchar(300), @pOrderByCsvOpt nvarchar(1000) = null, -- Order the Results @pOnlyCsvOpt nvarchar(4000) = null, -- Only compare these columns @pIgnoreCsvOpt nvarchar(4000) = null, -- Ignore these columns (ignored if @pOnlyCsvOpt is specified) @pDebug bit = 0 as /*--------------------------------------------------------------------------------------------------------------------- Purpose: Compare rows between two tables. Usage: exec Common.usp_DiffTableRows '#a', '#b'; Modified By Description ---------- ---------- ------------------------------------------------------------------------------------------- 2015.10.06 crokusek Initial Version 2019.03.13 crokusek Added @pOrderByCsvOpt 2019.06.26 crokusek Support for @pIgnoreCsvOpt, @pOnlyCsvOpt. 2019.09.04 crokusek Minor debugging improvement 2020.03.12 crokusek Detect duplicate rows in either source table ---------------------------------------------------------------------------------------------------------------------*/ begin try if (substring(@pTable0, 1, 1) = '#') set @pTable0 = 'tempdb..' + @pTable0; -- object_id test below needs full names for temp tables if (substring(@pTable1, 1, 1) = '#') set @pTable1 = 'tempdb..' + @pTable1; -- object_id test below needs full names for temp tables if (object_id(@pTable0) is null) raiserror('Table name is not recognized: ''%s''', 16, 1, @pTable0); if (object_id(@pTable1) is null) raiserror('Table name is not recognized: ''%s''', 16, 1, @pTable1); create table #ColumnGathering ( Name nvarchar(300) not null, Sequence int not null, TableArg tinyint not null ); declare @usp varchar(100) = object_name(@@procid), @sql nvarchar(4000), @sqlTemplate nvarchar(4000) = ' use $database$; insert into #ColumnGathering select Name, column_id as Sequence, $TableArg$ as TableArg from sys.columns c where object_id = object_id(''$table$'', ''U'') '; set @sql = replace(replace(replace(@sqlTemplate, '$TableArg$', 0), '$database$', (select DatabaseName from Common.ufn_SplitDbIdentifier(@pTable0))), '$table$', @pTable0); if (@pDebug = 1) print 'Sql #CG 0: ' + @sql; exec sp_executesql @sql; set @sql = replace(replace(replace(@sqlTemplate, '$TableArg$', 1), '$database$', (select DatabaseName from Common.ufn_SplitDbIdentifier(@pTable1))), '$table$', @pTable1); if (@pDebug = 1) print 'Sql #CG 1: ' + @sql; exec sp_executesql @sql; if (@pDebug = 1) select * from #ColumnGathering; select Name, min(Sequence) as Sequence, convert(bit, iif(min(TableArg) = 0, 1, 0)) as InTable0, convert(bit, iif(max(TableArg) = 1, 1, 0)) as InTable1 into #Columns from #ColumnGathering group by Name having ( @pOnlyCsvOpt is not null and Name in (select Value from Common.ufn_UsvToNVarcharKeyTable(@pOnlyCsvOpt, default))) or ( @pOnlyCsvOpt is null and @pIgnoreCsvOpt is not null and Name not in (select Value from Common.ufn_UsvToNVarcharKeyTable(@pIgnoreCsvOpt, default))) or ( @pOnlyCsvOpt is null and @pIgnoreCsvOpt is null) if (exists (select 1 from #Columns where InTable0 = 0 or InTable1 = 0)) begin select 1; -- without this the debugging info doesn't stream sometimes select * from #Columns order by Sequence; waitfor delay '00:00:02'; -- give results chance to stream before raising exception raiserror('Columns are not equal between tables, consider using args @pIgnoreCsvOpt, @pOnlyCsvOpt. See Result Sets for details.', 16, 1); end if (@pDebug = 1) select * from #Columns order by Sequence; declare @columns nvarchar(4000) = --iif(@pOnlyCsvOpt is null and @pIgnoreCsvOpt is null, -- '*', ( select substring((select ',' + ac.name from #Columns ac order by Sequence for xml path('')),2,200000) as csv ); if (@pDebug = 1) begin print 'Columns: ' + @columns; waitfor delay '00:00:02'; -- give results chance to stream before possibly raising exception end -- Based on https://stackoverflow.com/a/2077929/538763 -- - Added sensing for duplicate rows -- - Added reporting of source table location -- set @sqlTemplate = ' with a as (select ~, Row_Number() over (partition by ~ order by (select null)) -1 as Duplicates from $a$), b as (select ~, Row_Number() over (partition by ~ order by (select null)) -1 as Duplicates from $b$) select 0 as SourceTable, ~ from ( select * from a except select * from b ) anb union all select 1 as SourceTable, ~ from ( select * from b except select * from a ) bna order by $orderBy$ '; set @sql = replace(replace(replace(replace(@sqlTemplate, '$a$', @pTable0), '$b$', @pTable1), '~', @columns), '$orderBy$', coalesce(@pOrderByCsvOpt, @columns + ', SourceTable') ); if (@pDebug = 1) print 'Sql: ' + @sql; exec sp_executesql @sql; end try begin catch declare @CatchingUsp varchar(100) = object_name(@@procid); if (xact_state() = -1) rollback; -- Disabled for S.O. post --exec Common.usp_Log --@pMethod = @CatchingUsp; --exec Common.usp_RethrowError --@pCatchingMethod = @CatchingUsp; throw; end catch go create function Common.Trim ( @pOriginalString nvarchar(max), @pCharsToTrim nvarchar(50) = null -- specify null or 'default' for whitespae ) returns table with schemabinding as /*-------------------------------------------------------------------------------------------------- Purpose: Trim the specified characters from a string. Modified By Description ---------- -------------- -------------------------------------------------------------------- 2012.09.25 S.Rutszy/crok Modified from https://dba.stackexchange.com/a/133044/9415 --------------------------------------------------------------------------------------------------*/ return with cte AS ( select patindex(N'%[^' + EffCharsToTrim + N']%', @pOriginalString) AS [FirstChar], patindex(N'%[^' + EffCharsToTrim + N']%', reverse(@pOriginalString)) AS [LastChar], len(@pOriginalString + N'~') - 1 AS [ActualLength] from ( select EffCharsToTrim = coalesce(@pCharsToTrim, nchar(0x09) + nchar(0x20) + nchar(0x0d) + nchar(0x0a)) ) c ) select substring(@pOriginalString, [FirstChar], ((cte.[ActualLength] - [LastChar]) - [FirstChar] + 2) ) AS [TrimmedString] -- --cte.[ActualLength], --[FirstChar], --((cte.[ActualLength] - [LastChar]) + 1) AS [LastChar] from cte; go create function [Common].[ufn_UsvToNVarcharKeyTable] ( @pCsvList nvarchar(MAX), @pSeparator nvarchar(1) = ',' -- can pass keyword 'default' when calling using ()'s ) -- -- SQL Server 2012 distinguishes nvarchar keys up to maximum of 450 in length (900 bytes) -- returns @tbl table (Value nvarchar(450) not null primary key(Value)) as /*------------------------------------------------------------------------------------------------- Purpose: Converts a comma separated list of strings into a sql NVarchar table. From http://www.programmingado.net/a-398/SQL-Server-parsing-CSV-into-table.aspx This may be called from RunSelectQuery: GRANT SELECT ON Common.ufn_UsvToNVarcharTable TO MachCloudDynamicSql; Modified By Description ---------- -------------- ------------------------------------------------------------------- 2011.07.13 internet Initial version 2011.11.22 crokusek Support nvarchar strings and a custom separator. 2017.12.06 crokusek Trim leading and trailing whitespace from each element. 2019.01.26 crokusek Remove newlines -------------------------------------------------------------------------------------------------*/ begin declare @pos int, @textpos int, @chunklen smallint, @str nvarchar(4000), @tmpstr nvarchar(4000), @leftover nvarchar(4000), @csvList nvarchar(max) = iif(@pSeparator not in (char(13), char(10), char(13) + char(10)), replace(replace(@pCsvList, char(13), ''), char(10), ''), @pCsvList); -- remove newlines set @textpos = 1 set @leftover = '' while @textpos <= len(@csvList) begin set @chunklen = 4000 - len(@leftover) set @tmpstr = ltrim(@leftover + substring(@csvList, @textpos, @chunklen)) set @textpos = @textpos + @chunklen set @pos = charindex(@pSeparator, @tmpstr) while @pos > 0 begin set @str = substring(@tmpstr, 1, @pos - 1) set @str = (select TrimmedString from Common.Trim(@str, default)); insert @tbl (value) values(@str); set @tmpstr = ltrim(substring(@tmpstr, @pos + 1, len(@tmpstr))) set @pos = charindex(@pSeparator, @tmpstr) end set @leftover = @tmpstr end -- Handle @leftover set @str = (select TrimmedString from Common.Trim(@leftover, default)); if @str <> '' insert @tbl (value) values(@str); return end GO create function Common.ufn_SplitDbIdentifier(@pIdentifier nvarchar(300)) returns @table table ( InstanceName nvarchar(300) not null, DatabaseName nvarchar(300) not null, SchemaName nvarchar(300), BaseName nvarchar(300) not null, FullTempDbBaseName nvarchar(300), -- non-null for tempdb (e.g. #Abc____...) InstanceWasSpecified bit not null, DatabaseWasSpecified bit not null, SchemaWasSpecified bit not null, IsCurrentInstance bit not null, IsCurrentDatabase bit not null, IsTempDb bit not null, OrgIdentifier nvarchar(300) not null ) as /*----------------------------------------------------------------------------------------------------------- Purpose: Split a Sql Server Identifier into its parts, providing appropriate default values and handling temp table (tempdb) references. Example: select * from Common.ufn_SplitDbIdentifier('t') union all select * from Common.ufn_SplitDbIdentifier('s.t') union all select * from Common.ufn_SplitDbIdentifier('d.s.t') union all select * from Common.ufn_SplitDbIdentifier('i.d.s.t') union all select * from Common.ufn_SplitDbIdentifier('#d') union all select * from Common.ufn_SplitDbIdentifier('tempdb..#d'); -- Empty select * from Common.ufn_SplitDbIdentifier('illegal name'); Modified By Description ---------- -------------- ----------------------------------------------------------------------------- 2013.09.27 crokusek Initial version. -----------------------------------------------------------------------------------------------------------*/ begin declare @name nvarchar(300) = ltrim(rtrim(@pIdentifier)); -- Return an empty table as a "throw" -- --Removed for SO post --if (Common.ufn_IsSpacelessLiteralIdentifier(@name) = 0) -- return; -- Find dots starting from the right by reversing first. declare @revName nvarchar(300) = reverse(@name); declare @firstDot int = charindex('.', @revName); declare @secondDot int = iif(@firstDot = 0, 0, charindex('.', @revName, @firstDot + 1)); declare @thirdDot int = iif(@secondDot = 0, 0, charindex('.', @revName, @secondDot + 1)); declare @fourthDot int = iif(@thirdDot = 0, 0, charindex('.', @revName, @thirdDot + 1)); --select @firstDot, @secondDot, @thirdDot, @fourthDot, len(@name); -- Undo the reverse() (first dot is first from the right). -- set @firstDot = iif(@firstDot = 0, 0, len(@name) - @firstDot + 1); set @secondDot = iif(@secondDot = 0, 0, len(@name) - @secondDot + 1); set @thirdDot = iif(@thirdDot = 0, 0, len(@name) - @thirdDot + 1); set @fourthDot = iif(@fourthDot = 0, 0, len(@name) - @fourthDot + 1); --select @firstDot, @secondDot, @thirdDot, @fourthDot, len(@name); declare @baseName nvarchar(300) = substring(@name, @firstDot + 1, len(@name) - @firstdot); declare @schemaName nvarchar(300) = iif(@firstDot - @secondDot - 1 <= 0, null, substring(@name, @secondDot + 1, @firstDot - @secondDot - 1)); declare @dbName nvarchar(300) = iif(@secondDot - @thirdDot - 1 <= 0, null, substring(@name, @thirdDot + 1, @secondDot - @thirdDot - 1)); declare @instName nvarchar(300) = iif(@thirdDot - @fourthDot - 1 <= 0, null, substring(@name, @fourthDot + 1, @thirdDot - @fourthDot - 1)); with input as ( select coalesce(@instName, '[' + @@servername + ']') as InstanceName, coalesce(@dbName, iif(left(@baseName, 1) = '#', 'tempdb', db_name())) as DatabaseName, coalesce(@schemaName, iif(left(@baseName, 1) = '#', 'dbo', schema_name())) as SchemaName, @baseName as BaseName, iif(left(@baseName, 1) = '#', ( select [name] from tempdb.sys.objects where object_id = object_id('tempdb..' + @baseName) ), null) as FullTempDbBaseName, iif(@instName is null, 0, 1) InstanceWasSpecified, iif(@dbName is null, 0, 1) DatabaseWasSpecified, iif(@schemaName is null, 0, 1) SchemaWasSpecified ) insert into @table select i.InstanceName, i.DatabaseName, i.SchemaName, i.BaseName, i.FullTempDbBaseName, i.InstanceWasSpecified, i.DatabaseWasSpecified, i.SchemaWasSpecified, iif(i.InstanceName = '[' + @@servername + ']', 1, 0) as IsCurrentInstance, iif(i.DatabaseName = db_name(), 1, 0) as IsCurrentDatabase, iif(left(@baseName, 1) = '#', 1, 0) as IsTempDb, @name as OrgIdentifier from input i; return; end GO
источник
Вы можете использовать, например, что-то вроде этого:
-- DB1..Tb1 have values than DB2..Tb1 not have Select Col1,Col2,Col3 From DB1..Tb1 except Select Col1,Col2,Col3 From DB2..Tb1 -- Now we change order -- DB2..Tb1 have values than DB1..Tb1 not have Select Col1,Col2,Col3 From DB2..Tb1 except Select Col1,Col2,Col3 From DB1..Tb1
источник
Для простого дымового теста, когда вы пытаетесь убедиться, что две таблицы совпадают, не беспокоясь об именах столбцов:
--ensure tables have matching records Select count (*) from tbl_A Select count (*) from tbl_B --create temp table of all records in both tables Select * into #demo from tbl_A Union All Select * from tbl_B --Distinct #demo records = Total #demo records/2 = Total tbl_A records = total tbl_B records Select distinct * from #demo
Вы можете легко написать процедуру сохранения для сравнения пакета таблиц.
источник
Существует проблема производительности, связанная с левым соединением, а также с полным соединением с большими данными.
На мой взгляд, это лучшее решение:
select [First Name], count(1) e from (select * from [Temp Test Data] union all select * from [Temp Test Data 2]) a group by [First Name] having e = 1
источник