SQL Pivot table with string fields - sql-server

I am new with SQL Pivot, so i would like to have a detail explaination regarding to it. My table is like the following:
PID | NAME | PlateNumber | COUNTRY|
------------------------------------------
111 | Alex | ab123456 | GB |
------------------------------------------
111 | Alex | fe123344 | ES |
------------------------------------------
111 | Alex | r223456e | US |
------------------------------------------
112 | Simon | t22er563 | GB |
------------------------------------------
112 | Simon | q32345ke | DE |
------------------------------------------
113 | Ben | ve923456 | IT |
------------------------------------------
And i would to have the result in the following format:
PID |NAME |PlateNumber1|PlateNumber2| PlateNumber3|COUNTRY1| COUNTRY2| COUNTRY3|
--------------------------------------------------------------------------------
111 | Alex | ab123456 | fe123344 | r223456e | GB | ES | US |
--------------------------------------------------------------------------------
112 | Simon | t22er563| q32345ke | | GB | DE | |
--------------------------------------------------------------------------------
113 | Ben | ve923456| | | IT | | |
--------------------------------------------------------------------------------
Could you please help me with this?
Thank you in advance!

The traditional cross tab / conditional aggregation version would be like so:
test setup: http://rextester.com/SKMUL25726
select
pid
, name
, PlateNumber1 = max(case when rn = 1 then PlateNumber end)
, PlateNumber2 = max(case when rn = 2 then PlateNumber end)
, PlateNumber3 = max(case when rn = 3 then PlateNumber end)
, Country1 = max(case when rn = 1 then Country end)
, Country2 = max(case when rn = 2 then Country end)
, Country3 = max(case when rn = 3 then Country end)
from (
select t.*
, rn=row_number() over (partition by PID order by platenumber)
from t
) as t
group by pid, name
returns:
+-----+-------+--------------+--------------+--------------+----------+----------+----------+
| pid | name | PlateNumber1 | PlateNumber2 | PlateNumber3 | Country1 | Country2 | Country3 |
+-----+-------+--------------+--------------+--------------+----------+----------+----------+
| 111 | Alex | ab123456 | fe123344 | r223456e | GB | ES | US |
| 113 | Ben | ve923456 | NULL | NULL | IT | NULL | NULL |
| 112 | Simon | q32345ke | t22er563 | NULL | DE | GB | NULL |
+-----+-------+--------------+--------------+--------------+----------+----------+----------+

Using PIVOT:
with your_table(PID , NAME , PlateNumber , Country) as (
select 111 , 'Alex' , 'ab123456' , 'GB' union all
select 111 , 'Alex' , 'fe123344' , 'ES' union all
select 111 , 'Alex' , 'r223456e' , 'US' union all
select 112 , 'Simon' , 't22er563' , 'GB' union all
select 112 , 'Simon' , 'q32345ke' , 'DE' union all
select 113 , 'Ben' , 've923456' , 'IT'
)
select pid,
name,
max(PlateNumber1) PlateNumber1,
max(PlateNumber2) PlateNumber2,
max(PlateNumber3) PlateNumber3,
max(Country1) Country1,
max(Country2) Country2,
max(Country3) Country3
from (
select *,
'Country' + cast(row_number() over (
partition by PID order by pnum
) as varchar(30)) cn
from (
select t.*,PlateNumber pnum,
'PlateNumber' + cast(row_number() over (
partition by PID order by PlateNumber
) as varchar(30)) pn
from your_table t
) t
pivot(max(PlateNumber) for pn in ([PlateNumber1], [PlateNumber2], [PlateNumber3])) x
) t
pivot(max(Country) for cn in ([Country1], [Country2], [Country3])) x
group by pid,
name;
Demo

If the PlateNumber count is not fixed of PID, you can use dynamic statement.
CREATE TABLE #tt(PID INT,[Name] VARCHAR(10),PlateNumber VARCHAR(10),Country VARCHAR(5))
INSERT INTO #tt
select 111 , 'Alex' , 'ab123456' , 'GB' union all
select 111 , 'Alex' , 'fe123344' , 'ES' union all
select 111 , 'Alex' , 'r223456e' , 'US' union all
select 112 , 'Simon' , 't22er563' , 'GB' union all
select 112 , 'Simon' , 'q32345ke' , 'DE' union all
select 113 , 'Ben' , 've923456' , 'IT'
DECLARE #SQL VARCHAR(max),#col VARCHAR(max)
---- Get the max line count of all the PID
DECLARE #MaxNumber INT =0
SELECT #MaxNumber=CASE WHEN COUNT(0)>#MaxNumber THEN count(0) ELSE #MaxNumber END FROM #tt AS t GROUP BY t.Name,t.PID
PRINT #MaxNumber
SELECT #col=ISNULL(#col+',','')+'PlateNumber'+LTRIM(sv.number)+',Country'+LTRIM(sv.number)
FROM master.dbo.spt_values AS sv WHERE sv.type='P' AND sv.number BETWEEN 1 AND #MaxNumber
PRINT #col
SET #SQL='
SELECT * FROM (
SELECT pid,[name],c.col_value,c.col_title+LTRIM(ROW_NUMBER()OVER(PARTITION BY t.PID,[name],c.col_title ORDER BY (SELECT 0))) AS col_title FROM #tt AS t
CROSS APPLY(VALUES(''PlateNumber'',t.PlateNumber),(''Country'',t.Country))c(col_title,col_value)
) AS t
PIVOT(MAX(col_value) FOR col_title IN ('+#col+')) p'
EXEC(#SQL)
pid name PlateNumber1 Country1 PlateNumber2 Country2 PlateNumber3 Country3
----------- ---------- ------------ ---------- ------------ ---------- ------------ ----------
111 Alex r223456e GB fe123344 ES ab123456 US
113 Ben ve923456 IT NULL NULL NULL NULL
112 Simon q32345ke DE t22er563 GB NULL NULL

Related

Merging multiple rows with same ID in SQL Server

What is the most efficient method to combine multiple rows of values with the same ID in SQL Server?
Original data table dbo.ProductCategory:
+-----------+----------+------+
| ProductID | CATID | AA |
+-----------+----------+------+
| 1 | 123 | A |
| 1 | 412 | B |
| 2 | 567 | C |
| 2 | 521 | A |
| 3 | 2 | D |
| 3 | 5 | A |
| 4 | 6 | C |
| 4 | 8 | E |
| 4 | 123 | A |
+----+------+----------+------+
And I'm trying to achieve the following result
+-----------+----------+------+
| ProductID | CATID | AA |
+-----------+----------+------+
| 1 | 123,412 | A,B |
| 2 | 567,521 | C,A |
| 3 | 2,5 | D,A |
| 4 | 6,8,123 | C,E,A|
+----+------+----------+------+
In SQL Server 2017+, you can use STRING_AGG
select ProductId, STRING_AGG(CATID, ',') as CATID, STRING_AGG(AA, ',') AA
from PC
GROUP BY ProductID
Sample Data
DECLARE #Temp AS TABLE (ProductID INT, CATID INT, AA CHAR(2))
INSERT INTO #Temp
SELECT 1 , 123 , 'A' UNION ALL
SELECT 1 , 412 , 'B' UNION ALL
SELECT 2 , 567 , 'C' UNION ALL
SELECT 2 , 521 , 'A' UNION ALL
SELECT 3 , 2 , 'D' UNION ALL
SELECT 3 , 5 , 'A' UNION ALL
SELECT 4 , 6 , 'C' UNION ALL
SELECT 4 , 8 , 'E' UNION ALL
SELECT 4 , 123 , 'A'
Using STUFF() In sql server
SELECT ProductID,STUFF((SELECT CONCAT(', ', CATID)
FROM #Temp i
WHERE i.ProductID = o.ProductID
FOR XML PATH ('')),1,1,'') AS CATID
,STUFF((SELECT CONCAT(', ', AA)
FROM #Temp i
WHERE i.ProductID = o.ProductID
FOR XML PATH ('')),1,1,'') AS AA
FROM #Temp o
GROUP BY ProductID
Result
ProductID CATID AA
------------------------------------
1 123, 412 A , B
2 567, 521 C , A
3 2, 5 D , A
4 6, 8, 123 C , E , A

SQL - How to combine rows

I have the following table which looks at calls and attendances. I got this by using union all on a 'calls' and 'attendances' tables and then used row number on the ID and ordered by dates.
Table1:
Type | ID | Call/AttendanceDate | RowNum
------------|----|---------------------|--------
Attendance | 12 | 2018-09-16 10:11:00 | 82
Call | 12 | 2018-09-18 14:11:47 | 83
Call | 12 | 2018-10-02 17:26:13 | 84
Call | 12 | 2018-10-05 14:58:31 | 85
Attendance | 12 | 2018-10-13 01:41:00 | 86
Call | 12 | 2018-10-13 02:39:12 | 87
Call | 12 | 2018-10-13 04:31:22 | 88
Attendance | 12 | 2018-10-13 14:29:00 | 89
Call | 12 | 2018-10-13 14:59:19 | 90
Attendance | 12 | 2018-10-15 15:50:00 | 91
The code I used for this is:
WITH CTE1 AS
(
SELECT 'Call' as [Type], ID, CallDate AS Date1
FROM CallsTable
UNION ALL
SELECT 'Attendance' as [Type], ID, AttendanceDate AS Date2
FROM AttendanceTable]
)
,CTE2 AS
(
SELECT [Type], Date1, ID, ROW_NUMBER() OVER (PARTITION BY ID ORDER BY Date1 ASC) AS RowNum
FROM CTE1
)
--------------------------------OUTPUT--------------------------------
SELECT a.[Type], a.ID, a.Date1, a.RowNum
FROM CTE2 a
JOIN CTE2 b
ON a.ID= b.ID
AND a.RowNum = b.RowNum + 1
WHERE a.ID = '12'
ORDER BY ID, RowNum
I want to modify this to look like the below output, so that whenever an attendance follows a call, it should be in the same row.
Table2:
Type | ID | CallDate | RowNum | Type | AttendanceDate | RowNum
------|----|------------------|--------|------------|------------------|--------
NULL | 12 | NULL | NULL | Attendance | 16/09/2018 10:11 | 82
Call | 12 | 18/09/2018 14:11 | 83 | NULL | NULL | NULL
Call | 12 | 02/10/2018 17:26 | 84 | NULL | NULL | NULL
Call | 12 | 05/10/2018 14:58 | 85 | Attendance | 13/10/2018 01:41 | 86
Call | 12 | 13/10/2018 02:39 | 87 | NULL | NULL | NULL
Call | 12 | 13/10/2018 04:31 | 88 | Attendance | 13/10/2018 14:29 | 89
Call | 12 | 13/10/2018 14:59 | 90 | Attendance | 15/10/2018 15:50 | 91
Is this possible? What code could I use?
Use FULL JOIN
SELECT
*
FROM
(SELECT * FROM CTE2 WHERE Type = 'CALL') A
FULL JOIN
(SELECT * FROM CTE2 WHERE Type = 'ATTENDANCE') B
ON A.ID = B.ID AND A.RowNum = B.RowNum - 1
You can use APPLY :
SELECT C.[Type], C.ID, C.CallDate, C.RowNum,
(CASE WHEN C2.RowNum - C.RowNum = 1 THEN C2.[TYPE] end) [TYPE],
(CASE WHEN C2.RowNum - C.RowNum = 1 THEN C2.CallDate end) AttendanceDate,
(CASE WHEN C2.RowNum - C.RowNum = 1 THEN C2.RowNum end) RowNum
FROM CTE2 C OUTER APPLY
(SELECT TOP (1) C2.*
FROM CTE2 C2
WHERE C2.ID = C.ID AND C2.[Type] = 'Attendance' AND C2.RowNum > C.RowNum
ORDER BY C2.RowNum
) C2
WHERE C.ID = 12 AND C.[Type] = 'Call';
Not as elegant, but works for me, a table valued function
alter FUNCTION GetCallActivity()
RETURNS #activityTable TABLE
(
call_type varchar(16),
call_id int,
call_date datetime,
call_rownum int,
atnd_type varchar(16),
atnd_id int,
atnd_date datetime,
atnd_rownum int
)
AS
BEGIN
-- initialize the return table
insert into #activityTable
(call_type, call_id, call_date, call_rownum )
select a.type, a.id, a.activity_date, a.rownum
from stack_calls a
where a.type = 'Call'
order by a.activity_date;
-- match to the attendence recs to the call recs
update #activityTable
set atnd_type = b.type,
atnd_id = b.id,
atnd_date = b.activity_date,
atnd_rownum = b.rownum
from stack_calls b
join #activityTable a
on b.rownum = a.call_rownum + 1
where b.type = 'Attendance';
-- deal with the edge cases
insert into #activityTable
( atnd_type, atnd_id, atnd_date, atnd_rownum )
select x.type,
x.id,
x.activity_date,
x.rownum
from
(
select a.type,
a.id,
a.activity_date,
a.rownum,
lag(a.type, 1) over (order by a.activity_date) as prev_type
from stack_calls a
where a.type = 'Attendance'
) x
where x.prev_type is null
RETURN
END
GO

How to insert dummy rows for distinct ids in a result set

I have a result set as follows:
+---------+-----------+--------+-----------------+
| ChildID | ReleaseID | Status | ParentSignature |
+---------+-----------+--------+-----------------+
| 1152 | 68 | Yes | |
| 1152 | 70 | Yes | |
| 5059 | 68 | Yes | ad |
| 5410 | 68 | Yes | 111 |
| 5410 | 70 | Yes | 111 |
| 5410 | 71 | Yes | |
+---------+-----------+--------+-----------------+
In the above result set, there are 3 distinct values in ReleaseID column such as 68, 70 and 71.
Only one child id (5410) has records corresponding to all the releaseids.
My requirement is to get an output result set in which there are records for each child for each release id with blank values for remaining columns.
The number of maximum distinct releaseids can vary. In this example, there are 3 releaseids.
Expected output:-
+---------+-----------+--------+-----------------+
| ChildID | ReleaseID | Status | ParentSignature |
+---------+-----------+--------+-----------------+
| 1152 | 68 | Yes | |
| 1152 | 70 | Yes | |
| 1152 | 71 | | |
| 5059 | 68 | Yes | ad |
| 5059 | 70 | | |
| 5059 | 71 | | |
| 5410 | 68 | Yes | 111 |
| 5410 | 70 | Yes | 111 |
| 5410 | 71 | Yes | |
+---------+-----------+--------+-----------------+
select 1152 as clientid, 68 as releaseid , 'Yes' as status , '' as ParentSignature into #input
union select 1152 , 70 , 'Yes' , ''
union select 5059 , 68 , 'Yes' , 'ad '
union select 5410 , 68 , 'Yes' , '111 '
union select 5410 , 70 , 'Yes' , '111 '
union select 5410 , 71 , 'Yes' , ''
WITH Clients
AS (
SELECT DISTINCT ClientId
FROM #Input
)
,Releases
AS (
SELECT DISTINCT releaseid
FROM #input
)
,ClientReleases
AS (
SELECT ClientId
,ReleaseId
FROM Clients
CROSS JOIN Releases
)
SELECT *
FROM #input
UNION
SELECT ClientId
,ReleaseId
,''
,''
FROM ClientReleases cr
WHERE NOT EXISTS (
SELECT 1
FROM #input i
WHERE i.clientid = cr.clientid
AND i.releaseid = cr.releaseid
)
You can query as below:
;With cte as (
SELECT distinct clientid, a.releaseid FROM #input x
cross apply (select distinct releaseid from #input) a
) Select c.*, i.[Status], i.ParentSignature from cte c
left join #input i
on c.clientid = i.clientid
and c.releaseid = i.releaseid
select *
into #input
from (
select 1152 as clientid, 68 as releaseid , 'Yes' as status , '' as ParentSignature union all
select 1152 , 70 , 'Yes' , '' union all
select 5059 , 68 , 'Yes' , 'ad ' union all
select 5410 , 68 , 'Yes' , '111 ' union all
select 5410 , 70 , 'Yes' , '111 ' union all
select 5410 , 71 , 'Yes' , ''
) x
SELECT * FROM #input x
SELECT b.clientid
FROM #input b
GROUP BY b.clientid
HAVING COUNT(DISTINCT b.releaseid) = (SELECT COUNT(DISTINCT a.releaseid) FROM #input a)
Note: If there is an UNIQUE index/constraint defined on {clientid, releaseid} then I would remove DISTINCT from COUNT(DISTINCT b.releaseid).

How to pull the average sales per visit of the top 10% of customers from 3 segments in T-SQL?

I'm newer to my job and i am not used to doing queries as complicated as the one i am being asked to do. Please help! I'm trying to pull the average sales per visit of the top 10% of customers from 3 customer segments. I'm stuck on pulling only the top 10% of customers. Here are two sample tables and what i have so far.
SELECT
cust_segment.segment,
AVG(cust_sales.sales) / cust_sales.visits AS "sales/vist"
FROM
cust_segment
INNER JOIN
cust_sales ON cust_segment.Customer = cust_sales.Customer
WHERE
cust_sales in (SELECT TOP 10 Percent cust_sales.sales, cust_segement.segment
FROM cust_segment
INNER JOIN cust_sales ON cust_segment.customer = cust_sales.customer)
GROUP BY
segment;
cust_segment
+-------------+---------+
| Customer | Segment |
+-------------+---------+
| 10000834678 | A |
| 10000467169 | A |
| 10000217202 | B |
| 10001562687 | C |
| 10000742574 | C |
| 10001577918 | A |
| 10000825179 | B |
| 10000019009 | B |
| 10001225606 | C |
| 10000473429 | A |
+-------------+---------+
cust_sales
+-------------+----------------+--------+
| Customer | Sales | Visits |
+-------------+----------------+--------+
| 10000834678 | $ 54.56 | 8 |
| 10000467169 | $ 27.61 | 7 |
| 10000217202 | $ 150.01 | 39 |
| 10001562687 | $ 39.59 | 8 |
| 10000742574 | $ 18.35 | 9 |
| 10001577918 | $ 23.72 | 4 |
| 10000825179 | $ 7.69 | 7 |
| 10000019009 | $ 94.41 | 47 |
| 10001225606 | $ 36.00 | 12 |
| 10000473429 | $ 5.76 | 6 |
+-------------+----------------+--------+
It should return:
+---------+-------------+
| Segment | Sales/Visit |
+---------+-------------+
| A | 6.82 |
| B | 3.846410256 |
| C | 4.94875 |
+---------+-------------+
This should do it:
SELECT
seg.Segment,
SUM( sales.Sales ) / SUM( sales.Visits ) AS [Sales/Visit]
FROM
cust_segment AS seg
LEFT OUTER JOIN cust_sales AS sales
ON seg.Customer = sales.Customer
AND seg.Customer IN (
SELECT TOP 10 PERCENT sa.Customer
FROM cust_sales AS sa
INNER JOIN cust_segment AS sg
ON sg.Segment = seg.Segment
AND sg.Customer = sa.Customer
ORDER BY sa.Sales DESC
)
GROUP BY
seg.Segment
;With cust_segment( Customer , Segment )
AS
(
SELECT 10000834678 , 'A' union all
SELECT 10000467169 , 'A' union all
SELECT 10000217202 , 'B' union all
SELECT 10001562687 , 'C' union all
SELECT 10000742574 , 'C' union all
SELECT 10001577918 , 'A' union all
SELECT 10000825179 , 'B' union all
SELECT 10000019009 , 'B' union all
SELECT 10001225606 , 'C' union all
SELECT 10000473429 , 'A'
)
,cust_sales(Customer,Sales,Visits)
AS
(
SELECT 10000834678 , 54.56 , 8 UNION ALL
SELECT 10000467169 , 27.61 , 7 UNION ALL
SELECT 10000217202 , 150.01 , 39 UNION ALL
SELECT 10001562687 , 39.59 , 8 UNION ALL
SELECT 10000742574 , 18.35 , 9 UNION ALL
SELECT 10001577918 , 23.72 , 4 UNION ALL
SELECT 10000825179 , 7.69 , 7 UNION ALL
SELECT 10000019009 , 94.41 , 47 UNION ALL
SELECT 10001225606 , 36.00 , 12 UNION ALL
SELECT 10000473429 , 5.76 , 6
)
SELECT Segment,seg AS [Sales/Visit] FROM
(
SELECT * ,ROW_NUMBER()OVER(PARTITION by seg ORDER BY Segment )Seq FROM
(
SELECT * ,MAX([Sales/Visit])OVER (PARTITION BY Segment ORDER BY Segment)seg FROM
(
SELECT C.Customer,c.Segment,SUM(Sales) /SUM(Visits) AS [Sales/Visit] FROM cust_sales s
INNER JOIN cust_segment c ON c.Customer=s.Customer
GROUP BY C.Customer,c.Segment
)dt
)dt2
)Final WHERE Final.Seq=1 ORDER BY 1
OutPut
Segment|Sales/Visit
------------------
A 6.820000
B 3.846410
C 4.948750

SQL Server query to see what changed from month to month

I am struggling with developing a query to compare changes in a single table from month to month, example data -
+-----------------------------------------------------------+
| TaxGroupDetails |
+-----------+--+----------+--+-----------+--+---------------+
| Tax Group | | Tax Type | | Geocode | | EffectiveDate |
+-----------+--+----------+--+-----------+--+---------------+
| 2001 | | 1D | | 440011111 | | 1120531 |
| 2001 | | X1 | | 440011111 | | 1120531 |
| 2001 | | D3 | | 440011111 | | 1120531 |
| 2001 | | DGH | | 440011111 | | 1120531 |
| 2001 | | 1D | | 440011111 | | 1130101 |
| 2001 | | X1 | | 440011111 | | 1130101 |
| 2001 | | D3 | | 440011111 | | 1130101 |
| 2001 | | 1D | | 440011111 | | 1140201 |
| 2001 | | X1 | | 440011111 | | 1140201 |
| 2001 | | D3 | | 440011111 | | 1140201 |
| 2001 | | Z9 | | 440011111 | | 1140201 |
+-----------+--+----------+--+-----------+--+---------------+
I want to see the changes in the table, what was added or removed from a taxgroup, between the top two effective dates.
The results I am trying to obtain based on the sample data would be Z9 (added) if I was running the query in February (1140201) of this year.
If I was running the query in January (1130101) of last year I would expect to see DGH (removed)
I would expect two seperate queries, one to show what was added and another to show what was removed.
I have tried multiple avenues to come up with these two queries but cant seem to obtain the correct results. Can anyone point me in the right direction ?
SELECT
Current.TaxGroup,
Current.TaxType,
Current.GeoCode,
'Added'
FROM
TaxGroupDetails AS Current
WHERE
Current.EffectiveDate = #CurrentPeriod AND
NOT EXISTS
(
SELECT *
FROM TaxGroupDetails As Previous
WHERE
Previous.EffectiveDate = #PreviousPeriod
Current.TaxGroup = Previous.TaxGroup and
Current.TaxType = Previous.TaxType and
Current.GeoCode = Previous.GeoCode
)
UNION ALL
SELECT
Current.TaxGroup,
Current.TaxType,
Current.GeoCode,
'Added'
FROM
TaxGroupDetails AS Previous
WHERE
Previous.EffectiveDate = #PreviousPeriod AND
NOT EXISTS
(
SELECT *
FROM TaxGroupDetails As Current
WHERE
Current.EffectiveDate = #CurrentPeriod
Current.TaxGroup = Previous.TaxGroup and
Current.TaxType = Previous.TaxType and
Current.GeoCode = Previous.GeoCode
)
As you say you need two queries, one to select each of the two groups of data you want to compare.
SELECT [Tax Group], [Tax Type], [Geocode], [EffectiveDate]
FROM TaxGroupDetails
WHERE EffectiveDate = 1120531
SELECT [Tax Group], [Tax Type], [Geocode], [EffectiveDate]
FROM TaxGroupDetails
WHERE EffectiveDate = 1140201
You then need to join these two together using some form of key, the combination of tax group and tax type seems sensible here.
SELECT *
FROM
(
SELECT [Tax Group], [Tax Type], [Geocode], [EffectiveDate]
FROM TaxGroupDetails
WHERE EffectiveDate = 1120531
) AS FirstGroup
FULL OUTER JOIN
(
SELECT [Tax Group], [Tax Type], [Geocode], [EffectiveDate]
FROM TaxGroupDetails
WHERE EffectiveDate = 1140201
) AS SecondGroup
ON FirstGroup.[Tax Group] = SecondGroup.[Tax Group]
AND FirstGroup.[Tax Type] = SecondGroup.[Tax Type]
The FULL OUTER JOIN here tells SQL to include the remaining row when the other doesn't exist.
Finally let's tidy up and order the columns and not use a *:
SELECT COALESCE(FirstGroup.[Tax Group], SecondGroup.[Tax Group]),
COALESCE(FirstGroup.[Tax Type], SecondGroup.[Tax Type]),
FirstGroup.Geocode, SecondGroup.Geocode,
FirstGroup.EffectiveDate, SecondGroup.EffectiveDate
FROM
.
.
.
COALESCE removes the NULLs from the first matched columns and as we are saying these muct be equal there is no point showing both copies.
The set-based solution: take the difference between the whole table and the whole table with all dates projected forward by one time interval. That will eliminate all rows except the ones with "new" codes.
SELECT
[TaxGroup],
[Tax Type],
[EffectiveDate]
FROM TaxGroupDetails t
EXCEPT
SELECT
[TaxGroup],
[Tax Type],
( SELECT MIN([EffectiveDate])
FROM TaxGroupDetails
WHERE [EffectiveDate] > t.[EffectiveDate]
AND [TaxGroup] = t.[TaxGroup]
)
FROM TaxGroupDetails t
To see what got deleted, project backwards instead. Change the subquery to:
SELECT MAX([EffectiveDate])
FROM TaxGroupDetails
WHERE [EffectiveDate] < t.[EffectiveDate]
AND [TaxGroup] = t.[TaxGroup]
If you have SQL2012:
WITH t AS (
SELECT *,
ROW_NUMBER() OVER(PARTITION BY [TaxGroup], [Tax Type] ORDER BY [EffectiveDate] ASC) rownum
FROM [TaxGroup]
)
SELECT *
FROM t
WHERE rownum = 1
AND [EffectiveDate] = #Date
To get the other query, change ASC to DESC
Try this / you could start from this [partial] solution:
DECLARE #MyTable TABLE (
ID INT IDENTITY PRIMARY KEY,
[Tax Group] SMALLINT NOT NULL,
[Tax Type] VARCHAR(3) NOT NULL,
[Geocode] INT NOT NULL,
[EffectiveDate] INT NOT NULL
);
INSERT #MyTable
SELECT 2001, '1D ', 440011111, 1120531
UNION ALL SELECT 2001, 'X1 ', 440011111, 1120531
UNION ALL SELECT 2001, 'D3 ', 440011111, 1120531
UNION ALL SELECT 2001, 'DGH', 440011111, 1120531
UNION ALL SELECT 2001, '1D ', 440011111, 1130101
UNION ALL SELECT 2001, 'X1 ', 440011111, 1130101
UNION ALL SELECT 2001, 'D3 ', 440011111, 1130101
UNION ALL SELECT 2001, '1D ', 440011111, 1140201
UNION ALL SELECT 2001, 'X1 ', 440011111, 1140201
UNION ALL SELECT 2001, 'D3 ', 440011111, 1140201
UNION ALL SELECT 2001, 'Z9 ', 440011111, 1140201;
DECLARE #Results TABLE (
ID INT NOT NULL,
Rnk INT NOT NULL,
EffectiveYear SMALLINT NOT NULL,
PRIMARY KEY (Rnk, EffectiveYear)
);
INSERT #Results
SELECT x.ID,
DENSE_RANK() OVER(ORDER BY x.[Tax Group], x.[Tax Type], x.[Geocode]) AS Rnk,
x.EffectiveDate / 10000 AS EffectiveYear
FROM #MyTable x;
SELECT
crt.*,
prev.*,
CASE
WHEN crt.ID IS NOT NULL AND prev.ID IS NOT NULL THEN '-' -- No change
WHEN crt.ID IS NULL AND prev.ID IS NOT NULL THEN 'D' -- Deleted
WHEN crt.ID IS NOT NULL AND prev.ID IS NULL THEN 'I' -- Inserted
END AS RowStatus
FROM #Results crt FULL OUTER JOIN #Results prev ON crt.Rnk = prev.Rnk
AND crt.EffectiveYear - 1 = prev.EffectiveYear
ORDER BY ISNULL(crt.EffectiveYear - 1, prev.EffectiveYear), crt.Rnk;
Sample output:
---- ---- ------------- ---- ---- -------------
| Current data | | Previous data |
---- ---- ------------- ---- ---- ------------- ---------
ID Rnk EffectiveYear ID Rnk EffectiveYear RowStatus
---- ---- ------------- ---- ---- ------------- ---------
1 1 112 NULL NULL NULL I -- Current vs. previous: current row hasn't a previous row
3 2 112 NULL NULL NULL I -- the same thing
4 3 112 NULL NULL NULL I -- the same thing
2 4 112 NULL NULL NULL I -- the same thing
NULL NULL NULL 4 3 112 D <-- Deleted: ID 4 = 'DGH'
5 1 113 1 1 112 - -- there is no change
7 2 113 3 2 112 -
6 4 113 2 4 112 -
8 1 114 5 1 113 -
10 2 114 7 2 113 -
9 4 114 6 4 113 -
11 5 114 NULL NULL NULL I <-- Inserted: ID 11 = 'Z9'
NULL NULL NULL 8 1 114 D
NULL NULL NULL 10 2 114 D
NULL NULL NULL 9 4 114 D
NULL NULL NULL 11 5 114 D
Note: I assume that there are no duplicated rows (x.[Tax Group], x.[Tax Type], x.[Geocode]) within a year.

Resources