Duplicate values to be removed on conditions - tsql

I have two tables IAM and IAM_audit as below. I am trying to get results for account numbers who changed country or tax country. That works fine. However, I want the results to show only unique account numbers even though they are changed both in tax country and country. I don't want the account number 120 in this example to be shown twice? :) Is there a way.
Initial table Creation:
create table #Iam_audit1
(
accnum int,
invnumber int,
audit_field varchar(10),
field_before varchar(10),
field_after varchar(10),
modified_date datetime
)
insert into #Iam_audit1 (accnum, invnumber, audit_field, field_before,field_after, modified_date)
values
(120, 131, 'country', 'US', 'CAN','2014-08-09'),
(120, 131, 'taxcountry', 'US','CAN', '2015-07-09'),
(121, 132, 'country', 'CAN','US', '2014-09-15'),
(121, 132, 'taxcountry', 'CAN', 'US','2015-09-14'),
(122, 133, 'Taxcountry','CAN','US','2014-05-27')
create table #Iam
(
Accnum int,
invnumber int,
country varchar(10) ,
Taxcountry varchar(10)
)
insert into #Iam (Accnum, invnumber, country, taxcountry)
values (120, 131, 'CAN', 'CAN'),
(120, 132, 'US', 'US'),
(122, 133, 'CAN', 'CAN')
Main QUERY:
Select distinct IAMA.accnum,
IAMA.invnumber,
IAM.taxcountry,
Iama.audit_field,
IAM.country
From #iam_audit1 IAMA
join #IAM iam on iam.Accnum = iama.Accnum
AND iam.invnumber = iama.invnumber
Where Audit_Field IN ('TaxCountry', 'Country')
AND (
(isnull(Field_Before,'CAN') <> 'CAN' AND isnull(Field_After,'CAN') = 'CAN')
OR (isnull(Field_Before,'CAN') = 'CAN' AND isnull(Field_After,'CAN') <> 'CAN')
)
The current Results
accnum invnumber taxcountry audit_field country
120 131 CAN country CAN
120 131 CAN taxcountry CAN
122 133 CAN Taxcountry CAN
Expected Results:
accnum invnumber taxcountry audit_field country
120 131 CAN country CAN
122 133 CAN Taxcountry CAN

As long as the audit_field is in the select clause you will have duplicates. After refactoring the original query you can see that it is the only field with the potential to cause duplicates if your #IAM table has unique account numbers i.e. accnum. This is because all of the other columns in the select clause can be from the #IAM table.
select distinct
IAM.accnum
, IAM.invnumber
, IAM.taxcountry
, IAMA.audit_field -- causing the 'duplicates' you describe
, IAM.country
from
#iam_audit1 IAMA
join
#IAM IAM
on
IAM.Accnum = IAMA.Accnum
and
IAM.invnumber = IAMA.invnumber
where
IAMA.Audit_Field IN ('TaxCountry', 'Country')
and (
(isnull(IAMA.Field_Before,'CAN') <> 'CAN' and isnull(IAMA.Field_After,'CAN') = 'CAN')
or
(isnull(IAMA.Field_Before,'CAN') = 'CAN' and isnull(IAMA.Field_After,'CAN') <> 'CAN')
)
Depending on your specific use requirements (and version of SQL Server) there are a few different workarounds you could try if you really don't want multiple records for a single account number.
First, you could just remove the audit_field from the select clause.
select distinct
IAM.accnum
, IAM.invnumber
, IAM.taxcountry
, IAM.country
from
#iam_audit1 IAMA
join
#IAM IAM
on
IAM.Accnum = IAMA.Accnum
and
IAM.invnumber = IAMA.invnumber
where
IAMA.Audit_Field IN ('TaxCountry', 'Country')
and (
(isnull(IAMA.Field_Before,'CAN') <> 'CAN' and isnull(IAMA.Field_After,'CAN') = 'CAN')
or
(isnull(IAMA.Field_Before,'CAN') = 'CAN' and isnull(IAMA.Field_After,'CAN') <> 'CAN')
)
which produces
accnum invnumber taxcountry country
120 131 CAN CAN
122 133 CAN CAN
Second, you could get the account number and the number of different audit_fields that changed.
select distinct
IAM.accnum
, IAM.invnumber
, IAM.taxcountry
, count(distinct IAMA.audit_field) as cnt_audits_with_changes
, IAM.country
from
#iam_audit1 IAMA
join
#IAM IAM
on
IAM.Accnum = IAMA.Accnum
and
IAM.invnumber = IAMA.invnumber
where
IAMA.Audit_Field IN ('TaxCountry', 'Country')
and (
(isnull(IAMA.Field_Before,'CAN') <> 'CAN' and isnull(IAMA.Field_After,'CAN') = 'CAN')
or
(isnull(IAMA.Field_Before,'CAN') = 'CAN' and isnull(IAMA.Field_After,'CAN') <> 'CAN')
)
group by
IAM.accnum
, IAM.invnumber
, IAM.taxcountry
, IAM.country
which produces
accnum invnumber taxcountry cnt_audits_with_changes country
120 131 CAN 2 CAN
122 133 CAN 1 CAN
Third, if you are using SQL Server 2017 or later you can use the string aggregate function to concatenate the audit_fields
select distinct
IAM.accnum
, IAM.invnumber
, IAM.taxcountry
, string_agg(IAMA.audit_field, ', ') as list_audits_with_changes
, IAM.country
from
#iam_audit1 IAMA
join
#IAM IAM
on
IAM.Accnum = IAMA.Accnum
and
IAM.invnumber = IAMA.invnumber
where
IAMA.Audit_Field IN ('TaxCountry', 'Country')
and (
(isnull(IAMA.Field_Before,'CAN') <> 'CAN' and isnull(IAMA.Field_After,'CAN') = 'CAN')
or
(isnull(IAMA.Field_Before,'CAN') = 'CAN' and isnull(IAMA.Field_After,'CAN') <> 'CAN')
)
group by
IAM.accnum
, IAM.invnumber
, IAM.taxcountry
, IAM.country
which produces
accnum invnumber taxcountry list_audits_with_changes country
120 131 CAN country, taxcountry CAN
122 133 CAN taxcountry CAN

Related

SQL Server split overlapping date ranges

I need to split date ranges that overlap. I have a primary table (I've called it Employment for this example), and I need to return all Begin-End date ranges for a person from this table. I also have multiple sub tables (represented by Car and Food), and I want to return the value that was active in the sub tables during the times given in the main tables. This will involve splitting the main table date ranges when a sub table item changes.
I don't want to return sub table information for dates not in the main tables.
DECLARE #Employment TABLE
( Person_ID INT, Employment VARCHAR(50), Begin_Date DATE, End_Date DATE )
DECLARE #Car TABLE
( Person_ID INT, Car VARCHAR(50), Begin_Date DATE, End_Date DATE )
DECLARE #Food TABLE
( Person_ID INT, Food VARCHAR(50), Begin_Date DATE, End_Date DATE )
INSERT INTO #Employment ( [Person_ID], [Employment], [Begin_Date], [End_Date] )
VALUES ( 123 , 'ACME' , '1986-01-01' , '1990-12-31' )
, ( 123 , 'Office Corp' , '1995-05-15' , '1998-10-03' )
, ( 123 , 'Job 3' , '1998-10-04' , '2999-12-31' )
INSERT INTO #Car ( [Person_ID] , [Car] , [Begin_Date] , [End_Date] )
VALUES ( 123, 'Red Car', '1986-05-01', '1997-06-23' )
, ( 123, 'Blue Car', '1997-07-03', '2999-12-31' )
INSERT INTO #Food ( [Person_ID], [Food], [Begin_Date], [End_Date] )
VALUES ( 123, 'Eggs', '1997-01-01', '1997-03-09' )
, ( 123, 'Donuts', '2001-02-23', '2001-02-25' )
For the above data, the results should be:
Person_ID Employment Food Car Begin_Date End_Date
123 ACME 1986-01-01 1986-04-30
123 ACME Red Car 1986-05-01 1990-12-31
123 Office Corp Red Car 1995-05-15 1996-12-31
123 Office Corp Eggs Red Car 1997-01-01 1997-03-09
123 Office Corp Red Car 1997-03-10 1997-06-23
123 Office Corp 1997-06-24 1997-07-02
123 Office Corp Blue Car 1997-07-03 1998-10-03
123 Job 3 Blue Car 1998-10-04 2001-02-22
123 Job 3 Donuts Blue Car 2001-02-23 2001-02-25
123 Job 3 Blue Car 2001-02-26 2999-12-31
The first row is his time working for ACME, where he didn't have a car or a weird food obsession. In the second row, he purchased a car, and still worked at ACME. In the third row, he changed jobs to Office Corp, but still has the Red Car. Note how we're not returning data during his unemployment gap, even though he had the Red Car. We only want to know what was in the Car and Food tables during the times there are values in the Employment table.
I found a solution for SQL Server 2012 that uses the LEAD/LAG functions to accomplish this, but I'm stuck with 2008 R2.
To change the 2012 solution from that blog to work with 2008, you need to replace the LEAD in the following
with
ValidDates as …
,
ValidDateRanges1 as
(
select EmployeeNo, Date as ValidFrom, lead(Date,1) over (partition by EmployeeNo order by Date) ValidTo
from ValidDates
)
There are a number of ways to do this, but one example is a self join to the same table + 1 row (which is effectively what a lead does). One way to do this is to put a rownumber on the previous table (so it is easy to find the next row) by adding another intermediate CTE (eg ValidDatesWithRowno). Then do a left outer join to that table where EmployeeNo is the same and rowno = rowno + 1, and use that value to replace the lead. If you wanted a lead 2, you would join to rowno + 2, etc. So the 2008 version would look something like
with
ValidDates as …
,
ValidDatesWithRowno as --This is the ValidDates + a RowNo for easy self joining below
(
select EmployeeNo, Date, ROW_NUMBER() OVER (ORDER BY EmployeeNo, Date) as RowNo from ValidDates
)
,
ValidDateRanges1 as
(
select VD.EmployeeNo, VD.Date as ValidFrom, VDLead1.Date as ValidTo
from ValidDatesWithRowno VD
left outer join ValidDatesWithRowno VDLead1 on VDLead1.EmployeeNo = VD.EmployeeNo
and VDLead1.RowNo = VD.RowNo + 1
)
The rest of the solution described looks like it will work like you want on 2008.
Here is the answer I came up with. It works, but it's not very pretty.
It goes it two waves, first splitting any overlapping Employment/Car dates, then running the same SQL a second time add the Food dates and split any overlaps again.
DECLARE #Employment TABLE
( Person_ID INT, Employment VARCHAR(50), Begin_Date DATE, End_Date DATE )
DECLARE #Car TABLE
( Person_ID INT, Car VARCHAR(50), Begin_Date DATE, End_Date DATE )
DECLARE #Food TABLE
( Person_ID INT, Food VARCHAR(50), Begin_Date DATE, End_Date DATE )
INSERT INTO #Employment ( [Person_ID], [Employment], [Begin_Date], [End_Date] )
VALUES ( 123 , 'ACME' , '1986-01-01' , '1990-12-31' )
, ( 123 , 'Office Corp' , '1995-05-15' , '1998-10-03' )
, ( 123 , 'Job 3' , '1998-10-04' , '2999-12-31' )
INSERT INTO #Car ( [Person_ID] , [Car] , [Begin_Date] , [End_Date] )
VALUES ( 123, 'Red Car', '1986-05-01', '1997-06-23' )
, ( 123, 'Blue Car', '1997-07-03', '2999-12-31' )
INSERT INTO #Food ( [Person_ID], [Food], [Begin_Date], [End_Date] )
VALUES ( 123, 'Eggs', '1997-01-01', '1997-03-09' )
, ( 123, 'Donuts', '2001-02-23', '2001-02-25' )
DECLARE #Person_ID INT = 123;
--A table to hold date ranges that need to be merged together
DECLARE #DatesToMerge TABLE
(
ID INT,
Person_ID INT,
Date_Type VARCHAR(10),
Begin_Date DATETIME,
End_Date DATETIME
)
INSERT INTO #DatesToMerge
SELECT ROW_NUMBER() OVER(ORDER BY [Car])
, Person_ID
, 'Car'
, Begin_Date
, End_Date
FROM #Car
WHERE Person_ID = #Person_ID
INSERT INTO #DatesToMerge
SELECT ROW_NUMBER() OVER(ORDER BY [Employment])
, Person_ID
, 'Employment'
, Begin_Date
, End_Date
FROM #Employment
WHERE Person_ID = #Person_ID;
--A table to hold the merged #Employment and Car records
DECLARE #EmploymentAndCar TABLE
(
RowNumber INT,
Person_ID INT,
Begin_Date DATETIME,
End_Date DATETIME
)
;
WITH CarCTE AS
(--This CTE grabs just the Car rows so we can compare and split dates from them
SELECT ID,
Person_ID,
Date_Type,
Begin_Date,
End_Date
FROM #DatesToMerge
WHERE Date_Type = 'Car'
),
NewRowsCTE AS
( --This CTE creates just new rows starting after the Car dates for each #Employment date range
SELECT a.ID,
a.Person_ID,
a.Date_Type,
DATEADD(DAY,1,b.End_Date) AS Begin_Date,
a.End_Date
FROM #DatesToMerge a
INNER JOIN CarCTE b
ON a.Begin_Date <= b.Begin_Date
AND a.End_Date > b.Begin_Date
AND a.End_Date > b.End_Date -- This is needed because if both the Car and #Employment end on the same date, there is split row after
),
UnionCTE AS
( -- This CTE merges the new rows with the existing ones
SELECT ID,
Person_ID,
Date_Type,
Begin_Date,
End_Date
FROM #DatesToMerge
UNION ALL
SELECT ID,
Person_ID,
Date_Type,
Begin_Date,
End_Date
FROM NewRowsCTE
),
FixEndDateCTE AS
(
SELECT CONVERT (CHAR,c.ID)+CONVERT (CHAR,c.Begin_Date) AS FixID,
MIN(d.Begin_Date) AS Begin_Date
FROM UnionCTE c
LEFT OUTER JOIN CarCTE d
ON c.Begin_Date < d.Begin_Date
AND c.End_Date >= d.Begin_Date
WHERE c.Date_Type <> 'Car'
GROUP BY CONVERT (CHAR,c.ID)+CONVERT (CHAR,c.Begin_Date)
),
Finalize AS
(
SELECT ROW_NUMBER() OVER (ORDER BY e.Begin_Date) AS RowNumber,
e.Person_ID,
e.Begin_Date,
CASE WHEN f.Begin_Date IS NULL THEN e.End_Date
ELSE DATEADD (DAY,-1,f.Begin_Date)
END AS EndDate
FROM UnionCTE e
LEFT OUTER JOIN FixEndDateCTE f
ON (CONVERT (CHAR,e.ID)+CONVERT (CHAR,e.Begin_Date)) = f.FixID
)
INSERT INTO #EmploymentAndCar ( RowNumber, Person_ID, Begin_Date, End_Date )
SELECT F.RowNumber
, F.Person_ID
, F.Begin_Date
, F.EndDate
FROM Finalize F
INNER JOIN #Employment Employment
ON F.Begin_Date BETWEEN Employment.Begin_Date AND Employment.End_Date AND Employment.Person_ID = #Person_ID
ORDER BY F.Begin_Date
--------------------------------------------------------------------------------------------------
--Now that the Employment and Car dates have been merged, empty the DatesToMerge table
DELETE FROM #DatesToMerge;
--Reload the DatesToMerge table with the newly-merged Employment and Car records,
--and the Food records that still need to be merged
INSERT INTO #DatesToMerge
SELECT RowNumber
, Person_ID
, 'PtBCar'
, Begin_Date
, End_Date
FROM #EmploymentAndCar
WHERE Person_ID = #Person_ID
INSERT INTO #DatesToMerge
SELECT ROW_NUMBER() OVER(ORDER BY [Food])
, Person_ID
, 'Food'
, Begin_Date
, End_Date
FROM #Food
WHERE Person_ID = #Person_ID
;
WITH CarCTE AS
(--This CTE grabs just the Food rows so we can compare and split dates from them
SELECT ID,
Person_ID,
Date_Type,
Begin_Date,
End_Date
FROM #DatesToMerge
WHERE Date_Type = 'Food'
),
NewRowsCTE AS
( --This CTE creates just new rows starting after the Food dates for each Employment date range
SELECT a.ID,
a.Person_ID,
a.Date_Type,
DATEADD(DAY,1,b.End_Date) AS Begin_Date,
a.End_Date
FROM #DatesToMerge a
INNER JOIN CarCTE b
ON a.Begin_Date <= b.Begin_Date
AND a.End_Date > b.Begin_Date
AND a.End_Date > b.End_Date -- This is needed because if both the Food and Car/Employment end on the same date, there is split row after
),
UnionCTE AS
( -- This CTE merges the new rows with the existing ones
SELECT ID,
Person_ID,
Date_Type,
Begin_Date,
End_Date
FROM #DatesToMerge
UNION ALL
SELECT ID,
Person_ID,
Date_Type,
Begin_Date,
End_Date
FROM NewRowsCTE
),
FixEndDateCTE AS
(
SELECT CONVERT (CHAR,c.ID)+CONVERT (CHAR,c.Begin_Date) AS FixID,
MIN(d.Begin_Date) AS Begin_Date
FROM UnionCTE c
LEFT OUTER JOIN CarCTE d
ON c.Begin_Date < d.Begin_Date
AND c.End_Date >= d.Begin_Date
WHERE c.Date_Type <> 'Food'
GROUP BY CONVERT (CHAR,c.ID)+CONVERT (CHAR,c.Begin_Date)
),
Finalize AS
(
SELECT ROW_NUMBER() OVER (ORDER BY e.Begin_Date) AS RowNumber,
e.Person_ID,
e.Begin_Date,
CASE WHEN f.Begin_Date IS NULL THEN e.End_Date
ELSE DATEADD (DAY,-1,f.Begin_Date)
END AS EndDate
FROM UnionCTE e
LEFT OUTER JOIN FixEndDateCTE f
ON (CONVERT (CHAR,e.ID)+CONVERT (CHAR,e.Begin_Date)) = f.FixID
)
SELECT DISTINCT
F.Person_ID
, Employment
, Car
, Food
, F.Begin_Date
, F.EndDate
FROM Finalize F
INNER JOIN #Employment Employment
ON F.Begin_Date BETWEEN Employment.Begin_Date AND Employment.End_Date AND Employment.Person_ID = #Person_ID
LEFT JOIN #Car Car
ON Car.[Begin_Date] <= F.Begin_Date
AND Car.[End_Date] >= F.[EndDate]
AND Car.Person_ID = #Person_ID
LEFT JOIN #Food Food
ON Food.[Begin_Date] <= F.[Begin_Date]
AND Food.[End_Date] >= F.[EndDate]
AND Food.Person_ID = #Person_ID
ORDER BY F.Begin_Date
If anyone has a more elegant solution, I will be happy to accept their answer.

Create insert query from select query by incrementing id

I am trying to copy data from a table and insert into it with some different fields. Here while inserting it results in a problem where I have to specify id(primary key) which is supposed to be auto incrementing but for some reason not set to serial when the tables were created. I wish to add an incrementing id in the data to be inserted based on the already existing data.
Any help will be appreciated.
WITH my_admin_id AS
(
SELECT id
FROM dashboard_admin
WHERE email = 'someone#somedomain.com'
),
get_admin AS
(
SELECT id
FROM dashboard_admin
WHERE user_id IN (SELECT id FROM auth_user WHERE username = 'demo')
),
get_admin_projects AS
(
SELECT *
FROM dashboard_project
WHERE admin_id IN (SELECT * FROM get_admin LIMIT 1)
AND is_deleted = 'false'
) INSERT INTO dashboard_project
(
SELECT id,
name,
created_date,
(SELECT * FROM my_admin_id LIMIT 1)AS admin_id,
category_id,
subcategory_id,
is_deleted FROM get_admin_projects
)
Update :
Earlier the execution of select query meant to be inserted, fetched the following result.
67 1stAd 2017-04-21 15:25:16.430889 100 18 47 false
71 2stAd 2017-05-12 10:18:55.383967 100 34 90 false
Here I have just added a query to get the max id. I just need to increment it for every record in my final select query.
WITH my_admin_id AS
(
SELECT id
FROM dashboard_admin
WHERE email = 'someone#somedomain.com'
),
get_admin AS
(
SELECT id
FROM dashboard_admin
WHERE user_id IN (SELECT id FROM auth_user WHERE username = 'demo')
),
get_max_project AS
(
SELECT MAX(id) FROM dashboard_project
),
get_admin_projects AS
(
SELECT id,
name,
created_date,
admin_id,
category_id,
subcategory_id,
is_deleted
FROM dashboard_project
WHERE admin_id IN (SELECT * FROM get_admin LIMIT 1)
AND is_deleted = 'false'
) (SELECT (SELECT * FROM get_max_project) id,
name,
created_date,
(SELECT * FROM my_admin_id LIMIT 1) AS admin_id,
category_id,
subcategory_id,
is_deleted
FROM get_admin_projects)
This is the resultant value on executing the select query.
101 1stAd 2017-04-21 15:25:16.430889 100 18 47 false
101 2ndAd 2017-05-12 10:18:55.383967 100 34 90 false
Finally got it working like this used the row_number() window function which I added to max id , to increment values above max id.
WITH my_admin_id AS
(
SELECT id
FROM dashboard_admin
WHERE email = 'someone#somedomain.com'
),
get_admin AS
(
SELECT id
FROM dashboard_admin
WHERE user_id IN (SELECT id FROM auth_user WHERE username = 'demo')
),
get_max_project AS
(
SELECT MAX(id) FROM dashboard_project
),
get_admin_projects AS
(
SELECT id,
name,
created_date,
admin_id,
category_id,
subcategory_id,
is_deleted
FROM dashboard_project
WHERE admin_id IN (SELECT * FROM get_admin LIMIT 1)
AND is_deleted = 'false'
)
insert into dashboard_project (SELECT ((SELECT * FROM get_max_project) + row_number() over ()) as id,
name,
created_date,
(SELECT * FROM my_admin_id LIMIT 1) AS admin_id,
category_id,
subcategory_id,
is_deleted
FROM get_admin_projects)

User-defined parameters in stored procedure are not calculating data

I am creating a stored procedure for an SSRS report and normally when I have a multi-valued parameter, I use a split function to separate and calculate the results.
But my issue with this stored procedure is that I have three additional values I need to add to the data set; which I have done here:
CREATE PROCEDURE dbo.rpt_DataSet_AgentClassType
AS
BEGIN
SET NOCOUNT ON;
SELECT
x.ID, x.ClassType
FROM
(SELECT 997 AS [ID], 'Desktops' AS [ClassType]
UNION ALL
SELECT 998 AS [ID], 'Mobile Devices' AS [ClassType]
UNION ALL
SELECT 999 AS [ID], 'Mobile Apps' AS [ClassType]
UNION ALL
SELECT ACT.[id] AS [ID], ACT.[description] AS [ClassType]
FROM dbo.AgentClassTypes AS [ACT]
WHERE ACT.id NOT IN (14, 15, 16)
AND ACT.active = 1
) AS x
ORDER BY
x.ID;
END;
The resulting data set looks like:
ID ClassType
--------------------------------
1 TWRA Central License Sales
3 General Agent
4 Service Desk
5 Internet
....
18 State Parks
19 TWRA Special
997 Desktops
998 Mobile Devices
999 Mobile Apps
What I am trying to accomplish here is that if the user selects 997 - Desktops, 998 - Mobile Devices, or 999 - Mobiles Apps; the stored procedure should not use the split function since the value does not exist in the table, but search a specific field in a table for the data set.
Here is my stored procedure:
ALTER PROCEDURE dbo.rpt_HarvestByAgentType
#StartDate DATE,
#EndDate DATE,
#AgentClassType VARCHAR(MAX),
#SpeciesID VARCHAR(MAX)
AS
BEGIN
SET NOCOUNT ON;
IF OBJECT_ID('tempdb..#HarvestByAgentTypeResults') IS NOT NULL
DROP TABLE #HarvestByAgentTypeResults;
CREATE TABLE #HarvestByAgentTypeResults
(
TotalHarvest INT,
HarvestDate DATE,
AgentClassType VARCHAR(100),
SpeciesID INT,
SpeciesType VARCHAR(50)
);
-- Desktops
IF #AgentClassType IN ('997')
BEGIN
INSERT INTO #HarvestByAgentTypeResults (TotalHarvest, HarvestDate, AgentClassType, SpeciesID, SpeciesType)
SELECT
COUNT(x.HarvestRecordID) AS [TotalHarvest],
x.HarvestDate AS [HarvestDate],
'Desktop' AS [AgentClassType],
x.SpeciesID AS [SpeciesID],
x.SpeciesType AS [SpeciesType]
FROM
(SELECT
CHR.HarvestRecordID,
CAST(CHR.HarvestDate AS DATE) AS [HarvestDate],
CHR.SpeciesID AS [SpeciesID],
HST.SpeciesType AS SpeciesType
FROM
dbo.CustomerHarvestReports AS [CHR]
INNER JOIN
dbo.Agents AS [A] ON A.agentID = CHR.agentID
INNER JOIN
dbo.AgentClassTypes AS [ACT] ON ACT.id = A.agentClassTypeID
INNER JOIN
dbo.HarvestSpeciesTypes AS [HST] ON HST.SpeciesID = CHR.SpeciesID AND HST.InActive = 0
WHERE
CHR.HarvestDate >= #StartDate
AND CHR.HarvestDate <= #EndDate
AND HST.SpeciesID In (SELECT Item
FROM dbo.fnSplit(#SpeciesID,','))
AND CHR.isInvalidated = 0
AND ((CHR.httpUserAgentString LIKE '%Win%' AND CHR.httpUserAgentString NOT LIKE '%Windows Phone%' ) -- Windows Machines excluding Windows phones
OR (CHR.httpUserAgentString LIKE '%Linux%' AND CHR.httpUserAgentString NOT LIKE '%Android') -- Linux machines excluding Android phones
OR CHR.httpUserAgentString LIKE '%Macintosh%' -- Mac books
OR CHR.httpUserAgentString LIKE '%CrOS%') -- Chrome Books
) x
GROUP BY
x.HarvestDate, x.SpeciesID, x.SpeciesType
END;
-- Mobile Devices
ELSE IF #AgentClassType IN ('998')
BEGIN
INSERT INTO #HarvestByAgentTypeResults(TotalHarvest, HarvestDate, AgentClassType, SpeciesID, SpeciesType)
SELECT
COUNT(x.HarvestRecordID) AS [TotalHarvest],
x.HarvestDate AS [HarvestDate],
'MobileDevice' AS [AgentClassType] ,
x.SpeciesID AS [SpeciesID] ,
x.SpeciesType AS [SpeciesType]
FROM ( SELECT CHR.HarvestRecordID ,
CAST(CHR.HarvestDate AS DATE) AS [HarvestDate] ,
ACT.[description] agentClass ,
CHR.SpeciesID AS [SpeciesID] ,
HST.SpeciesType AS SpeciesType
FROM dbo.CustomerHarvestReports AS [CHR]
LEFT OUTER JOIN dbo.Agents AS [A] ON A.agentID = CHR.agentID
LEFT OUTER JOIN dbo.AgentClassTypes AS [ACT] ON ACT.id = A.agentClassTypeID
LEFT OUTER JOIN dbo.HarvestSpeciesTypes AS [HST] ON HST.SpeciesID = CHR.SpeciesID AND HST.InActive = 0
WHERE CHR.HarvestDate >= #StartDate
AND CHR.HarvestDate <= #EndDate
AND HST.SpeciesID In ( SELECT Item FROM dbo.fnSplit(#SpeciesID,','))
AND CHR.isInvalidated = 0
AND (( httpUserAgentString LIKE '%iPhone%' AND httpUserAgentString LIKE '%Version%' ) -- iPhones
OR ( httpUserAgentString LIKE '%Android%' AND httpUserAgentString NOT LIKE '%Version%' ) -- Androids
OR ( httpUserAgentString LIKE '%iPad%' AND httpUserAgentString LIKE '%Version%' ) -- iPads
OR ( httpUserAgentString LIKE '%BB10%' ) -- Blackberries
OR ( httpUserAgentString LIKE '%Windows Phone%' )) -- Windows Phones
) x
GROUP BY x.HarvestDate ,
x.SpeciesID ,
x.SpeciesType
END;
-- Mobile Apps
ELSE IF #AgentClassType IN ('999')
BEGIN
INSERT INTO #HarvestByAgentTypeResults
( TotalHarvest ,
HarvestDate ,
AgentClassType ,
SpeciesID ,
SpeciesType
)
SELECT COUNT(x.HarvestRecordID) AS [TotalHarvest] ,
x.HarvestDate AS [HarvestDate],
'MobileApp' AS [AgentClassType] ,
x.SpeciesID AS [SpeciesID] ,
x.SpeciesType AS [SpeciesType]
FROM ( SELECT CHR.HarvestRecordID ,
CAST(CHR.HarvestDate AS DATE) AS [HarvestDate] ,
ACT.[description] agentClass ,
CHR.SpeciesID AS [SpeciesID] ,
HST.SpeciesType AS SpeciesType
FROM dbo.CustomerHarvestReports AS [CHR]
LEFT OUTER JOIN dbo.Agents AS [A] ON A.agentID = CHR.agentID
LEFT OUTER JOIN dbo.AgentClassTypes AS [ACT] ON ACT.id = A.agentClassTypeID
LEFT OUTER JOIN dbo.HarvestSpeciesTypes AS [HST] ON HST.SpeciesID = CHR.SpeciesID AND HST.InActive = 0
WHERE CHR.HarvestDate >= #StartDate
AND CHR.HarvestDate <= #EndDate
AND HST.SpeciesID In ( SELECT Item FROM dbo.fnSplit(#SpeciesID,','))
AND CHR.isInvalidated = 0
AND (( httpUserAgentString LIKE '%iPhone%' AND httpUserAgentString NOT LIKE '%Version%' AND httpUserAgentString NOT LIKE '%Windows Phone%' ) -- iPhones
OR ( httpUserAgentString LIKE '%Android%' AND httpUserAgentString LIKE '%Version%' )) -- Androids
) x
GROUP BY x.HarvestDate ,
x.SpeciesID ,
x.SpeciesType
END;
ELSE IF (#AgentClassType NOT IN ('997','998','999'))
BEGIN
INSERT INTO #HarvestByAgentTypeResults
( TotalHarvest ,
HarvestDate ,
AgentClassType ,
SpeciesID ,
SpeciesType
)
SELECT COUNT(x.HarvestRecordID) AS [TotalHarvest] ,
x.HarvestDate AS [HarvestDate] ,
x.AgentTypeClass AS [AgentTypeClass] ,
x.SpeciesID AS [SpeciesID] ,
x.SpeciesType AS [SpeciesType]
FROM ( SELECT CHR.HarvestRecordID ,
CAST(CHR.HarvestDate AS DATE) AS [HarvestDate] ,
ACT.[description] AS [AgentTypeClass] ,
CHR.SpeciesID AS [SpeciesID] ,
HST.SpeciesType AS [SpeciesType]
FROM dbo.CustomerHarvestReports AS [CHR]
INNER JOIN dbo.Agents AS [A] ON A.agentID = CHR.agentID
INNER JOIN dbo.AgentClassTypes AS [ACT] ON ACT.id = A.agentClassTypeID
INNER JOIN dbo.HarvestSpeciesTypes AS [HST] ON HST.SpeciesID = CHR.SpeciesID AND HST.InActive = 0
WHERE CHR.HarvestDate >= #StartDate
AND CHR.HarvestDate <= #EndDate
AND HST.SpeciesID IN ( SELECT Item FROM dbo.fnSplit(#SpeciesID, ','))
AND A.agentClassTypeID IN ( SELECT Item FROM dbo.fnSplit(#AgentClassType ,','))
AND CHR.isInvalidated = 0
) AS x
GROUP BY x.HarvestDate ,
x.AgentTypeClass ,
x.SpeciesID ,
x.SpeciesType
ORDER BY x.HarvestDate ,
x.AgentTypeClass,
x.SpeciesType;
END
SELECT TotalHarvest ,
HarvestDate ,
AgentClassType ,
SpeciesID ,
SpeciesType FROM #HarvestByAgentTypeResults
ORDER BY HarvestDate, SpeciesType, AgentClassType;
DROP TABLE #HarvestByAgentTypeResults;
END;
When I execute the stored procedure from Management Studio, if I only input 997 or 998 or 999 in the #AgentClasstype variable, I get results. But if I include values such as 1, 3, 5, etc... - then the data set I would expect from 997, 998, or 999 are not returned.
Hopefully I have explained this clearly - it has been difficult to script correctly and seemingly - just as hard to write up for help.

SQL Server CTE to recursively fetch all the linked rows for the given row

I have a table with the following details:
-- SQL
CREATE TABLE [dbo].[LedgerTbl](
[LedgerID] [int] NOT NULL,
[Name] [varchar](50) NOT NULL,
[ParentID] [int] NULL,
[Cr Amt] [decimal](8, 2) NOT NULL,
[Dr Amt] [decimal](8, 2) NOT NULL,
CONSTRAINT [PK_LedgerTbl] PRIMARY KEY CLUSTERED
(
[LedgerID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
-- Data
INSERT [dbo].[LedgerTbl] ([LedgerID], [Name], [ParentID], [Cr Amt], [Dr Amt]) VALUES (17, N'L1 Ledger', 20, CAST(25252.00 AS Decimal(8, 2)), CAST(0.00 AS Decimal(8, 2)))
INSERT [dbo].[LedgerTbl] ([LedgerID], [Name], [ParentID], [Cr Amt], [Dr Amt]) VALUES (18, N'L2 Ledger', 20, CAST(9000.00 AS Decimal(8, 2)), CAST(0.00 AS Decimal(8, 2)))
INSERT [dbo].[LedgerTbl] ([LedgerID], [Name], [ParentID], [Cr Amt], [Dr Amt]) VALUES (20, N'Master Ledger', NULL, CAST(0.00 AS Decimal(8, 2)), CAST(6900.00 AS Decimal(8, 2)))
INSERT [dbo].[LedgerTbl] ([LedgerID], [Name], [ParentID], [Cr Amt], [Dr Amt]) VALUES (45, N'L1.1 Ledger', 17, CAST(361.00 AS Decimal(8, 2)), CAST(0.00 AS Decimal(8, 2)))
INSERT [dbo].[LedgerTbl] ([LedgerID], [Name], [ParentID], [Cr Amt], [Dr Amt]) VALUES (46, N'L1.1.1 Ledger', 45, CAST(6541.00 AS Decimal(8, 2)), CAST(0.00 AS Decimal(8, 2)))
INSERT [dbo].[LedgerTbl] ([LedgerID], [Name], [ParentID], [Cr Amt], [Dr Amt]) VALUES (47, N'L1.1.2 Ledger', 45, CAST(321.00 AS Decimal(8, 2)), CAST(0.00 AS Decimal(8, 2)))
GO
ALTER TABLE [dbo].[LedgerTbl] WITH CHECK ADD CONSTRAINT [FK_LedgerTbl_LedgerTbl] FOREIGN KEY([ParentID])
REFERENCES [dbo].[LedgerTbl] ([LedgerID])
GO
ALTER TABLE [dbo].[LedgerTbl] CHECK CONSTRAINT [FK_LedgerTbl_LedgerTbl]
GO
The sample data is like this:
LedgerID Name ParentID Cr Amt Dr Amt
20 Master Ledger NULL 0.00 6900.00
17 L1 Ledger 20 25252.00 0.00
18 L2 Ledger 20 9000.00 0.00
45 L1.1 Ledger 17 361.00 0.00
46 L1.1.1 Ledger 45 6541.00 0.00
47 L1.1.2 Ledger 45 321.00 0.00
In the above table for the LedgerID 20, I need all the linked ledgers (in all levels), which are directly or indirectly linked for this ledger. In the above table all ledgers are directly linked to 20.
If I query for balances of ledger 20 it should show like this (adding all the Cr Amt and Dr Amount of all linked ledgers from all levels):
Cr Bal Dr Bal
41475.00 6900.00
Since all the ledgers are directly or indirectly linked to 20, all the Cr Amt and Dr Amt are being summed up.
The results should be like this after adding sum of Cr Amt and Dr Amt of all linked/not linked LedgerID's:
LedgerID Tot. Cr Amt Tot Dr Amt
20 41475.00 6900.00
18 9000.00 0.00
17 32475.00 0.00
45 7223.00 0.00
46 6541.00 0.00
47 321.00 0.00
Please notice that ledger 18 does not have any child ledgers and hence no need to add any balances.
Please help to achieve this using CTE or any other method. Thanks in advance.
This is what I've tried:
;WITH RecursiveLedger(LedgerID, [Name],[Cr Amt], [Dr Amt], LevelNum, LevelIndex, ParentID)
AS (
SELECT lg.LedgerID,
lg.[Name],
lg.[Cr Amt],
lg.[Dr Amt],
1 AS LevelNum,
CAST(lg.LedgerID AS VARCHAR) AS LevelIndex,
lg.ParentID
FROM [LedgerTbl] lg
WHERE lg.ParentID IS NULL
UNION ALL
SELECT l.LedgerID,
l.[Name],
l.[Cr Amt],
l.[Dr Amt],
r.LevelNum + 1 AS LevelNum,
CAST(r.LevelIndex + '.' + CAST(ROW_NUMBER() OVER(ORDER BY l.ParentID) AS VARCHAR) AS VARCHAR) AS LevelIndex,
l.ParentID
FROM [LedgerTbl] l,
RecursiveLedger r
WHERE r.LedgerID = l.ParentID
) SELECT * FROM RecursiveLedger
with anc as
(
select ledgerid, parentid
from [LedgerTbl] where parentid is not null
union all
select c.ledgerid, isnull(anc.parentid, anc.parentid)
from anc
inner join [LedgerTbl] c on anc.ledgerid = c.parentid
), anc2 as
(
select * from anc
union all
select ledgerid, ledgerid
from ledgertbl
)
select a.parentid,
sum(l.[Cr Amt]),
sum(l.[dr Amt])
from anc2 a
inner join ledgertbl l on a.ledgerid = l.ledgerid
group by a.parentid
order by a.parentid;

concatenating single column in TSQL

I am using SSMS 2008 and trying to concatenate one of the rows together based on a different field's grouping. I have two columns, people_id and address_desc. They look like this:
address_desc people_id
---------- ------------
Murfreesboro, TN 37130 F15D1135-9947-4F66-B778-00E43EC44B9E
11 Mohawk Rd., Burlington, MA 01803 C561918F-C2E9-4507-BD7C-00FB688D2D6E
Unknown, UN 00000 C561918F-C2E9-4507-BD7C-00FB688D2D6E Jacksonville, NC 28546 FC7C78CD-8AEA-4C8E-B93D-010BF8E4176D
Memphis, TN 38133 8ED8C601-5D35-4EB7-9217-012905D6E9F1
44 Maverick St., Fitchburg, MA 8ED8C601-5D35-4EB7-9217-012905D6E9F1
Now I want to concatenate the address_desc field / people_id. So the first one here should just display "Murfreesboro, TN 37130" for address_desc. But second person should have just one line instead of two which says "11 Mohawk Rd., Burlington, MA 01803;Unknown, UN 00000" for address_desc.
How do I do this? I tried using CTE, but this was giving me ambiguity error:
WITH CTE ( people_id, address_list, address_desc, length )
AS ( SELECT people_id, CAST( '' AS VARCHAR(8000) ), CAST( '' AS VARCHAR(8000) ), 0
FROM dbo.address_view
GROUP BY people_id
UNION ALL
SELECT p.people_id, CAST( address_list +
CASE WHEN length = 0 THEN '' ELSE ', ' END + c.address_desc AS VARCHAR(8000) ),
CAST( c.address_desc AS VARCHAR(8000)), length + 1
FROM CTE c
INNER JOIN dbo.address_view p
ON c.people_id = p.people_id
WHERE p.address_desc > c.address_desc )
SELECT people_id, address_list
FROM ( SELECT people_id, address_list,
RANK() OVER ( PARTITION BY people_id ORDER BY length DESC )
FROM CTE ) D ( people_id, address_list, rank )
WHERE rank = 1 ;
Here was my initial SQL query:
SELECT a.address_desc, a.people_id
FROM dbo.address_view a
INNER JOIN (SELECT people_id
FROM dbo.address_view
GROUP BY people_id
HAVING COUNT(*) > 1) t
ON a.people_id = t.people_id
order by a.people_id
You can use FOR XML PATH('') like this:
DECLARE #TestData TABLE
(
address_desc NVARCHAR(100) NOT NULL
,people_id UNIQUEIDENTIFIER NOT NULL
);
INSERT #TestData
SELECT 'Murfreesboro, TN 37130', 'F15D1135-9947-4F66-B778-00E43EC44B9E'
UNION ALL
SELECT '11 Mohawk Rd., Burlington, MA 01803', 'C561918F-C2E9-4507-BD7C-00FB688D2D6E'
UNION ALL
SELECT 'Unknown, UN 00000', 'C561918F-C2E9-4507-BD7C-00FB688D2D6E'
UNION ALL
SELECT 'Memphis, TN 38133', '8ED8C601-5D35-4EB7-9217-012905D6E9F1'
UNION ALL
SELECT '44 Maverick St., Fitchburg, MA', '8ED8C601-5D35-4EB7-9217-012905D6E9F1';
SELECT a.people_id,
(SELECT SUBSTRING(
(SELECT ';'+b.address_desc
FROM #TestData b
WHERE a.people_id = b.people_id
FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)')
,2
,4000)
) GROUP_CONCATENATE
FROM #TestData a
GROUP BY a.people_id
Results:
people_id GROUP_CONCATENATE
------------------------------------ ------------------------------------------------------
F15D1135-9947-4F66-B778-00E43EC44B9E Murfreesboro, TN 37130
C561918F-C2E9-4507-BD7C-00FB688D2D6E 11 Mohawk Rd., Burlington, MA 01803;Unknown, UN 00000
8ED8C601-5D35-4EB7-9217-012905D6E9F1 Memphis, TN 38133;44 Maverick St., Fitchburg, MA