PIVOT table is converting all columns to DATE - sql-server

Why does my view get created with FIRM_PANEL and FIRM_DATE as DATEs and not an NVARCHAR and a DATE as I expect?
SELECT DISTINCT piv3.CASE_NUMBER, FIRM_PANEL, FIRM_DATE
FROM
(
SELECT DISTINCT piv2.CASE_NUMBER, piv2.DETAIL_CODE,
CASE piv2.DATA_FLAGS
WHEN 'TEXT_FLAG' THEN CONVERT(NVARCHAR, TEXT_VALUE)
WHEN 'DATE_FLAG' THEN CONVERT(DATE, DATE_VALUE)
END AS 'VALUE'
FROM
(
SELECT DISTINCT CASE_NUMBER, DETAIL_CODE, piv.DATA_FLAGS, TEXT_VALUE, CONVERT(DATE, DATE_VALUE) AS DATE_VALUE
FROM table
UNPIVOT
(
YES_OR_NO
FOR DATA_FLAGS IN (DATE_FLAG, TEXT_FLAG)
) piv
) piv2
) as sourcetable
PIVOT
(
MAX(VALUE)
FOR DETAIL_CODE IN (FIRM_PANEL,
FIRM_DATE )
) AS piv3
Let me explain each nested pivot/select. The first SELECT gives you an idea how the data is store raw. I needed a way to show 1 CASE_NUMBER and DETAIL_CODEs as unique columns.
SELECT DISTINCT CASE_NUMBER, DETAIL_CODE, piv.DATA_FLAGS, TEXT_VALUE,
CONVERT(DATE, DATE_VALUE) AS DATE_VALUE
FROM table
I then needed to take DATE_VALUE and TEXT_VALUE and merge into one column called VALUE, hence my UNPIVOT.
SELECT DISTINCT piv2.CASE_NUMBER, piv2.DETAIL_CODE,
CASE piv2.DATA_FLAGS
WHEN 'TEXT_FLAG' THEN CONVERT(NVARCHAR, TEXT_VALUE)
WHEN 'DATE_FLAG' THEN CONVERT(NVARCHAR, DATE_VALUE)
END AS 'VALUE'
FROM
(
SELECT DISTINCT CASE_NUMBER, DETAIL_CODE, piv.DATA_FLAGS, TEXT_VALUE, CONVERT(DATE, DATE_VALUE) AS DATE_VALUE
FROM table
UNPIVOT
(
YES_OR_NO
FOR DATA_FLAGS IN (DATE_FLAG, TEXT_FLAG)
) piv
) piv2
Then my final SQL (top) finally PIVOTs again and removes nulls and interprets if the column was a text field then use NVARCHAR and if date use DATE.
However the only way I can SELECT from the view without errors is making both NVARCHAR or else I'll receive "Conversion failed when converting date and/or time from character string" due to both columns being a DATE field when they shouldn't be. However I do not understand why the view is created with the two DATE columns. My current workaround is setting as string and just doing the conversion when selecting from the view.
Sorry for the wordiness.

I think an-d was on the right path.. You should definitely try case aggregates.
SELECT CASE_NUMBER,
MAX( CASE WHEN DETAIL_CODE = 'FIRM_PANEL' THEN TEXT_VALUE END) AS FIRM_PANEL,
MAX( CASE WHEN DETAIL_CODE = 'FIRM_DATE' THEN CONVERT(DATE, DATE_VALUE) END) AS FIRM_DATE
FROM table
GROUP BY CASE_NUMBER
You can add more where clauses to the case expression if you need to, but the above may work.

I don't quite understand the reasons for your use of pivots, but the problem seems to be that you're trying to cast the same column VALUE as both DATE and NVARCHAR here -
...
CASE piv2.DATA_FLAGS
WHEN 'TEXT_FLAG' THEN CONVERT(NVARCHAR, TEXT_VALUE)
WHEN 'DATE_FLAG' THEN CONVERT(DATE, DATE_VALUE)
END AS 'VALUE'
...
You can't control when SQL Server performs the CONVERT operation and it might do it before checking the DATA_FLAGS. Your query should work if you try to move the CONVERT outside to the first select -
SELECT DISTINCT piv3.CASE_NUMBER, CONVERT(NVARCHAR, FIRM_PANEL), CONVERT(DATE, FIRM_DATE)
FROM
(
SELECT DISTINCT piv2.CASE_NUMBER, piv2.DETAIL_CODE,
CASE piv2.DATA_FLAGS
WHEN 'TEXT_FLAG' THEN TEXT_VALUE
WHEN 'DATE_FLAG' THEN DATE_VALUE
END AS 'VALUE'
...
If you're open to not using pivots, here's a simple alternative to get the same result -
SELECT CASE_NUMBER
, MAX(CASE WHEN DATA_FLAGS = 'TEXT_FLAG' THEN TEXT_VALUE END) FIRM_DATE
, MAX(CASE WHEN DATA_FLAGS = 'DATE_FLAG' THEN CONVERT(DATE, DATE_VALUE) END) FIRM_PANEL
FROM #in
GROUP BY CASE_NUMBER
This gives the same result as you're looking for with the columns in their expected data types. Note that it assumes you will have only one record with TEXT_FLAG and DATE_FLAG each for every CASE_NUMBER.

Related

Datatype in INNER select statement is defined as MONEY but then is changed in outer SELECT statement to int datatype

In my t-sql code below, in the INNER SELECT I am doing a SUM(t.WrittenPremium) and the t.WrittenPremium is defined as a MONEY datatype but then in the OUTER SELECT statement when I hover over the a.WrittenPremium it is NOW an INT datatype. The reason for the question is that I'm getting an error in a SSRS report that I'm working on from the results of this query stating that the WrittenPremium cannot be converted to String though I'm not trying to convert anything to a string. Any help/direction would be appreciated. Thanks.
Here is my SQL code:
SELECT
CASE
WHEN a.[Period] = '' THEN a.[Year] + ' - Total '+a.TermType
ELSE a.[Year]
END as [Year]
, a.[Period]
, a.TermType
, a.WrittenPremium
FROM (
SELECT
CASE
WHEN GROUPING (t.[Year]) = 1 THEN 'Total'
ELSE t.[Year]
END as [Year]
, ISNULL(t.[Period],'') as [Period]
, ISNULL(t.TermType,'') as TermType
, SUM(t.WrittenPremium) as WrittenPremium
, RN
FROM #temp t
GROUP BY GROUPING SETS ((t.[Year], t.[Period], t.TermType, t.RN), (t.[Year], t.TermType))
) a
ORDER BY 1 asc, a.RN asc, a.TermType;
Under the assuption that Year is an INT datatype I think the type mismatch is happen in the CASE setting Year. Please try this:
CASE
WHEN GROUPING (t.[Year]) = 1 THEN 'Total'
ELSE CAST(t.[Year] AS VARCHAR(10))
END as [Year]

Can't Seem to Insert CTE Into Table

My cte run find, and gives me the numbers I expect, but I can't seem to insert the results into a table. I did some research online, before posting, and the setup seems correct, based on what I've seen, but I must be missing some step somewhere, because this script doesn't work. Can someone see something that I can't see? I am on SQL Server 2008.
with cte as
(
select
*, rn = row_number() over (partition by Credit_Line_NO order by REVIEW_FREQUENCY)
from TBL_FBNK_LIMIT_HIST
)
(select CREDIT_LINE_NO
,LIMIT_CURRENCY
,(CAST(AVAIL_AMT AS DECIMAL(30,15)) * (CAST(SUBSTRING(CUSIP_NO,1,CHARINDEX('%',CUSIP_NO)-1) AS DECIMAL(30,15))/100))/(12/CAST(LEFT(SUBSTRING(REVIEW_FREQUENCY, CHARINDEX('M',review_frequency)+1,LEN(REVIEW_FREQUENCY)),2) AS DECIMAL)) AS AMOUNT
,REVIEW_FREQUENCY
,CAST(LEFT(REVIEW_FREQUENCY, 8) AS DATE) AS STARTDATE
,CAST(EXPIRY_DATE AS DATE) AS EXPIRY_DATE
,CAST(round((DATEDIFF(MONTH,cast(LEFT(REVIEW_FREQUENCY,8) as DATE),CAST(EXPIRY_DATE AS DATE)))/cast(LEFT(SUBSTRING (REVIEW_FREQUENCY, CHARINDEX('M',review_frequency)+1,LEN(REVIEW_FREQUENCY)),2) as decimal)+0.4,0) AS INTEGER) AS FREQUENCY
,CAST(DATEADD(MONTH, (rn-1)* LEFT((SUBSTRING(REVIEW_FREQUENCY, CHARINDEX('M',review_frequency)+1,LEN(REVIEW_FREQUENCY))),2),LEFT(REVIEW_FREQUENCY, 8)) AS DATE) AS EFFECTIVESTARTDATE
FROM cte
WHERE AVAIL_AMT NOT LIKE '%]%'
AND CUSIP_NO IS NOT NULL
AND CUSIP_NO <> '0'
AND AVAIL_AMT <> '0'
AND AVAIL_AMT IS NOT NULL)
INSERT TBL_FBNK_LIMIT_HIST_TRANS_SPLIT (CREDIT_LINE_NO,LIMIT_CURRENCY,AMOUNT,REVIEW_FREQUENCY,START_DATE,EXPIRY_DATE,FREQUENCY,AsOfDate,EFFECTIVESTARTDATE)
Select CREDIT_LINE_NO,LIMIT_CURRENCY,AMOUNT,REVIEW_FREQUENCY,START_DATE,EXPIRY_DATE,FREQUENCY,AsOfDate,EFFECTIVESTARTDATE
From cte
Thanks!
You are not really using the cute in the insert. Try it like:
with cte as
(
select
*, rn = row_number() over (partition by Credit_Line_NO order by REVIEW_FREQUENCY)
from TBL_FBNK_LIMIT_HIST
),
cte2 as
(select CREDIT_LINE_NO
,LIMIT_CURRENCY
,(CAST(AVAIL_AMT AS DECIMAL(30,15)) * (CAST(SUBSTRING(CUSIP_NO,1,CHARINDEX('%',CUSIP_NO)-1) AS DECIMAL(30,15))/100))/(12/CAST(LEFT(SUBSTRING(REVIEW_FREQUENCY, CHARINDEX('M',review_frequency)+1,LEN(REVIEW_FREQUENCY)),2) AS DECIMAL)) AS AMOUNT
,REVIEW_FREQUENCY
,CAST(LEFT(REVIEW_FREQUENCY, 8) AS DATE) AS STARTDATE
,CAST(EXPIRY_DATE AS DATE) AS EXPIRY_DATE
,CAST(round((DATEDIFF(MONTH,cast(LEFT(REVIEW_FREQUENCY,8) as DATE),CAST(EXPIRY_DATE AS DATE)))/cast(LEFT(SUBSTRING (REVIEW_FREQUENCY, CHARINDEX('M',review_frequency)+1,LEN(REVIEW_FREQUENCY)),2) as decimal)+0.4,0) AS INTEGER) AS FREQUENCY
,CAST(DATEADD(MONTH, (rn-1)* LEFT((SUBSTRING(REVIEW_FREQUENCY, CHARINDEX('M',review_frequency)+1,LEN(REVIEW_FREQUENCY))),2),LEFT(REVIEW_FREQUENCY, 8)) AS DATE) AS EFFECTIVESTARTDATE
FROM cte
WHERE AVAIL_AMT NOT LIKE '%]%'
AND CUSIP_NO IS NOT NULL
AND CUSIP_NO <> '0'
AND AVAIL_AMT <> '0'
AND AVAIL_AMT IS NOT NULL)
INSERT TBL_FBNK_LIMIT_HIST_TRANS_SPLIT (CREDIT_LINE_NO,LIMIT_CURRENCY,AMOUNT,REVIEW_FREQUENCY,START_DATE,EXPIRY_DATE,FREQUENCY,AsOfDate,EFFECTIVESTARTDATE)
Select CREDIT_LINE_NO,LIMIT_CURRENCY,AMOUNT,REVIEW_FREQUENCY,START_DATE,EXPIRY_DATE,FREQUENCY,AsOfDate,EFFECTIVESTARTDATE
From cte2;
Code looks like redundant but at least it would work the way you think it should.
You can only use the CTE in the statement for which it is defined. In your case, you have the CTE definition, and a SELECT statement that reads the CTE.
Then, you have a totally separate statement which attempts to read the CTE again for the INSERT. This is not permitted, because the CTE does not exist in the second query's context. So, from the perspective of your INSERT statement, the CTE does not exist. I'm sure you're getting this message:
Msg 208, Level 16, State 1, Line [x] Invalid object name 'cte'.
Get rid of the SELECT statement and replace it with your INSERT.
Alternatively, if you must have the SELECT statement used in both the SELECT and INSERT statements, a CTE may not be appropriate for the use case, or you will need to include the CTE definition for both the SELECT and INSERT.
It should be working fine unless those columns are not in TBL_FBNK_LIMIT_HIST_TRANS_SPLIT or the data types do not match.
That select in the middle is not part of the insert.
INSERT TBL_FBNK_LIMIT_HIST_TRANS_SPLIT
(CREDIT_LINE_NO, LIMIT_CURRENCY, AMOUNT, REVIEW_FREQUENCY, START_DATE, EXPIRY_DATE, FREQUENCY, AsOfDate, EFFECTIVESTARTDATE)
Select CREDIT_LINE_NO, LIMIT_CURRENCY, AMOUNT, REVIEW_FREQUENCY, START_DATE, EXPIRY_DATE, FREQUENCY, AsOfDate, EFFECTIVESTARTDATE
From cte

SQL Server contiguous dates - summarizing multiple rows into contiguous start and end date rows without CTE's, loops,...s

Is it possible to write an sql query that will summarize rows with start and end dates into rows that have contiguous start and end dates?
The constraint is that it has to be regular sql, i.e. no CTE's, loops and the like as a third party tool is used that only allows an sql statement to start with Select.
e.g.:
ID StartDate EndDate
1001, Jan-1-2018, Jan-04-2018
1002, Jan-5-2018, Jan-13-2018
1003, Jan-14-2018, Jan-18-2018
1004, Jan-25-2018, Feb-05-2018
The required output needs to be:
Jan-1-2018, Jan-18-2018
Jan-25-2018, Feb-05-2018
Thank you
You can take advantage of both window functions and the use of a concept called gaps-and-islands. In your case, contiguous dates would be the island, and the the gaps are self explanatory.
I wrote the answer below in a verbose way to help make it clear what the query is doing, but it could most likely be written in a different way that is more concise. Please see my comments in the answer explaining what each step (sub-query) does.
--Determine Final output
select min(c.StartDate) as StartDate
, max(c.EndDate) as EndDate
from (
--Assign a number to each group of Contiguous Records
select b.ID
, b.StartDate
, b.EndDate
, b.EndDatePrev
, b.IslandBegin
, sum(b.IslandBegin) over (order by b.ID asc) as IslandNbr
from (
--Determine if its Contiguous (IslandBegin = 1, means its not Contiguous with previous record)
select a.ID
, a.StartDate
, a.EndDate
, a.EndDatePrev
, case when a.EndDatePrev is NULL then 1
when datediff(d, a.EndDatePrev, a.StartDate) > 1 then 1
else 0
end as IslandBegin
from (
--Determine Prev End Date
select tt.ID
, tt.StartDate
, tt.EndDate
, lag(tt.EndDate, 1, NULL) over (order by tt.ID asc) as EndDatePrev
from dbo.Table_Name as tt
) as a
) as b
) as c
group by c.IslandNbr
order by c.IslandNbr
I hope following SQL query can help you to identify gaps and covered dates for given case
I did not use a CTE expression of a dates table function, etc
On the other hand, I used a numbers table using master..spt_values to generate the dates table as the main table of a LEFT join
You can create a numbers table or a dates table if it does not fit to your requirements
In the query, to catch changes between borders I used SQL LAG() function which enables me to compare with previous value of a column in a sorted list
select
max(startdate) as startdate,
max(enddate) as enddate
from (
select
date,
case when exist = 1 then date else null end as startdate,
case when exist = 0 then dateadd(d,-1,date) else null end as enddate,
( row_number() over (order by date) + 1) / 2 as rn
from (
select date, exist, case when exist <> (lag(exist,1,'') over (order by date)) then 1 else 0 end as changed
from (
select
d.date,
case when exists (select * from Periods where d.date between startdate and enddate) then 1 else 0 end as exist
from (
SELECT dateadd(dd,number,'20180101') date
FROM master..spt_values
WHERE Type = 'P' and dateadd(dd,number,'20180101') <= '20180228'
) d
) cte
) tbl
where changed = 1
) dates
group by rn
Here is the result

Calculate same day start/end dates as 0 days if another occurrence already exists

I have a query where there are instances where a "phase" starts and ends on the same day - this is calculated as 1 day. If, however, another "phase" starts and ends on the same day against the same ref. no. and period no., then I'd like to calculate this as 0 days.
Example:
**Ref. Period. Phase StDt EndDt**
013 3 KAA 01/01/16 01/01/16 - This is one day
013 3 TAA 02/01/16 03/01/16 - this is 2 days
013 3 KAT 01/01/16 01/01/16 - **would like this to be counted as 0 day**
013 3 TTA 04/04/16 04/04/16 - this is one day
I would like this unique calculation to be done in the data grouped by Ref. And Period numbers. This is a tricky one....
Thanks
Try this.
I am assuming that you are using TSQl (Not sure a you have also tagged SQL.
;WITH cte_result(ID,Ref, Period,Phase,StDt,EndDt) AS
(
SELECT 1,'013' ,3,'KAA',CAST('01/01/16'AS DATETIME),CAST('01/01/16'AS DATETIME) UNION ALL
SELECT 2,'013' ,3,'TAA','01/02/16','01/03/16' UNION ALL
SELECT 3,'013' ,3,'KAT','01/01/16','01/01/16' UNION ALL
SELECT 4,'013' ,3,'TTA','04/04/16','04/04/16')
,cte_PreResult AS
(
SELECT ROW_NUMBER() OVER (PARTITION BY CAST(StDt AS DATE), CAST(EndDt AS DATE) ORDER BY ID) AS [Order],
Ref,
Period,
Phase,
StDt,
EndDt
FROM cte_result
)
SELECT Ref,
Period,
Phase,
StDt,
EndDt,
CASE
WHEN [Order] <> 1
THEN '0 Day(s)'
ELSE CAST(DATEDIFF(dd, StDt, EndDt) + 1 AS VARCHAR(10)) + ' Day(s)'
END AS Comment
FROM cte_PreResult
If there is no ID column then select some column to order by, probably Phase so replace ID with Phase as here ROW_NUMBER() OVER (PARTITION BY StDt,EndDt ORDER BY ID) AS [Order], if there is no candidate column to order by then try this
;WITH cte_result(ID,Ref, Period,Phase,StDt,EndDt) AS
(
SELECT 1,'013' ,3,'KAA',CAST('01/01/16'AS DATETIME),CAST('01/01/16'AS DATETIME) UNION ALL
SELECT 2,'013' ,3,'TAA','01/02/16','01/03/16' UNION ALL
SELECT 3,'013' ,3,'KAT','01/01/16','01/01/16' UNION ALL
SELECT 4,'013' ,3,'TTA','04/04/16','04/04/16')
,cte_PreResult AS
(
SELECT ROW_NUMBER() OVER (PARTITION BY CAST(StDt AS DATE), CAST(EndDt AS DATE) ORDER BY (SELECT NULL)) AS [Order],
Ref,
Period,
Phase,
StDt,
EndDt
FROM cte_result
)
SELECT Ref,
Period,
Phase,
StDt,
EndDt,
CASE
WHEN [Order] <> 1
THEN '0 Day(s)'
ELSE CAST(DATEDIFF(dd, StDt, EndDt) + 1 AS VARCHAR(10)) + ' Day(s)'
END AS Comment
FROM cte_PreResult
This expression should work on the SSRS side:
=IIF(Fields!StartDate.Value=Fields!EndDate.Value AND Fields!Phase.Value <> LOOKUPSET(Fields!StartDate.Value &"_" & Fields!EndDate.Value,Fields!StartDate.Value & "_" & Fields!EndDate.Value,Fields!Phase.Value,"DatasetName").GetValue(0),0,DATEDIFF("D",Fields!StartDate.Value,Fields!EndDate.Value)+1)
It will return a value of 1 for the first phase returned by the dataset. If the phase-date range combinations are not unique within the grouping, this will not work as written, but you should be able to modify accordingly.
Also, if the rows are sorted differently between SSRS and the dataset, it may not be the first row that appears that gets the 1.
The below did the trick! Basically, I'm using count aggregate to count the number of instances where phases start and end on the same day PER Ref and period. Then, for any where there are more than 1, I just use simple case statments to count the first one as 1 and any subsequent ones as 0. I'm creating the below as a subquery in the joins as a left outer join:
LEFT OUTER JOIN
(SELECT TOP (100) PERCENT Period, Ref,
CONVERT(date, PhaseStartDate) AS stdt, CONVERT(date, PhaseEndDate) AS enddt,
COUNT(*)
AS NoOfSameDayPhases,
MIN(PhaseSequence) AS FirstPhSeq
FROM Phases AS Phases_1
WHERE (CONVERT(date, PhaseStartDate) =
CONVERT(date, PhaseEndDate))
GROUP BY VoidPeriod, Ref, CONVERT(date,
PhaseStartDate), CONVERT(date, PhaseEndDate)) AS SameDayPH ON CONVERT(date,
PhaseEndDate) = SameDayPH.enddt AND CONVERT(date,
PhaseStartDate) = SameDayPH.stdt AND
VoidPeriod = SameDayPH.VoidPeriod AND SameDayPH.Ref =
VoidPhases.Ref

Use DISTINCT with ORDER BY clause

I want to Alter my following stored procedure
ALTER Procedure [dbo].[spEGCRedemptionReportForMHR]
#DateTo datetime,
#DateFrom datetime,
#MerchantID varchar(11),
#Pan varchar(16)
As
Set #DateTo = #DateTo +1
Select Distinct
Convert(varchar(50),pt.TransactionDate,103) 'TransactionDate',
m.MerchantName1 MerchantName,
m.MerchantAddress Location,
m.MerchantID,
pt.TerminalID,
pt.batchnumber 'Batch #',
pt.SequenceNumber 'Receipt #',
pt.PAN 'Card Number',
c.EmbossName 'Card Holder Name',
Convert(Decimal(10,2),Case when pt.TransactionTypeID=2 then (pt.TotalAmount) end) As 'Points Redeemed',
Convert(Decimal(10,2),Case when pt.TransactionTypeID=2 then (((pt.TotalAmount)/(cc.usdconversionrate))/2) end) as 'Total Payment Amount (AED)', --/cc.USDConversionRate end) As 'Total Amount in AED',
Convert(Decimal(10,2),Case when pt.TransactionTypeID=2 then (((pt.TotalAmount)/(cc.usdconversionrate))/2) -15 end) as 'Total loaded Amount (AED)',
3.00 as 'Procco Share',
Convert(Decimal(10,2),Case when pt.TransactionTypeID=2 then (((pt.TotalAmount)/(cc.usdconversionrate))/2) - 3 end) as 'Settlement Amount'
from POS_Transactions pt
inner join Terminal t on t.TerminalID=pt.TerminalID
inner join Merchant m on m.MerchantID=t.MerchantID
inner join Card c on c.EmbossLine=pt.PAN
inner join Share s on s.MerchantID=m.MerchantID,Currency cc
where IsEmaar =1 and
cc.CurrencyCode='AED'
--and m.isemaarmerchant = 1
and (m.MerchantID=#MerchantID or #MerchantID='-999')
and (pt.TransactionDate>=#datefrom and pt.TransactionDate<=#dateto)
and (pt.PAN=#Pan or #Pan ='-999')
order by pt.TransactionDate
But it throws an error everytime I am trying to execute it
ORDER BY items must appear in the select list if SELECT DISTINCT is specified.
I have already used pt.TransactionDate in my select but still its asking me to include it since it is in my order by clause. What is possibly wrong with my query?
Try:
ORDER BY 'TransactionDate'
The error is not that helpful, but the comment by ughai is correct. The engine is particular about how the column is modified in the SELECT statement, and the exact form must be included in the "ORDER BY."
I have run into this when concatenating columns for output reasons, and it gets ugly fast.
You must
ORDER BY Convert(varchar(50),pt.TransactionDate,103)
If you use GROUP BY instead of DISTINCT to eliminate duplicates then you can include grouped fields whether they're in the select list of not. The downside is you'll have to list all the columns used but this will do it.
GROUP BY
pt.TransactionDate,
m.MerchantName1,
m.MerchantAddress ,
m.MerchantID,
pt.TerminalID,
pt.batchnumber,
pt.SequenceNumber,
pt.PAN ,
c.EmbossName,
pt.TransactionTypeID,
pt.TotalAmount,
cc.usdconversionrate
ORDER BY pt.TransactionDate
Yes the column pt.TransactionDate appears in the SELECT but, in inside the CONVERT function as below:
CONVERT(VARCHAR(50), pt.TransactionDate, 103) 'TransactionDate',
add pt.TransactionDate by itself as an extra column as follows:
...
SELECT DISTINCT
CONVERT(VARCHAR(50), pt.TransactionDate, 103) 'TransactionDate',
pt.TransactionDate, -- <-- here
m.MerchantName1 MerchantName,
m.MerchantAddress Location,
m.MerchantID,
pt.TerminalID,
....

Resources