Wanted to select from a table but it's ignoring it - sql

I want to have a select statement that takes a table parameter of IDs. The desired behavior is that if there are no elements in the table, get all of the values but if there are elements in the table, only select those who's values match the ID parameter of the table.
My where clause looks like this:
SELECT * FROM BLAH
WHERE [TransactionDateTime] BETWEEN #BeginTransDate AND #EndTransDate AND
((SELECT COUNT(ID) FROM #LocationIds) = 0) OR (EXISTS (SELECT 1 FROM
#LocationIds WHERE t.LocationId = ID))
However, the result is always bringing back all locations even where the LocationIds table contains one value and that ID matches only one value.
I can set the values such as, but it still brings back all locations:
DECLARE #LocationIds TABLE
(
ID int
)
INSERT INTO #LocationIds (ID)
VALUES (10227)

Personally, I do this as 2 queries, as the query plan for when there are rows in #LocationIds and is likely to differ enough that the reuse of the plan won't be beneficial to the other, Therefore you get:
IF (SELECT COUNT(*) FROM #LocationIds) = 0 BEGIN
SELECT *
FROM BLAH
WHERE [TransactionDateTime] BETWEEN #BeginTransDate AND #EndTransDate;
END ELSE BEGIN
SELECT *
FROM BLAH B
WHERE [TransactionDateTime] BETWEEN #BeginTransDate AND #EndTransDate
AND EXISTS (SELECT 1
FROM #LocationIds L
WHERE B.LocationID = L.ID);
END
Alternatively, you could you dynamic SQL to build the query, depending on if the values of #LociationIDs is needed:
DECLARE #SQL nvarchar(MAX);
SET #SQL = N'SELECT *' + NCHAR(13) + NCHAR(10) +
N'FROM BLAH B' + NCHAR(13) + NCHAR(10) +
N'WHERE [TransactionDateTime] BETWEEN #BeginTransDate AND #EndTransDate' +
CASE WHEN (SELECT COUNT(*) FROM #LocationIds) = 0 THEN ';'
ELSE NCHAR(13) + NCHAR(10) +
N' AND EXISTS (SELECT 1' + NCHAR(13) + NCHAR(10) +
N' FROM #LocationIds L' + NCHAR(13) + NCHAR(10) +
N' WHERE B.LocationID = L.ID);'
END;
EXEC sp_executesql #SQL, N'#BeginTransDate date, #EndTransDate date', #BeginTransDate = #BeginTransDate, #EndTransDate = #EndTransDate;
Note I have guessed that #BeginTransDate and #EndTransDate are a date, and not a datetime. If they are the latter, then that query is unlikely to work as you want anyway, and you would be better of using the logic below:
WHERE TransactionDateTime >= #BeginTransDate
AND TransactionDateTime < DATEADD(DAY, 1, #EndTransDate)

You are missing a pair brackets.
WHERE [TransactionDateTime] BETWEEN #BeginTransDate AND #EndTransDate AND
( ((SELECT COUNT(ID) FROM #LocationIds) = 0) OR (EXISTS (SELECT 1 FROM
#LocationIds WHERE t.LocationId = ID)) )
LEFT JOIN might be a simpler solution.
SELECT b.*
FROM BLAH b
LEFT JOIN #LocationIds l ON l.ID = b.LocationId

Try this:
SELECT *
FROM BLAH
WHERE [TransactionDateTime] BETWEEN #BeginTransDate AND #EndTransDate
AND (
(SELECT COUNT(ID) FROM #LocationIds) = 0
OR
ID in (SELECT ID FROM #LocationIds)
)

Related

Select column name dynamically based on rows from another table in SQL server

I need to make below situation. I need a.DATE_TIME field to find right column name in parentheses. Because there are columns for each days and I need calculate day from SHIFT_TABLE. Column names start with 'TPR' plus day like TPR13
DECLARE #SQLText NVARCHAR(MAX) = ''
SELECT #SQLText += 'SELECT (SELECT '
SELECT #SQLText += ( 'TPR' + Convert(varchar,DATEPART(day,a.DATE_TIME) )
) +
' from SHIFT_TABLE as s Where s.id = a.id ) from MAIN_TABLE as a where a.id=''1'' and a.ay=''12'' and a.yil=''2018'' '
print #SQLText
I take error message: The multi-part identifier "a.DATE_TIME" could not be bound.
SHIFT_TABLE occurs id and TPR01,TPR02,...TPR31 columns
MAIN_TABLE occurs id, ay, yil and DATE_TIME columns
I have to calculate day from a.DATE_TIME.
As a result I try to reach like this, SELECT (SELECT TPR13 from SHIFT_TABLE as s Where s.id = a.id ) from MAIN_TABLE as a where a.id='1' and a.ay='12' and a.yil='2018'
How can I solve it? Many Thanks,
DECLARE #SQLText NVARCHAR(MAX) = ''
SELECT #SQLText += 'SELECT (SELECT '
SELECT #SQLText += ( 'TPR' + Convert(varchar,DATEPART(day,a.DATE_TIME) )
) +
' from SHIFT_TABLE as s Where s.id = a.id ) from MAIN_TABLE as a where a.id=''1'' and a.ay=''12'' and a.yil=''2018'' '
FROM [YourTableWhere_DATE_TIME_Exists] AS a
print #SQLText

Merging rows to columns

I have the following situation (heavily abstracted, please ignore bad design):
CREATE TABLE dbo.PersonTest (Id INT, name VARCHAR(255))
INSERT INTO dbo.PersonTest
(Id, name )
VALUES (1, 'Pete')
, (1, 'Marie')
, (2, 'Sam')
, (2, 'Daisy')
I am looking for the following result:
Id Name1 Name2
1 Marie Pete
2 Daisy Sam
So, for each Id, the rows should be merged.
Getting this result I used the following query:
WITH PersonRN AS
(
SELECT *
, ROW_NUMBER() OVER(PARTITION BY Id ORDER BY name) RN
FROM dbo.PersonTest
)
SELECT PT1.Id
, PT1.name Name1
, PT2.name Name2
FROM PersonRN AS PT1
LEFT JOIN PersonRN AS PT2 -- Left join in case there's only 1 name
ON PT2.Id = PT1.Id
AND PT2.RN = 2
WHERE PT1.RN = 1
Which works perfectly fine.
My question is: Is this the best way (best in terms of performance and resilience)? If, for example, one of these Id's has a third name, this third name is ignored by my query. I'm thinking the best way to deal with that would be dynamic SQL, which would be fine, but if it can be done without dynamic, I would prefer that.
Aside from dynamic PIVOT, you can do this using Dynamic Crosstab, which I prefer for readability.
SQL Fiddle
DECLARE #sql1 VARCHAR(1000) = '',
#sql2 VARCHAR(1000) = '',
#sql3 VARCHAR(1000) = ''
DECLARE #max INT
SELECT TOP 1 #max = COUNT(*) FROM PersonTest GROUP BY ID ORDER BY COUNT(*) DESC
SELECT #sql1 =
'SELECT
ID' + CHAR(10)
SELECT #sql2 = #sql2 +
' , MAX(CASE WHEN RN =' + CONVERT(VARCHAR(5), RN)
+ ' THEN name END) AS ' + QUOTENAME('Name' + CONVERT(VARCHAR(5), RN)) + CHAR(10)
FROM(
SELECT TOP(#max)
ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) AS RN
FROM sys.columns
)t
ORDER BY RN
SELECT #sql3 =
'FROM(
SELECT *,
RN = ROW_NUMBER() OVER(PARTITION BY ID ORDER BY name)
FROM PersonTest
)t
GROUP BY ID
ORDER BY ID'
PRINT (#sql1 + #sql2 + #sql3)
EXEC (#sql1 + #sql2 + #sql3)

2005 SSRS/SQL Server PIVOT results need reversing

Preamble: I've read through the three questions/answers here,here, and here, with big ups to #cade-roux. This all stemmed from trying to use the following data in a 2005 SSRS matrix that, I believe, doesn't work because I want to show a member having to take a test multiple times, and SSRS seems to require the aggregate where I want to show all dates.
I get the following results in my table, which seems to be showing all the data correctly:
How do I change the code below to show a) the "tests" at the top of each column with b) if it's called for, the multiple dates that test was taken?
Here's the code I have to produce the table, above. Much of it is commented out as I was just trying to get the pivot to work, but you may notice I am also trying to specify which test column comes first.
CREATE TABLE #tmp ( ---THIS WORKS BUT TESTS ARE VERTICAL
[TEST] [varchar](30) NOT NULL,
[ED] [datetime] NOT NULL
)
--WHERE THE TEST AND ED COME FROM
INSERT #TMP
SELECT DISTINCT
-- N.FULL_NAME
-- , CONVERT(VARCHAR(30), AM.CREATEDATE, 101) AS ACCOUNT_CLAIMED
-- , N.EMAIL
-- , NULL AS 'BAD EMAIL'
-- , CONVERT(VARCHAR(30), AC.EFFECTIVE_DATE, 101) AS EFFECTIVE_DATE
AC.PRODUCT_CODE AS TEST
, CONVERT(VARCHAR(30), AC.EFFECTIVE_DATE, 101) AS ED
-- , CASE
-- WHEN AC.PRODUCT_CODE = 'NewMem_Test' THEN '9'
-- WHEN AC.PRODUCT_CODE = 'NM_Course1' THEN '1'
-- WHEN AC.PRODUCT_CODE = 'NMEP_Course1' THEN '2'
-- WHEN AC.PRODUCT_CODE = 'NMEP_Course2' THEN '3'
-- WHEN AC.PRODUCT_CODE = 'NMEP_Course3' THEN '4'
-- WHEN AC.PRODUCT_CODE = 'NMEP_Course4' THEN '5'
-- WHEN AC.PRODUCT_CODE = 'NMEP_Course5' THEN '6'
-- WHEN AC.PRODUCT_CODE = 'NMEP_Course6' THEN '7'
-- WHEN AC.PRODUCT_CODE = 'NMEP_Course7' THEN '8'
-- END AS 'COLUMN_ORDER'
FROM NAME N
JOIN USERMAIN UM
ON N.ID = UM.CONTACTMASTER
JOIN formTransLog TL
ON UM.USERID = TL.USERNAME
JOIN anet_Users AU
ON UM.USERID = AU.USERNAME
JOIN anet_Membership AM
ON AU.USERID = AM.USERID
JOIN ACTIVITY AC
ON N.ID = AC.ID
AND AC.ACTIVITY_TYPE = 'COURSE'
AND AC.PRODUCT_CODE LIKE 'N%'
--ORDER BY 1, 7
DECLARE #sql AS varchar(max)
DECLARE #pivot_list AS varchar(max) -- Leave NULL for COALESCE technique
DECLARE #select_list AS varchar(max) -- Leave NULL for COALESCE technique
SELECT #pivot_list = COALESCE(#pivot_list + ', ', '') + '[' + CONVERT(varchar, PIVOT_CODE) + ']'
,#select_list = COALESCE(#select_list + ', ', '') + '[' + CONVERT(varchar, PIVOT_CODE) + '] AS [col_' + CONVERT(varchar, PIVOT_CODE) + ']'
FROM (
SELECT DISTINCT PIVOT_CODE
FROM (
SELECT TEST, ED, ROW_NUMBER() OVER (PARTITION BY TEST ORDER BY ED) AS PIVOT_CODE
FROM #tmp
) AS rows
) AS PIVOT_CODES
SET #sql = '
;WITH p AS (
SELECT TEST, ED, ROW_NUMBER() OVER (PARTITION BY TEST ORDER BY ED) AS PIVOT_CODE
FROM #tmp
)
SELECT TEST, ' + #select_list + '
FROM p
PIVOT (
MIN(ED)
FOR PIVOT_CODE IN (
' + #pivot_list + '
)
) AS pvt
'
PRINT #sql
EXEC (#sql)
EDIT:
The goal is to have the report in SSRS look like this:
I was able to produce the results you were looking for by adding in a number (RowNum) to the query underneath the PIVOT operator. It doesn't have to be in the final query (though you might want it for client-side sorting), but by having it in the underlying layer the PIVOT operation treats that number like a member of a GROUP BY clause.
Please look through my sample SQL below and let me know if this matches your criteria.
CREATE TABLE #TMP
(
Name VARCHAR(10),
Test VARCHAR(20),
EffectiveDate DATETIME
)
INSERT INTO #TMP (Name, Test, EffectiveDate)
SELECT 'Jane', 'NM_Course1', '01/17/2014' UNION
SELECT 'Jane', 'NMEP_Course1', '12/19/2013' UNION
SELECT 'Jane', 'NMEP_Course1', '12/20/2013' UNION
SELECT 'Jane', 'NMEP_Course2', '12/19/2013' UNION
SELECT 'Jane', 'NMEP_Course2', '12/22/2013' UNION
SELECT 'Jane', 'NMEP_Course2', '01/05/2014' UNION
SELECT 'John', 'NM_Course1', '01/17/2014' UNION
SELECT 'John', 'NMEP_Course1', '01/11/2014'
DECLARE #sql AS varchar(max)
DECLARE #pivot_list AS varchar(max) -- Leave NULL for COALESCE technique
DECLARE #select_list AS varchar(max) -- Leave NULL for COALESCE technique
SELECT #pivot_list = COALESCE(#pivot_list + ', ', '') + '[' + CONVERT(varchar, PIVOT_CODE) + ']'
,#select_list = COALESCE(#select_list + ', ', '') + '[' + CONVERT(varchar, PIVOT_CODE) + '] AS [col_' + CONVERT(varchar, PIVOT_CODE) + ']'
FROM (
SELECT DISTINCT PIVOT_CODE
FROM (
SELECT TEST AS PIVOT_CODE
FROM #tmp
) AS rows
) AS PIVOT_CODES
SET #sql = '
SELECT Name, ' + #select_list + '
FROM
(
SELECT b.Name, RowNum, b.EffectiveDate, b.TEST AS PIVOT_CODE
FROM
(
SELECT Name, Test, EffectiveDate, ROW_NUMBER() OVER (PARTITION BY NAME, TEST ORDER BY EffectiveDate) RowNum
FROM #Tmp
) b
) p
PIVOT (
MIN(EffectiveDate)
FOR PIVOT_CODE IN (
' + #pivot_list + '
)
) AS pvt
ORDER BY Name, RowNum
'
PRINT #sql
EXEC (#sql)
DROP TABLE #TMP

How to declare the columns dynamically in a Select query using PIVOT

I am writing a query to get the address for PersonID. Following query is working for me but it only returns with two Addresses. I want to handle the 'n' number of address with a single query. Is there any way to do this?
Many thanks
SELECT
PersonID, PersonName
[Address1], [Address2]
FROM
(
SELECT
P.PersonID,
P.PersonName,
(ROW_NUMBER() OVER(PARTITION BY P.PersonID ORDER BY A.AddressID)) RowID
FROM tblPerson
INNER JOIN tblAddress AS A ON A.PersonID = P.PersonID
) AS AddressTable
PIVOT
(
MAX(AddressID)
FOR RowID IN ([Address1], [Address2])
) AS PivotTable;
Assuming the following tables and sample data:
USE tempdb;
GO
CREATE TABLE dbo.tblPerson(PersonID INT, PersonName VARCHAR(255));
INSERT dbo.tblPerson SELECT 1, 'Bob'
UNION ALL SELECT 2, 'Charlie'
UNION ALL SELECT 3, 'Frank'
UNION ALL SELECT 4, 'Amore';
CREATE TABLE dbo.tblAddress(AddressID INT, PersonID INT, [Address] VARCHAR(255));
INSERT dbo.tblAddress SELECT 1,1,'255 1st Street'
UNION ALL SELECT 2,2,'99 Elm Street'
UNION ALL SELECT 3,2,'67 Poplar Street'
UNION ALL SELECT 4,2,'222 Oak Ave.'
UNION ALL SELECT 5,1,'36 Main Street, Suite 22'
UNION ALL SELECT 6,4,'77 Sicamore Ct.';
The following query gets the results you want, and shows how it handles 0, 1 or n addresses. In this case the highest number is 3 but you can play with more addresses if you like by adjusting the sample data slightly.
DECLARE #col NVARCHAR(MAX) = N'',
#sel NVARCHAR(MAX) = N'',
#from NVARCHAR(MAX) = N'',
#query NVARCHAR(MAX) = N'';
;WITH m(c) AS
(
SELECT TOP 1 c = COUNT(*)
FROM dbo.tblAddress
GROUP BY PersonID
ORDER BY c DESC
)
SELECT #col = #col + ',[Address' + RTRIM(n.n) + ']',
#sel = #sel + ',' + CHAR(13) + CHAR(10) + '[Address' + RTRIM(n.n) + '] = x'
+ RTRIM(n.n) + '.Address',
#from = #from + CHAR(13) + CHAR(10) + ' LEFT OUTER JOIN xMaster AS x'
+ RTRIM(n.n) + ' ON x' + RTRIM(n.n) + '.PersonID = p.PersonID AND x'
+ RTRIM(n.n) + '.rn = ' + RTRIM(n.n)
FROM m CROSS JOIN (SELECT n = ROW_NUMBER() OVER (ORDER BY [object_id])
FROM sys.all_columns) AS n WHERE n.n <= m.c;
SET #query = N';WITH xMaster AS
(
SELECT PersonID, Address,
rn = ROW_NUMBER() OVER (PARTITION BY PersonID ORDER BY Address)
FROM dbo.tblAddress
)
SELECT PersonID, PersonName' + #col
+ ' FROM
(
SELECT p.PersonID, p.PersonName, ' + STUFF(#sel, 1, 1, '')
+ CHAR(13) + CHAR(10) + ' FROM dbo.tblPerson AS p ' + #from + '
) AS Addresses;';
PRINT #query;
--EXEC sp_executesql #query;
If you print the SQL you will see this result:
;WITH xMaster AS
(
SELECT PersonID, Address,
rn = ROW_NUMBER() OVER (PARTITION BY PersonID ORDER BY Address)
FROM dbo.tblAddress
)
SELECT PersonID, PersonName,[Address1],[Address2],[Address3] FROM
(
SELECT p.PersonID, p.PersonName,
[Address1] = x1.Address,
[Address2] = x2.Address,
[Address3] = x3.Address
FROM dbo.tblPerson AS p
LEFT OUTER JOIN xMaster AS x1 ON x1.PersonID = p.PersonID AND x1.rn = 1
LEFT OUTER JOIN xMaster AS x2 ON x2.PersonID = p.PersonID AND x2.rn = 2
LEFT OUTER JOIN xMaster AS x3 ON x3.PersonID = p.PersonID AND x3.rn = 3
) AS Addresses;
If you execute it, you will see this:
I know the query to get here is an ugly mess, but your requirement dictates it. It would be easier to return a comma-separated list as I suggested in my comment, or to have the presentation tier deal with the pivoting.

sql query to return differences between two tables

I am trying to compare two tables, SQL Server, to verify some data. I want to return all the rows from both tables where data is either in one or the other. In essence, I want to show all the discrepancies. I need to check three pieces of data in doing so, FirstName, LastName and Product.
I'm fairly new to SQL and it seems like a lot of the solutions I'm finding are over complicating things. I don't have to worry about NULLs.
I started by trying something like this:
SELECT DISTINCT [First Name], [Last Name], [Product Name] FROM [Temp Test Data]
WHERE ([First Name] NOT IN (SELECT [First Name]
FROM [Real Data]))
I'm having trouble taking this further though.
Thanks!
EDIT:
Based on the answer by #treaschf I have been trying to use a variation of the following query:
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)
But I keep getting 0 results back, when I know that there is at least 1 row in td that is not in d.
EDIT:
Ok, I think I figured it out. At least in my few minutes of testing it seems to work good enough.
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])))
This is basically going to tell me what is in my test data that is not in my real data. Which is completely fine for what I need to do.
( SELECT * FROM table1
EXCEPT
SELECT * FROM table2)
UNION ALL
( SELECT * FROM table2
EXCEPT
SELECT * FROM table1)
IF you have tables A and B, both with colum C, here are the records, which are present in table A but not in B:
SELECT A.*
FROM A
LEFT JOIN B ON (A.C = B.C)
WHERE B.C IS NULL
To get all the differences with a single query, a full join must be used, like this:
SELECT A.*, B.*
FROM A
FULL JOIN B ON (A.C = B.C)
WHERE A.C IS NULL OR B.C IS NULL
What you need to know in this case is, that when a record can be found in A, but not in B, than the columns which come from B will be NULL, and similarly for those, which are present in B and not in A, the columns from A will be null.
I know that this may not be a popular answer but I do agree with #Randy Minder on using third party tool when more complex comparison is needed.
This specific case here is easy and for this case such tools are not needed but this can get complex easily if you introduce more columns, databases on two servers, more complex comparison criteria and such.
There are a lot of these tools such as ApexSQL Data Diff or Quest Toad and you can always use them in trial mode to get the job done.
To get all the differences between two tables, you can use like me this SQL request :
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
;
Simple variation on #erikkallen answer that shows which table the row is present in:
( SELECT 'table1' as source, * FROM table1
EXCEPT
SELECT * FROM table2)
UNION ALL
( SELECT 'table2' as source, * FROM table2
EXCEPT
SELECT * FROM table1)
If you get an error
All queries combined using a UNION, INTERSECT or EXCEPT operator must have an equal number of expressions in their target lists.
then it may help to add
( 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)
If you want to get which column values are different, you could use Entity-Attribute-Value model:
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 EXAMPLE
Presenting the Cadillac of Diffs as an SP. See within for the basic template that was based on answer by #erikkallen. It supports
Duplicate row sensing (most other answers here do not)
Sort results by argument
Limit to specific columns
Ignore columns (e.g. ModifiedUtc)
Cross database tables names
Temp tables (use as workaround to diff views)
Usage:
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)
Code:
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
You can use except , for example something like this :
-- 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
Try this :
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])
Much simpler to read.
This will do the trick, similar with Tiago's solution, return "source" table as well.
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
result will contain differences between tables, in column _tabloc you will have table reference.
For a simple smoke test where you you're trying to ensure two tables match w/out worrying about column names:
--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
You can easily write a store procedure to compare a batch of tables.
There is a performance issue related with the left join as well as full join with large data.
In my opinion this is the best solution:
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
EXCEPT and NOT EXISTS are good ways to quickly find differences between sets of data, but I often want to know exactly which columns are different and how they are different.
What I find useful in these situations is using UNPIVOT to convert columns into key value pairs that one can compare row for row.
In order to be useful though, there needs to be a way to match the records you want to compare, in your example something like a social security number or a "person id":
CREATE TABLE [RealData]
(
[PersonId] INT,
[FirstName] NVARCHAR(100),
[LastName] NVARCHAR(100),
[ProductName] NVARCHAR(100)
)
CREATE TABLE [TempTestData]
(
[PersonId] INT,
[FirstName] NVARCHAR(100),
[LastName] NVARCHAR(100),
[ProductName] NVARCHAR(100)
)
INSERT INTO [RealData] ([PersonId], [FirstName], [LastName], [ProductName])
VALUES
(1, 'Al', 'Bundy', 'Ladies Size 12'),
(2, 'Peggy', 'Bundy', 'TV Guide')
INSERT INTO [TempTestData] ([PersonId], [FirstName], [LastName], [ProductName])
VALUES
(1, 'Al', 'Bundy', 'Ladies Size 13'),
(2, 'Peggy', 'Bundy', 'TV Guide')
And the UNPIVOT with a couple of CTEs thrown in:
;WITH RealDataCte AS (
SELECT
'Real Data' AS [DataSource],
unpivotedRealData.*
FROM
(SELECT
CAST([PersonId] AS NVARCHAR(100)) AS [PersonId],
[FirstName],
[LastName],
[ProductName]
FROM [RealData]) AS realData
UNPIVOT
(ColumnValue FOR ColumnName IN ([FirstName], [LastName], [ProductName])) AS unpivotedRealData
),
TempTestDataCte AS (
SELECT
'Temp Test Data' AS [DataSource],
unpivotedDempTestData.*
FROM
(SELECT
CAST([PersonId] AS NVARCHAR(100)) AS [PersonId],
[FirstName],
[LastName],
[ProductName]
FROM [TempTestData]) AS tempTestData
UNPIVOT
(ColumnValue FOR ColumnName IN ([FirstName], [LastName], [ProductName])) AS unpivotedDempTestData
)
SELECT
RealDataCte.[DataSource],
RealDataCte.[ColumnName],
RealDataCte.[ColumnValue],
TempTestDataCte.[DataSource],
TempTestDataCte.[ColumnName],
TempTestDataCte.[ColumnValue],
CASE WHEN RealDataCte.[ColumnValue] <> TempTestDataCte.[ColumnValue] THEN 'YES' ELSE 'NO' END AS ColumnsDiffer
FROM RealDataCte
INNER JOIN
TempTestDataCte
ON RealDataCte.[ColumnName] = TempTestDataCte.[ColumnName]
AND RealDataCte.[PersonId] = TempTestDataCte.[PersonId]
WHERE
RealDataCte.[ColumnValue] <> TempTestDataCte.[ColumnValue]
The result - the 2 products are different:
This is great if you have many, many columns to compare over a large number of rows.
It can take a while to setup, though, and you do need to convert every column value to the same type, which can take a while (notice the CAST for PersonId).
If the dataset is really large you may also want to use temp tables instead of CTEs.
SQL Fiddle Example Here
This ought to work just fine.
A select query to find difference between row counts of two different tables
select (t1 - t2) as count_diff from (select (select count(*) from tbl1) t1, (select count(*) from tbl2) t2)