How to fix this pivot query...? - sql

I have this query:
with cte1 as (
select id,
row_number() over (partition by [Id] order by id) as row,
first_name + ' ' + last_name as [Contact Name]
from contacts
where is_company = 0 and is_active = 1
),
companyContacts as (
select * from cte1 where row < 6
)
select company.company_name,
c.[1] as contact_1,
c.[2] as contact_2,
c.[3] as contact_3,
c.[4] as contact_4,
c.[5] as contact_5
from contacts company
left join contact_company_relation_additional_information relation
on company.id = relation.company_id and relation.ContactCompanyRelation_IsActive = 1
left outer join
(select *
from companyContacts
pivot (min([Contact Name]) for row in ([1],[2],[3],[4],[5])) x
) c on c.id = relation.contact_id
where is_company = 1 and is_active = 1
order by company.company_name
That brings me the data this way:
company_name contact_1 contact_2 contact_3 contact_4 contact_5
Analist Ori Reshef NULL NULL NULL NULL
Analist Ben Gurion NULL NULL NULL NULL
Analist Ofer Jerus NULL NULL NULL NULL
Bar Net Maya Leshe NULL NULL NULL NULL
Bar Net Yossi Farc NULL NULL NULL NULL
Bar Net Dima Brods NULL NULL NULL NULL
Here for some reason the contacts are in different rows and only in column: "contact_1".
But I need to have only one row ro each company name, and have the contacts in 5 diferent columns. Like this:
company_name contact_1 contact_2 contact_3 contact_4 contact_5
Analist Ori Reshef Ben Gurion Ofer Jerus NULL NULL
Bar Net Maya Leshe Yossi Farc Dima Brods NULL NULL
Can someone tell me how can I fix this query to bring me the data as I need it...?
My table struct is:
table Contacts: [id], [is_company], [first_name], [last_name], [company_name]
table contact_company_relation: [id], [company_id], [contact_id]
Sample Data:
table contacts:
id is_company first_name last_name company_name
1 True NULL NULL Analist
2 True NULL NULL Bar Net
3 False Ori Reshef NULL
4 False Ben Gurion NULL
5 False Ofer Jerus NULL
6 False Maya Leshe NULL
7 False Yossi Farc NULL
8 False Dima Brods NULL
table contact_company_relation:
id company_id contact_id
1 1 3
2 1 4
3 1 5
4 2 6
5 2 7
6 2 8

The problem that you are having is with the following line:
row_number() over (partition by [Id] order by id) as row,
This is creating a unique number for each row in your contacts table but you are partitioning the table by the id column which appears to be a unique value already for each row.
You should be partitioning the data based on the number of rows that exist in the contact_company_relation table instead.
I also would alter the code to something like the following:
select company_name,
Contact1, Contact2, Contact3,
Contact4, Contact5
from
(
select c.first_name + ' ' + c.last_name as contact_name,
comp.company_name,
'Contact'+
cast(row_number() over(partition by ccr.company_id
order by ccr.contact_id) as varchar(1)) row
from contacts c
inner join contact_company_relation ccr
on c.id = ccr.contact_id
inner join contacts comp
on ccr.company_id = comp.id
) d
pivot
(
max(contact_name)
for row in (Contact1, Contact2, Contact3,
Contact4, Contact5)
) p;
See SQL Fiddle with Demo. This gives a result:
| COMPANY_NAME | CONTACT1 | CONTACT2 | CONTACT3 | CONTACT4 | CONTACT5 |
|--------------|------------|------------|------------|----------|----------|
| Analist | Ori Reshef | Ben Gurion | Ofer Jerus | (null) | (null) |
| Bar Net | Maya Leshe | Yossi Farc | Dima Brods | (null) | (null) |

Related

Selecting all rows conditionally between 2 arbitrary column values in SQL Server

I've joined a number of tables to get to this table. From this table I need to select all of the b_id values that fall between the start end end values that are not null. There could be multiple start and end values in the table. How can I write a SQL Server query to select all of the b_ids between but not including those rows. So for this example table I would need the b_ids 99396 AND 71828
I tried to find a similar question and found something like this but I don't believe I'm using the correct values where they need to be. Is there another way to do it. I have a solution using a cursor, but I'm trying to find a non cursor solution. My friend told me the responses on here can be brutal if you don't word the question a certain way. Please be easy on me lol.
a_id | b_id | sequence | start | end |
---------+-------+----------+-------+-------+
3675151 | 68882 | 1 | null | null |
3675151 | 79480 | 2 | 79480 | null |
3675151 | 99396 | 3 | null | null |
3675151 | 71828 | 4 | null | null |
3675151 | 28911 | 5 | null | 28911 |
3675151 | 27960 | 6 | null | null |
3675183 | 11223 | 1 | null | null |
3675183 | 77810 | 2 | null | null |
3675183 | 11134 | 3 | null | null |
3675183 | 90909 | 4 | null | null |
Is this what you are looking for
select a_id, b_id, sequence
from
table
where
(a_id,sequence )
in
(select a_id, sequence from table t1
where
sequence >
(select sequence from table t2 where t1.a_id = t2.a_id and start is not null)
and
sequence <
(select sequence from table t3 where t1.a_id = t3.a_id and end is not null)
);
Would it be this?
Mark as answer if yes, if not exemplify otherwise.
create table #table (
a_id int
,b_id int
,c_sequence int
,c_start int
,c_end int
)
insert into #table
values
(3675151 ,68882 , 1 , null , null )
,(3675151 ,79480 , 2 , 79480 , null )
,(3675151 ,99396 , 3 , null , null )
,(3675151 ,71828 , 4 , null , null )
,(3675151 ,28911 , 5 , null , 28911)
,(3675151 ,27960 , 6 , null , null )
,(3675183 ,11223 , 1 , null , null )
,(3675183 ,77810 , 2 , 4343 , null )
,(3675183 ,11134 , 3 , null , null )
,(3675183 ,90939 , 4 , null , 1231 )
select
t.*
from #table t
where
exists (select t1.b_id,t1.c_sequence
from #table t1
where t1.c_start is not null
and t.a_id =t1.a_id and t.c_sequence>t1.c_sequence )
and exists (select t1.b_id,t1.c_sequence
from #table t1
where t1.c_end is not null
and t.a_id =t1.a_id
and t.c_sequence<t1.c_sequence
You can use window functions for this:
select t.*
from (select t.*,
max(case when c_start is not null then c_sequence end) over (partition by a_id order by c_sequence) as last_c_start,
max(case when c_end is not null then c_sequence end) over (partition by a_id order by c_sequence) as last_c_end,
min(case when c_end is not null then c_sequence end) over (partition by a_id order by c_sequence desc) as next_c_end
from t
) t
where c_sequence > last_c_start and
c_sequence < next_c_end and
(last_c_start > last_c_end or last_c_end is null);
Here is a db<>fiddle.
The subquery is returning the previous start and next end. That is pretty simply. The where uses this information. The last condition just checks that the most recent "start" is the one that should be considered.
Note: This does not handle more complicated scenarios like start-->start-->end-->end. If that is a possibility, you should ask another question.
EDIT:
Actually, there is an even easier way:
select t.*
from (select t.*,
count(coalesce(c_start, c_end)) over (partition by a_id order by c_sequence) as counter
from t
) t
where c_start is null and c_end is null and
counter % 2 = 1;
This returns rows where there two values are NULL (to avoid the endpoints) and there are an odd number of non-NULL c_start/c_end values up to that row.

Hierarchical structure, new columns, denormalization

Suppose I have table like this:
id parent_id name
11 NULL Company
33 11 Department 1
44 33 Department 2
I would like to transform it into:
id parent_id name Level1 Level2 Level3
11 NULL Company NULL NULL NULL
22 11 Company Department 1 NULL NULL
33 22 Company Department 1 Department 2 NULL
I am able to create a CTE and come up with Levels column showing a value in hierarchy, but I don't know how to make new columns for departments as presented.
with myCTE as (
select c.id, c."name", c.parent_id, 1 as Level
from table1 c
where c.parent_id IS NULL
UNION ALL
Select c1.id, c1."name", c1.parent_id, Level +1
from table1 c1
inner join myCTE on c1.parent_id = myCTE.id
where c1.parent_id IS NOT NULL
)
select * from myCTE
showing:
id parent_id name level
1 11 NULL Company 1
2 22 11 Department 2 2
3 33 22 Department 3 3
An (almost) fully generic approach:
DECLARE #tbl TABLE(id INT,parent_id INT,name VARCHAR(100));
INSERT INTO #tbl VALUES
(11,NULL,'Company')
,(33,11,'Department 1')
,(44,33,'Department 2a')
,(55,33,'Department 2b')
,(66,44,'SubDep 2a');
--The recursive CTE will build an XML fragment on a row-by-row level
--The SELECT will use XML method .nodes() and ROW_NUMBER to generate column names for PIVOT
WITH recCTE AS
(
SELECT id, parent_id,name,(SELECT name AS [*] FOR XML PATH('')) AS NameConcat
FROM #tbl WHERE parent_id IS NULL
UNION ALL
SELECT t.id,t.parent_id,t.name,recCTE.NameConcat + '</lvl><lvl>' + (SELECT t.name AS [*] FOR XML PATH(''))
FROM #tbl AS t
INNER JOIN recCTE ON recCTE.id=t.parent_id
)
SELECT p.*
FROM
(
SELECT id
,parent_id
,name
,'Level' + REPLACE(STR(ROW_NUMBER() OVER(PARTITION BY id ORDER BY (SELECT NULL)),2),' ','0') AS HierarchyRank
,lvl.value(N'(./text())[1]','nvarchar(max)') AS HierarchyName
FROM recCTE
CROSS APPLY (SELECT CAST('<lvl>' + NameConcat + '</lvl>' AS XML) AS PreLevels ) AS Casted
CROSS APPLY Casted.PreLevels.nodes(N'/lvl') AS A(lvl)
) AS tbl
PIVOT
(
MAX(HierarchyName) FOR HierarchyRank IN(Level01,Level02,Level03,Level04,Level05,Level06,Level07,Level08,Level09)
) AS p;
The result
+----+-----------+----------------+---------+----------------+---------------+-----------+---------+
| id | parent_id | name | Level01 | Level02 | Level03 | Level04 | Level05 |
+----+-----------+----------------+---------+----------------+---------------+-----------+---------+
| 11 | NULL | Company | Company | NULL | NULL | NULL | NULL |
+----+-----------+----------------+---------+----------------+---------------+-----------+---------+
| 33 | 11 | Department 1 | Company | Department 1 | NULL | NULL | NULL |
+----+-----------+----------------+---------+----------------+---------------+-----------+---------+
| 44 | 33 | Department 2a | Company | Department 1 | Department 2a | NULL | NULL |
+----+-----------+----------------+---------+----------------+---------------+-----------+---------+
| 55 | 33 | Department 2b | Company | Department 1 | Department 2b | NULL | NULL |
+----+-----------+----------------+---------+----------------+---------------+-----------+---------+
| 66 | 44 | SubDep 2a | Company | Department 1 | Department 2a | SubDep 2a | NULL |
+----+-----------+----------------+---------+----------------+---------------+-----------+---------+
If you need more levels, the only need was to add more column names into the PIVOT part...
You can calculate the rows for every level, and unite them :
with MyCTE as (
select id, parent_id, name, null as level1, null as level2, null as level3
from table1 as root
where root.parent_id is null
union
select level1.id, level1.parent_id, root.name, level1.name as level1, null as level2, null as level3
from table1 as level1
inner join table1 as root on root.id = level1.parent_id
where root.parent_id is null
union
select level2.id, level2.parent_id, root.name, level1.name as level1, level2.name as level2, null as level3
from table1 as level2
inner join table1 as level1 on level1.id = level2.parent_id
inner join table1 as root on root.id = level1.parent_id
where root.parent_id is null
union
select level3.id, level3.parent_id, root.name, level1.name as level1, level2.name as level2, level3.name as level3
from table1 as level3
inner join table1 as level2 on level2.id = level3.parent_id
inner join table1 as level1 on level1.id = level2.parent_id
inner join table1 as root on root.id = level1.parent_id
where root.parent_id is null
)
select * from MyCTE
If you need more levels, you will need to add more selects with additional joins

Select last changed row in sub-query

I have a table product:
id | owner_id | last_activity | box_id
------------------------------------
1 | 2 | 12/19/2014 | null
2 | 2 | 12/13/2014 | null
3 | 2 | 08/11/2014 | null
4 | 2 | 12/11/2014 | 99
5 | 2 | null | 99
6 | 2 | 12/15/2014 | 99
7 | 2 | null | 105
8 | 2 | null | 105
9 | 2 | null | 105
The only variable that I have is owner_id.
I need to select all products of a user, but if the product is in a box then only latest one should be selected.
Sample output for owner = 2 is following:
id | owner_id | last_activity | box_id
------------------------------------
1 | 2 | 12/19/2014 | null
2 | 2 | 12/13/2014 | null
3 | 2 | 08/11/2014 | null
6 | 2 | 12/15/2014 | 99
7 | 2 | null | 105
I'm not able to find a way to select the latest product from a box.
My current query, which does not return correct value, but can be executed:
SELECT p.* FROM product p
WHERE p.owner_id = 2
AND (
p.box IS NULL
OR (
p.box IS NOT NULL
AND
p.id = ( SELECT MAX(pp.id) FROM product pp
WHERE pp.box_id = p.box_id )
)
I tried with dates:
SELECT p.* FROM product p
WHERE p.owner_id = 2
AND (
p.box IS NULL
OR (
p.box IS NOT NULL
AND
p.id = ( SELECT * FROM (
SELECT pp.id FROM product pp
WHERE pp.box_id = p.box_id
ORDER BY last_activity desc
) WHERE rownum = 1
)
)
Which gives error: p.box_id is undefined as it's inside 2nd subquery.
Do you have any ideas how can I solve it?
The ROW_NUMBER analytical function might help with such queries:
SELECT "owner_id", "id", "box_id", "last_activity" FROM
(
SELECT "owner_id", "id", "box_id", "last_activity",
ROW_NUMBER()
OVER (PARTITION BY "box_id" ORDER BY "last_activity" DESC NULLS LAST) rn
-- ^^^^^^^^^^^^^^^
-- descending order, reject nulls after not null values
-- (this is the default, but making it
-- explicit here for self-documentation
-- purpose)
FROM T
WHERE "owner_id" = 2
) V
WHERE rn = 1 or "box_id" IS NULL
ORDER BY "id" -- <-- probably not necessary, but matches your example
See http://sqlfiddle.com/#!4/db775/8
there can be nulls as a value. If there are nulls in all products inside a box, then MIN(id) should be returned
Even if is is probably not a good idea to rely on id to order things is you think you need that, you will have to change the ORDER BY clause to:
... ORDER BY "last_activity" DESC NULLS LAST, "id" DESC
-- ^^^^^^^^^^^
Use exists
SELECT
p.*
FROM
product p
WHERE
p.owner_id = 2 AND
( p.box IS NULL OR
(
p.box IS NOT NULL AND
NOT EXISTS
(
SELECT
pp.id
FROM
product pp
WHERE
pp.box_id = p.box_id AND
pp.last_activity > p.last_activity
)
)
)
You can use union to first get all rows where box_is null and than fetch rows with max id and date where box_id is not null:
SELECT * FROM
(
SELECT id,owner_id,last_activity,box_id FROM product WHERE owner_id = 2 AND box_id IS NULL
UNION
SELECT MAX(id),owner_id,MAX(last_activity),box_id FROM product WHERE owner_id = 2 AND box_id IS NOT NULL GROUP BY owner_id, box_id
) T1
ORDER BY
id

How to use window functions in sql to bring distinct values?

I have this query to bring a company name and its top 5 contact names and top 5 phone numbers.
It works fine when I bring only contacts or only phones but when I try to bring both all the values returned are not distinct (e.g. there is more then one row for each company).
I think it has something to do with the partitions, but I have not idea what it is.
Can someone please help me to:
Fix this query.
Understand what the fix means.
query:
select
p.company_name,
p.Contact_1, p.Contact_2, p.Contact_3, p.Contact_4, p.Contact_5,
p.Phone_1, p.Phone_2, p.Phone_3, p.Phone_4, p.Phone_5
from
(
select contact.first_name + ' ' + contact.last_name as contact_name,
phone.display_phone,
company.company_name,
'Contact_'+
cast(row_number() over(partition by relation.company_id
order by contact.first_name, contact.last_name) as varchar(50)) row,
'Phone_'+
cast(row_number() over(partition by phone.contact_id
order by phone.display_phone) as varchar(50)) row2
from contacts company
left join contact_company_relation_additional_information relation
on company.id = relation.company_id and relation.ContactCompanyRelation_IsActive = 1
left join contacts contact
on relation.contact_id = contact.id and contact.is_company = 0 and contact.is_active = 1
left join contact_phones phone on company.id = phone.contact_id and phone.is_active = 1
where company.is_company = 1 and company.is_active = 1
) d
pivot
(
max(contact_name)
for row in (Contact_1, Contact_2, Contact_3, Contact_4, Contact_5)
) x
pivot
(
max(display_phone)
for row2 in (Phone_1, Phone_2, Phone_3, Phone_4, Phone_5)
) p
Here is a link to sql fiddle with the duplicated rows: Contacts and Phones
Here are links to the queries with only contacts or only phones that bring one row for each company:
Contacts only
Phones only
In order to get the result that you want, I would suggest a slightly different approach to this. Since you want to pivot on two columns of data Contacts and Phone, I would first unpivot these columns into multiple rows, then apply the PIVOT - I think it is easier to to that then trying to apply the PIVOT twice.
I see a few things that I would fix in your current query. The main part of you query that is joining to all of the tables has a couple of things to change. First, I would only create one Row column:
row_number() over(partition by relation.company_id
order by contact.first_name, contact.last_name) row
This column would be partitioned by the company_Id in the in the contact_company_relation table. This new row number will be used for both the Contact and the Phone number columns.
Second, your current join to return the Phone number appears to be incorrect. Your current code is using the main company id but you want to join on each contact. Change your code from:
left join contact_phones phone
on company.id = phone.contact_id
to:
left join contact_phones phone
on contact.id = phone.contact_id
This will make your subquery:
select
contact.first_name + ' ' + contact.last_name as contact_name,
phone.display_phone,
company.company_name,
row_number() over(partition by relation.company_id
order by contact.first_name, contact.last_name) row
from contacts company
left join contact_company_relation relation
on company.id = relation.company_id
left join contacts contact
on relation.contact_id = contact.id
and contact.is_company = 0
left join contact_phones phone
on contact.id = phone.contact_id -- change to join on contact
where company.is_company = 1;
See SQL Fiddle with Demo. The data will now look like:
| CONTACT_NAME | DISPLAY_PHONE | COMPANY_NAME | ROW |
|--------------|---------------|--------------|-----|
| Ben Gurion | 2222222 | Analist | 1 |
| Ofer Jerus | 3333333 | Analist | 2 |
| Ori Reshef | 1111111 | Analist | 3 |
Once you have the data with the row number, you can unpivot the display_phone and company_name into multiple rows instead of columns. You didn't specify what version of SQL Server you are using but you can use either UNPIVOT or CROSS APPLY to do this. When you unpivot the data you will then use the Row value to associate each contact and phone pair - this makes sure that each contact is still associated with the correct phone number. The code would be similar to:
;with cte as
(
-- query from above here
)
select compnay_name, col, value
from
(
select company_name,
col = col+'_'+cast(row as varchar(50)),
value
from cte
cross apply
(
select 'Contact', Contact_name union all
select 'Phone', display_phone
) c (col, value)
) src;
See SQL Fiddle with Demo. The data will now be in the format which has multiple rows for each company_name, contact and phone:
| COMPANY_NAME | COL | VALUE |
|--------------|-----------|------------|
| Analist | Contact_1 | Ben Gurion |
| Analist | Phone_1 | 2222222 |
| Analist | Contact_2 | Ofer Jerus |
| Analist | Phone_2 | 3333333 |
| Analist | Contact_3 | Ori Reshef |
| Analist | Phone_3 | 1111111 |
The final step would be to add the PIVOT function making the final code:
;with cte as
(
select
contact.first_name + ' ' + contact.last_name as contact_name,
phone.display_phone,
company.company_name,
row_number() over(partition by relation.company_id
order by contact.first_name, contact.last_name) row
from contacts company
left join contact_company_relation relation
on company.id = relation.company_id
left join contacts contact
on relation.contact_id = contact.id
and contact.is_company = 0
left join contact_phones phone
on contact.id = phone.contact_id -- change to join on contact
where company.is_company = 1
)
select company_name,
contact_1, contact_2, contact_3, contact_4, contact_5,
phone_1, phone_2, phone_3, phone_4, phone_5
from
(
select company_name,
col = col+'_'+cast(row as varchar(50)),
value
from cte
cross apply
(
select 'Contact', Contact_name union all
select 'Phone', display_phone
) c (col, value)
) src
pivot
(
max(value)
for col in (contact_1, contact_2, contact_3, contact_4, contact_5,
phone_1, phone_2, phone_3, phone_4, phone_5)
) p;
See SQL Fiddle with Demo. The final result looks like:
| COMPANY_NAME | CONTACT_1 | CONTACT_2 | CONTACT_3 | CONTACT_4 | CONTACT_5 | PHONE_1 | PHONE_2 | PHONE_3 | PHONE_4 | PHONE_5 |
|--------------|------------|------------|------------|-----------|-----------|---------|---------|---------|---------|---------|
| Analist | Ben Gurion | Ofer Jerus | Ori Reshef | (null) | (null) | 2222222 | 3333333 | 1111111 | (null) | (null) |
| Bar Net | Dima Brods | Maya Leshe | Yossi Farc | (null) | (null) | 7777777 | 4444444 | 6666666 | (null) | (null) |

Select distinct records with Min Date from two tables with Left Join

I'm trying to retrieve all distinct AccountId’s as well as the earliest InsertDate for each. Occasionally the AccountId is not known and although the transactions may be distinct I want to bucket all of the ‘-1’s into their own group.
This is what I have attempted so far along with the schemas.
CREATE TABLE #tmpResults (
Trans Varchar(12),
AccountId Varchar(50),
EarlyDate DateTime DEFAULT getdate(), CardType Varchar(16))
insert #tmpResults
select [Trans] = convert(varchar(12),'CashSale')
, [AccountId] = b.AccountId
, [EarlyDate] = min(b.InsertDate)
, case when c.name LIKE '%VISA%' then 'VISA'
when c.name LIKE '%MasterCard%' then 'MasterCard'
when c.name LIKE '%AMEX%' then 'AMEX'
else 'Other'
end as [CardType]
from TransBatch b
left join CardVer_3 c WITH (NOLOCK) ON c.Id = B.BatchId
left join TransBatch b2
on (b.accountid = b2.accountid and (b.InsertDate > b2.InsertDate or b.InsertDate = b2.InsertDate))
and b2.accountid is NULL
group by b.accountid, b.InsertDate,c.name
order by b.accountid DESC
select * from #tmpResults
The table schemas are like so:
**TransBatch**
RecordId |BatchId |InsertDate | AccountId | AccNameHolder
6676 | 11 | 2012-11-01 05:19:04.000 | 12345 | Account1
6677 | 11 | 2012-11-01 05:19:04.000 | 12345 | Account1
6678 | 11 | 2012-11-01 05:19:04.000 | 55555 | Account2
6679 | 11 | 2012-11-01 05:19:04.000 | -1 | NULL
6680 | 12 | 2012-11-02 05:20:04.000 | 12345 | Account1
6681 | 12 | 2012-11-02 05:20:04.000 | 55555 | Account2
6682 | 13 | 2012-11-04 06:20:04.000 | 44444 | Account3
6683 | 14 | 2012-11-05 05:30:04.000 | 44444 | Account3
6684 | 14 | 2012-11-05 05:31:04.000 | -1 | NULL
**CardVer_3**
BatchId |Name
11 |MasterCard
12 |Visa
13 |AMEX
14 |GoCard
This will be an intermediate table, the output is planned to look like the attached.
Gordon, I made some very minor changes to your suggestion and believe I have the correct output: http://www.sqlfiddle.com/#!3/cfbc3/7/0 . Thank you very much. I'm not at all familiar with the windows functions so I'm going to brush up on these.
The code is here:
select 'CashSale' as [Trans],
AccountId,
min(InsertDateTime),
(case when name LIKE '%VISA%' then 'VISA'
when name LIKE '%MasterCard%' then 'MasterCard'
when name LIKE '%AMEX%' then 'AMEX'
else 'Other'
end) as [CardType]
from (select AccountId, InsertDateTime, c.name,
row_number() over (partition by AccountId order by insertDateTime asc) as seqnum
from TransBatch b left join
CardVer_3 c WITH (NOLOCK) ON c.batchId = B.BatchId
) t
where seqnum = 1
group by t.accountid, t.name
The next steps are to dump this into a temp table and try and get the output looking like the attached excel screen.
It sounds like you are trying to get the full record for the minimum insert date time. For this, you want to use windows functions:
select 'CashSale' as Trans,
AccountId,
min(InsertDate),
(case when name LIKE '%VISA%' then 'VISA'
when name LIKE '%MasterCard%' then 'MasterCard'
when name LIKE '%AMEX%' then 'AMEX'
else 'Other'
end) as [CardType]
from (select AccountId, InsertDate, c.name,
row_number() over (partition by AccountId order by insertDate desc) as seqnum
from TransBatch b left join
CardVer_3 c WITH (NOLOCK)
ON c.Id = B.BatchId
) t
where seqnum = 1
I'm taking a guess that "CashSale" means that the credit card did not match. The TransId is then either the recordId or "CashSale".