Trouble combining rows into one column using CAST( - sql

Ok SO, here's your time to shine!
No really, I'm getting my butt kicked by an MS-SQL query that I can't seem to get to work.
What I am trying to do is search on a patient name; but also return patients who have a similar first or last name to the querying patient's last name. So "John Smith" can return anyone named "John Smith" or anyone who has a first or last name like "smith". If the a patient has multiple disease states, then combine those disease states into a single column. I have the following tables (though of course there are many more columns, but these are the most imortant):
Patient Table
PatientID FirstName LastName UserIDFK
10000 John Smith 1
10001 Miss Smith 2
10002 Smith Bomb 3
10003 Bobby Smith 4
-- etc
DiseaseStateForUser
UserIDFK DiseaseStateRefId
1 1
1 2
2 2
3 1
3 2
4 1
GlobalLookUp
RefId Ref_Code
1 HIV
2 HEPC
The results I'm looking for are this:
PatientID FirstName LastName DiseaseStates
10000 John Smith HIV|HEPC
10001 Miss Smith HEPC
10002 Smith Bomb HIV|HEPC
10003 Bobby Smith HIV
I've taken the examples from these questions (and countless others):
Is there a way to create a SQL Server function to “join” multiple
rows from a subquery into a single delimited
field?
Simulating group_concat MySQL function in MS SQL Server
2005?
As well as from this blog post Emulating MySQL’s GROUP_CONCAT() Function in SQL Server 2005 I came up with the following SQL procedure
DECLARE
#PatientID INT=null,
#FirstName Varchar(15)= null,
#LastName Varchar(15)= 'Smith',
#Name Varchar(15) = 'John Smith',
Select
Patient.First_Name,
Patient.Last_Name,
patient.PatientID,
(select CAST(GlobalLookUp.Ref_Code + '|' as VARCHAR(MAX))
from
TBL_PATIENT patient
,TBL_GBLLOOKUP GlobalLookUp
,TBL_DiseaseStateForUser DiseaseStateForUser
-- Try and make a collection of all the PatientIDs
-- that match the search criteria
-- so that only these are used to build
-- the DiseaseStatesColumn
,(Select
Patient.PatientID
FROM TBL_PATIENT patient
,TBL_SITEMASTER SiteMaster
,TBL_USERMASTER UserMaster
,TBL_USERSINSITES UserInSites
,TBL_GBLLOOKUP GlobalLookUp
,TBL_DiseaseStateForUser DiseaseStateForUser
WHERE (((patient.[Last_Name] like #LastName + '%') OR (patient.[Last_Name] Like #Name + '%' ))
OR ((patient.[First_Name] Like #Name + '%' ))
OR (patient.[First_Name] + ' ' + patient.[Last_Name] Like #Name + '%' ))
AND UserMaster.User_Id = UserInSites.User_Id_FK
AND UserInSites.Site_Id_FK = SiteMaster.Site_Id
AND UserInSites.Is_Active = 'True'
AND patient.[User_Id_FK] = UserMaster.[User_Id]
AND (DiseaseStateForUser.User_Id_FK = patient.User_Id_FK
AND DiseaseStateForUser.DiseaseState_RefId_FK = GlobalLookUp.Ref_Id)
and DiseaseStateForUser.Is_Active='True'
AND patient.[Is_Active] = 'TRUE'
group by Patient.PatientID) as PATIENTIDs
where patient.PatientID = PATIENTIDs.PatientID
AND (DiseaseStateForUser.User_Id_FK = patient.User_Id_FK
AND DiseaseStateForUser.DiseaseState_RefId_FK = GlobalLookUp.Ref_Id)
For XML PATH('')) as MultiDiseaseState
FROM TBL_PATIENT patient, TBL_SITEMASTER SiteMaster ,TBL_USERMASTER UserMaster,TBL_USERSINSITES UserInSites, TBL_GBLLOOKUP GlobalLookUp, TBL_DiseaseStateForUser DiseaseStateForUser
WHERE (((patient.[Last_Name] like #LastName + '%') OR (patient.[Last_Name] Like #Name + '%' ))
or ((patient.[First_Name] Like #Name + '%' ))
OR (patient.[First_Name] + ' ' + patient.[Last_Name] Like #Name + '%' ))
AND patient.PatientID = patient.PatientID
AND UserMaster.User_Id = UserInSites.User_Id_FK
AND UserInSites.Site_Id_FK = SiteMaster.Site_Id
AND UserInSites.Is_Active = 'True'
AND patient.[User_Id_FK] = UserMaster.[User_Id]
AND DiseaseStateForUser.User_Id_FK = patient.User_Id_FK
AND DiseaseStateForUser.DiseaseState_RefId_FK = GlobalLookUp.Ref_Id
and DiseaseStateForUser.Is_Active='True'
AND patient.[Is_Active] = 'TRUE'
group by PatientID, patient.First_Name, patient.Last_Name, GlobalLookUp.Ref_Code
order by PatientID
Unfortunately, this query nets me the following:
PatientID FirstName LastName MultiDiseaseState
10000 John Smith HIV|HEPC|HEPC|HIV|HEPC|HIV
10001 Miss Smith HIV|HEPC|HEPC|HIV|HEPC|HIV
10002 Smith Bomb HIV|HEPC|HEPC|HIV|HEPC|HIV
10003 Bobby Smith HIV|HEPC|HEPC|HIV|HEPC|HIV
In other words, the select CAST(GlobalLookUp.Ref_Code + '|' as VARCHAR(MAX)) call is building up the MultiDiseaseState column with all of the disease states for ALL of the selected patients.
I know there is something fundamentally wrong with the most inner SELECT statement, but I'm having a hard time figuring out what it is and how to write the query so that it builds only the disease states for a given patient.
Kind of a long post, but are there any suggestions people can make given the code snippets I've provided?

You should be able to use the Stuff function (I think it's only on SQL 2005 and higher) to make this work, I took your example data and wrote a demonstration off of that
SET NOCOUNT ON
CREATE TABLE #Patient
(
PatientID INT,
FirstName varchar(25),
LastName varchar(25),
UserIDFK INT
)
INSERT INTO #PATIENT SELECT 10000,'John','Smith',1
INSERT INTO #PATIENT SELECT 10001,'Miss','Smith',2
INSERT INTO #PATIENT SELECT 10002,'Smith','Bomb',3
INSERT INTO #PATIENT SELECT 10003,'Bobby','Smith',4
CREATE TABLE #DiseaseStateForUser
(
UserIDFK int,
DiseaseStateRefId int
)
INSERT INTO #DiseaseStateForUser SELECT 1,1
INSERT INTO #DiseaseStateForUser SELECT 1,2
INSERT INTO #DiseaseStateForUser SELECT 2,2
INSERT INTO #DiseaseStateForUser SELECT 3,1
INSERT INTO #DiseaseStateForUser SELECT 3,2
INSERT INTO #DiseaseStateForUser SELECT 4,1
CREATE TABLE #GlobalLookUp
(
RefId int,
Ref_Code varchar(10)
)
INSERT INTO #GlobalLookUp SELECT 1,'HIV'
INSERT INTO #GlobalLookUp SELECT 2,'HEPC'
SELECT
PatientID,
UserIDFK,
FirstName,
LastName,
STUFF(
(SELECT '|' + l.Ref_Code
FROM #DiseaseStateForUser u with (Nolock)
JOIN dbo.#GlobalLookUp l with (nolock)
ON u.DiseaseStateRefId = l.RefId
WHERE u.UserIDFK = p.UserIDFK FOR XML PATH('')
)
, 1, 1, '')
FROM #PATIENT p with (Nolock)
GROUP BY PatientID, FirstName, LastName, UserIDFK

Related

SQL how to do a LIKE search on each value of a declared variable

I have a query where I am trying to do a LIKE search on each value of a declared variable, instead of doing a like search on the entire field value/string.
Example:
DECLARE #name VARCHAR(30)
SET #name = 'John Smith'
SELECT name FROM customers WHERE name like '%'+ #name + '%'
The record I am looking for is "John and Jane Smith". The query above returns NO result. If the user searches just 'John' OR just 'Smith' there are too many results returned.
I am trying to get the query to search like the query below:
SELECT name from customers WHERE name LIKE '%John% %Smith%'
I've searched for many options but not sure if my search terms are not correct, I have yet to find a solution.
I would try replacing spaces in your #name with '% %'
Something like
SET #nameFilter = REPLACE(#name,' ','% %')
SELECT name FROM customers WHERE name like '%'+ # nameFilter + '%'
A full-text search seems like the best approach. But you can approximate this at the word level by splitting the search term and looking for each individual word:
with words as (
select value as word
from string_split(#name)
)
select c.name
from customers c cross apply
(select count(*) as cnt
from words w
where c.name like '%' + c.word + '%'
) w
where w.cnt = (select count(*) from words);
This uses the string_split() functionality available in more recent versions of SQL Server. There are online versions of the function for older versions.
This was answered/accepted before I could post and what #sugar2Code posted is how I would do it.
That said, I was unclear if you wanted both the first and last name needed to be similar or just one of them. What I put together will allow you to decide using a parameter.
-- Sample Data
DECLARE #t TABLE (CustomerName VARCHAR(30))
INSERT #t VALUES('Johny Smith'),('Freddie Roach'),('Mr. Smithers'),('Johnathan Smithe');
-- User Arguments
DECLARE
#name VARCHAR(30) = 'John Smith',
#partialmatch BIT = 1;
-- Dynamic Solution
SELECT
t.CustomerName,
FNMatch = SIGN(pos.F),
LNMatch = SIGN(pos.L)
FROM #t AS t
CROSS JOIN
(
SELECT SUBSTRING(#name,1,f.Mid-1), SUBSTRING(#name,f.Mid+1,8000)
FROM (VALUES(CHARINDEX(' ',#name))) AS f(Mid)
) AS f(FName,LName)
CROSS APPLY (VALUES (CHARINDEX(f.FName,t.CustomerName), CHARINDEX(f.LName,t.CustomerName))) AS pos(F,L)
WHERE (#partialmatch = 0 AND pos.F*pos.L > 0)
OR (#partialmatch = 1 AND pos.F+pos.L > 0);
When #partialmatch = 1 you get:
CustomerName FNMatch LNMatch
------------------------------ ----------- -----------
Johny Smith 1 1
Mr. Smithers 0 1
Johnathan Smithe 1 1
Setting #partialMatch to 0 will exclude "Mr. Smithers".

Splitting a Column into various columns

-- Create table dbo.FullNameTest
IF OBJECT_ID('FullNameTest') IS NOT NULL BEGIN
DROP TABLE dbo.FullNameTest;
END;
CREATE TABLE FullNameTest
(
ID INT Not Null IDENTITY
, FullName NVARCHAR(80) Not Null
, CONSTRAINT PK_ID PRIMARY KEY CLUSTERED(ID)
);
GO
INSERT INTO FullNameTest
VALUES('Mr Hog Finn Gad'), ('Grace Bruce'), ('Dr.Paul'), ('Master Clark James'), ('Mrs.Rignald')
SELECT * FROM FullNameTest
Rules
Treat entries (Mrs, Mr, Miss etc) as Titles and ignore the dot after title
When we have title and a single name, return it as Title, LastName
When we have title and two names return it as Title, FirstName and LastName
When we have title and three names, return Title, FirstName and treat the rest as LastName
When we have no title, reurn data as FirstName, LastName
No function required.
So we can get values in this form:
Title FirstName LastName
------------------------------
Mr Hog Finn Gad
Null Grace Bruce
Dr Null Paul
Master Clark James
Mrs Null Rignald
Thank you
EDIT: ***To test the results yourself, use the following to create your full name table, then run the code to create the title table, and then run the solution code below that. The results are exactly what you asked for.
CREATE TABLE #FullNameTest
(
ID INT Not Null IDENTITY
, FullName NVARCHAR(80) Not Null
);
GO
INSERT INTO #FullNameTest
VALUES('Mr Hog Finn Gad'), ('Grace Bruce'), ('Dr.Paul'), ('Master Clark James'), ('Mrs.Rignald')
This may not work in every situation, and it requires the use of a reference table with Titles, but it should get you started. Again, I would suggest putting in some data validation restraints so you don't get unexpected input. If that's not possible, then run some algorithms to clean your data first. It gets messy when you try to do stuff like this in SQL.
Create a title reference table with title and length of title.
create table #title (Title varchar(15), Long as len(Title))
insert into #title (Title) values
('Mr'),
('Mister'),
('Ms'),
('Miss'),
('Mrs'),
('Misses'),
('Dr'),
('Doctor'),
('Senator'),
('Officer'),
('Master')
Your table looks like this:
Mr 2
Mister 6
Ms 2
Miss 4
Mrs 3
Misses 6
Dr 2
Doctor 6
Senator 7
Officer 7
Master 6
Then you've got this monstrous block of code that attempts to account for every case of input you may have. AGAIN - Try to clean your data first if possible. Otherwise you will have to modify this code every time you receive something unexpected.
select y.RawName
, y.Title
, y.FName
, case when RawName like '%'+y.Title+'% %'+y.FName+'%' then RIGHT(RawName, len(RawName)-LEN(y.Title+'% %'+y.FName+' ')+1)
when Title is not null then RIGHT(RawName, len(RawName)-len(title))
when Title is null and RawName like '% %' then RIGHT(RawName, len(RawName)-len(Fname)-1)
end LName
from (
select RawName
, Title
, case when right(RawName, len(RawName)-len(title)-1) like '% %'
then left(right(RawName, len(RawName)-len(title)-1), CHARINDEX(' ', right(RawName, len(RawName)-len(title)-1),1))
when RawName like '% %'
then left(RawName, CHARINDEX(' ', RawName, 1))
end FName
from (
select
replace(f.FullName, '.', '') RawName
, t.title Title
, ROW_NUMBER() over(partition by f.fullname order by len(t.title) desc) row_n
from #FullNameTest f
left join #title t
on left(f.fullname, t.long) = t.title
) x
where row_n = 1
) y
Based on your samples from above, you are left with the following:
RawName Title FName LName
DrPaul Dr NULL Paul
Grace Bruce NULL Grace Bruce
Master Clark James Master Clark James
Mr Hog Finn Gad Mr Hog Finn Gad
MrsRignald Mrs NULL Rignald
If this is helps you with your problem, please accept it as the answer. :)
Below is the code I eventually used:
SELECT Title,
CASE
WHEN CHARINDEX(' ',PARTNAME) = 0 THEN NULL
ELSE LEFT(PARTNAME, CHARINDEX(' ',PARTNAME))
END AS [FirstName],
CASE WHEN CHARINDEX(' ',PARTNAME) = 0 THEN PARTNAME
ELSE RIGHT(PARTNAME, LEN(PARTNAME) - CHARINDEX(' ', PARTNAME))
END AS [LastName]
FROM
(
SELECT TITLE, LTRIM(REPLACE(REPLACE(FULLNAME,ISNULL(TITLE,''),''),'.','')) AS PARTNAME
FROM (
SELECT CASE
WHEN CHARINDEX('DR ',FullName) = 1 THEN 'Dr'
WHEN CHARINDEX('DR.',FullName) = 1 THEN 'Dr'
WHEN CHARINDEX('MR ',FullName) = 1 THEN 'Mr'
WHEN CHARINDEX('MR.',FullName) = 1 THEN 'Mr'
WHEN CHARINDEX('MRS ',FullName) = 1 THEN 'Mrs'
WHEN CHARINDEX('MRS.',FullName) = 1 THEN 'Mrs'
WHEN CHARINDEX('MISS ',FullName) = 1 THEN 'Miss'
WHEN CHARINDEX('MISS.',FullName) = 1 THEN 'Miss'
WHEN CHARINDEX('MASTER ',FullName) = 1 THEN 'Master'
WHEN CHARINDEX('MASTER.',FullName) = 1 THEN 'Master'
ELSE NULL
END AS [Title], FullName
FROM FullNameTest
) B
) C
Thanks so much for your help

Pivoting rows into columns in SQL Server

I have a set of data that looks like this:
Before
FirstName LastName Field1 Field2 Field3 ... Field27
--------- -------- ------ ------ ------ -------
Mark Smith A B C D
John Baptist X T Y G
Tom Dumm R B B U
However, I'd like the data to look like this:
After
FirstName LastName Field Value
--------- -------- ----- -----
Mark Smith 1 A
Mark Smith 2 B
Mark Smith 3 C
Mark Smith 4 D
John Baptist 1 X
John Baptist 2 T
John Baptist 3 Y
John Baptist 4 G
Tom Dumm 1 R
Tom Dumm 2 B
Tom Dumm 3 B
Tom Dumm 4 U
I have looked at the PIVOT function. It may work. I am not too sure. I couldn't make sense of how to use it. But, I am not sure that the pivot could place a '4' in the 'Field' column. From my understanding, the PIVOT function would simply transpose the values of Field1...Field27 into the 'Value' column.
I have also considered iterating over the table with a Cursor and then looping over the field columns, and then INSERTing into another table the 'Field's and 'Value's. However, I know this will impact performance since it's a serial-based operation.
Any help would be greatly appreciated! As you can tell, I'm quite new to T-SQL (or SQL in general) and SQL Server.
You can perform with an UNPIVOT. There are two ways to do this:
1) In a Static Unpivot you would hard-code your Field columns in your query.
select firstname
, lastname
, replace(field, 'field', '') as field
, value
from test
unpivot
(
value
for field in (field1, field2, field3, field27)
) u
See a SQL Fiddle for a working demo.
2) Or you could use a Dynamic Unpivot which will get the list of items to PIVOT when you run the SQL. The Dynamic is great if you have a large amount of fields that you will be unpivoting.
create table mytest
(
firstname varchar(5),
lastname varchar(10),
field1 varchar(1),
field2 varchar(1),
field3 varchar(1),
field27 varchar(1)
)
insert into mytest values('Mark', 'Smith', 'A', 'B', 'C', 'D')
insert into mytest values('John', 'Baptist', 'X', 'T', 'Y', 'G')
insert into mytest values('Tom', 'Dumm', 'R', 'B', 'B', 'U')
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX);
select #cols = stuff((select ','+quotename(C.name)
from sys.columns as C
where C.object_id = object_id('mytest') and
C.name like 'Field%'
for xml path('')), 1, 1, '')
set #query = 'SELECT firstname, lastname, replace(field, ''field'', '''') as field, value
from mytest
unpivot
(
value
for field in (' + #cols + ')
) p '
execute(#query)
drop table mytest
Both will produce the same results.
If you want to do it query than quick and dirty way will be to create Union
Select FirstName,LastName,1,Field1
from table
UNION ALL
Select FirstName,LastName,2,Field2
from table
.
.
And similar for all field cols
Rather than using pivot, use unpivot like this:
select firstname, lastname, substring(field,6,2) as field, value
from <yourtablename>
unpivot(value for field in (field1,field2,field3,field4,field5,field6,field7,field8,field9,field10,field11,field12,field13,field14,field15,field16,field17,field18,field19,field20,field21,field22,field23,field24,field25,field26,field27,field1,field2,field3,field4,field5,field6,field7,field8,field9,field10,field11,field12,field13,field14,field15,field16,field17,field18,field19,field20,field21,field22,field23,field24,field25,field26,field27)) as unpvt;

How to create a table of name-value pairs in SQL

Using SQL, how do I convert a single row table like this...
Firstname Surname Address1 City Country
--------- ------- --------------- ------ -------
Bob Smith 101 High Street London UK
...to a table of name-value pairs like this:
Name Value
--------- -------
Firstname Bob
Surname Smith
Address1 101 High Street
City London
Country UK
This script will create the original table:
create table #OriginalTable (Firstname varchar(10), Surname varchar(10),
Address1 varchar(50), City varchar(10), Country varchar(10))
insert into #OriginalTable
select
'Bob' Firstname,
'Smith' Surname,
'101 High Street' Address1,
'London' City,
'UK' Country
I'm after a generic solution that does not depend on the columns names always being what they are in the example.
EDIT:
I'm using SQL Server 2005.
The solution I'm after is the SQL script to convert this data into a name-value pair table
ANSWER:
Using the answer that I accepted as the answer, this is what I've used:
select
result.Name,
result.Value
from
(select
convert(sql_variant,FirstName) AS FirstName,
convert(sql_variant,Surname) AS Surname,
convert(sql_variant,Address1) AS Address1,
convert(sql_variant,City) AS City,
convert(sql_variant,Country) AS Country
from #OriginalTable) OriginalTable
UNPIVOT (Value For Name In (Firstname, Surname, Address1, City, Country)) as result
Basically you have two problems - to UNPIVOT, the data types have to be conformed. The other problem is that the number of columns is unknown. You want to reach something of the form:
WITH conformed
AS ( SELECT CONVERT(VARCHAR(255), [Firstname]) AS [Firstname],
CONVERT(VARCHAR(255), [Surname]) AS [Surname],
CONVERT(VARCHAR(255), [Address1]) AS [Address1],
CONVERT(VARCHAR(255), [City]) AS [City],
CONVERT(VARCHAR(255), [Country]) AS [Country]
FROM so1526080
)
SELECT ColumnKey,
ColumnValue
FROM conformed UNPIVOT ( ColumnValue FOR ColumnKey IN ( [Firstname], [Surname], [Address1], [City], [Country] ) ) AS unpvt
So using a dynamic SQL PIVOT using metadata (you might need to fix this up with TABLE_SCHEMA, etc):
DECLARE #table_name AS SYSNAME
SET #table_name = 'so1526080'
DECLARE #conform_data_type AS VARCHAR(25)
SET #conform_data_type = 'VARCHAR(255)'
DECLARE #column_list AS VARCHAR(MAX)
DECLARE #conform_list AS VARCHAR(MAX)
SELECT #conform_list = COALESCE(#conform_list + ', ', '') + 'CONVERT('
+ #conform_data_type + ', ' + QUOTENAME(COLUMN_NAME) + ') AS '
+ QUOTENAME(COLUMN_NAME),
#column_list = COALESCE(#column_list + ', ', '')
+ QUOTENAME(COLUMN_NAME)
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = #table_name
DECLARE #template AS VARCHAR(MAX)
SET #template = '
WITH conformed
AS ( SELECT {#conform_list}
FROM {#table_name}
)
SELECT ColumnKey,
ColumnValue
FROM conformed UNPIVOT ( ColumnValue FOR ColumnKey IN ( {#column_list} ) ) AS unpvt
'
DECLARE #sql AS VARCHAR(MAX)
SET #sql = REPLACE(REPLACE(REPLACE(#template, '{#conform_list}', #conform_list),
'{#column_list}', #column_list), '{#table_name}',
#table_name)
PRINT #sql
EXEC ( #sql
)
This is MS SQL Server solution using JSON. You don't need to specify the column names and data types. All you need to do is to specify the table name and valid where condition on line 3.
DECLARE #TEMP TABLE (JSONHOLDER NVARCHAR(MAX));
INSERT INTO #TEMP
SELECT (SELECT * FROM YOURTABLE WHERE YOURID = 2589 FOR JSON AUTO) AS JSONHOLDER
SELECT [KEY] as ColumnName, [VALUE] as ColumnValue FROM (SELECT [KEY] AS OLDKEY, [VALUE] AS OLDVALUE FROM #TEMP A
CROSS APPLY OPENJSON(A.JSONHOLDER)) B CROSS APPLY OPENJSON(B.OLDVALUE)
Not that it be a complete solution, but is a brainstorm idea, what if you cross join information_schema.columns with your table?
SELECT column_name, OriginalTable.*
FROM information_schema.columns
CROSS JOIN OriginalTable
WHERE table_name = 'OriginalTable'
AND /* PRIMARY KEY FILTER HERE*/
Often it is most effective to pivot in the application using application code. Pivoting does not tend to be a database's strong point.
Use two tables. One table as a table of 'keys' and the main table contains an id to the keys table, together with a value.
Then, you can add stuff like client_id or whatever to the main table as well and set a unique key on client_id and key_id.
This sounds like the kind of things the PIVOT clause can do in SQL Server since 2005 (look for the first example), but you don't mention which database engine you use.

Grouping runs of data

SQL Experts,
Is there an efficient way to group runs of data together using SQL?
Or is it going to be more efficient to process the data in code.
For example if I have the following data:
ID|Name
01|Harry Johns
02|Adam Taylor
03|John Smith
04|John Smith
05|Bill Manning
06|John Smith
I need to display this:
Harry Johns
Adam Taylor
John Smith (2)
Bill Manning
John Smith
#Matt: Sorry I had trouble formatting the data using an embedded html table it worked in the preview but not in the final display.
Try this:
select n.name,
(select count(*)
from myTable n1
where n1.name = n.name and n1.id >= n.id and (n1.id <=
(
select isnull(min(nn.id), (select max(id) + 1 from myTable))
from myTable nn
where nn.id > n.id and nn.name <> n.name
)
))
from myTable n
where not exists (
select 1
from myTable n3
where n3.name = n.name and n3.id < n.id and n3.id > (
select isnull(max(n4.id), (select min(id) - 1 from myTable))
from myTable n4
where n4.id < n.id and n4.name <> n.name
)
)
I think that'll do what you want. Bit of a kludge though.
Phew! After a few edits I think I have all the edge cases sorted out.
I hate cursors with a passion... but here's a dodgy cursor version...
Declare #NewName Varchar(50)
Declare #OldName Varchar(50)
Declare #CountNum int
Set #CountNum = 0
DECLARE nameCursor CURSOR FOR
SELECT Name
FROM NameTest
OPEN nameCursor
FETCH NEXT FROM nameCursor INTO #NewName
WHILE ##FETCH_STATUS = 0
BEGIN
if #OldName <> #NewName
BEGIN
Print #OldName + ' (' + Cast(#CountNum as Varchar(50)) + ')'
Set #CountNum = 0
END
SELECT #OldName = #NewName
FETCH NEXT FROM nameCursor INTO #NewName
Set #CountNum = #CountNum + 1
END
Print #OldName + ' (' + Cast(#CountNum as Varchar(50)) + ')'
CLOSE nameCursor
DEALLOCATE nameCursor
My solution just for kicks (this was a fun exercise), no cursors, no iterations, but i do have a helper field
-- Setup test table
DECLARE #names TABLE (
id INT IDENTITY(1,1),
name NVARCHAR(25) NOT NULL,
grp UNIQUEIDENTIFIER NULL
)
INSERT #names (name)
SELECT 'Harry Johns' UNION ALL
SELECT 'Adam Taylor' UNION ALL
SELECT 'John Smith' UNION ALL
SELECT 'John Smith' UNION ALL
SELECT 'Bill Manning' UNION ALL
SELECT 'Bill Manning' UNION ALL
SELECT 'Bill Manning' UNION ALL
SELECT 'John Smith' UNION ALL
SELECT 'Bill Manning'
-- Set the first id's group to a newid()
UPDATE n
SET grp = newid()
FROM #names n
WHERE n.id = (SELECT MIN(id) FROM #names)
-- Set the group to a newid() if the name does not equal the previous
UPDATE n
SET grp = newid()
FROM #names n
INNER JOIN #names b
ON (n.ID - 1) = b.ID
AND ISNULL(b.Name, '') <> n.Name
-- Set groups that are null to the previous group
-- Keep on doing this until all groups have been set
WHILE (EXISTS(SELECT 1 FROM #names WHERE grp IS NULL))
BEGIN
UPDATE n
SET grp = b.grp
FROM #names n
INNER JOIN #names b
ON (n.ID - 1) = b.ID
AND n.grp IS NULL
END
-- Final output
SELECT MIN(id) AS id_start,
MAX(id) AS id_end,
name,
count(1) AS consecutive
FROM #names
GROUP BY grp,
name
ORDER BY id_start
/*
Results:
id_start id_end name consecutive
1 1 Harry Johns 1
2 2 Adam Taylor 1
3 4 John Smith 2
5 7 Bill Manning 3
8 8 John Smith 1
9 9 Bill Manning 1
*/
Well, this:
select Name, count(Id)
from MyTable
group by Name
will give you this:
Harry Johns, 1
Adam Taylor, 1
John Smith, 2
Bill Manning, 1
and this (MS SQL syntax):
select Name +
case when ( count(Id) > 1 )
then ' ('+cast(count(Id) as varchar)+')'
else ''
end
from MyTable
group by Name
will give you this:
Harry Johns
Adam Taylor
John Smith (2)
Bill Manning
Did you actually want that other John Smith on the end of your results?
EDIT: Oh I see, you want consecutive runs grouped. In that case, I'd say you need a cursor or to do it in your program code.
How about this:
declare #tmp table (Id int, Nm varchar(50));
insert #tmp select 1, 'Harry Johns';
insert #tmp select 2, 'Adam Taylor';
insert #tmp select 3, 'John Smith';
insert #tmp select 4, 'John Smith';
insert #tmp select 5, 'Bill Manning';
insert #tmp select 6, 'John Smith';
select * from #tmp order by Id;
select Nm, count(1) from
(
select Id, Nm,
case when exists (
select 1 from #tmp t2
where t2.Nm=t1.Nm
and (t2.Id = t1.Id + 1 or t2.Id = t1.Id - 1))
then 1 else 0 end as Run
from #tmp t1
) truns group by Nm, Run
[Edit] That can be shortened a bit
select Nm, count(1) from (select Id, Nm, case when exists (
select 1 from #tmp t2 where t2.Nm=t1.Nm
and abs(t2.Id-t1.Id)=1) then 1 else 0 end as Run
from #tmp t1) t group by Nm, Run
For this particular case, all you need to do is group by the name and ask for the count, like this:
select Name, count(*)
from MyTable
group by Name
That'll get you the count for each name as a second column.
You can get it all as one column by concatenating like this:
select Name + ' (' + cast(count(*) as varchar) + ')'
from MyTable
group by Name