Grouping by an alias - sql

SELECT COALESCE (rsu.last_name + ', ' + rsu.first_name + ' ' + rsu.middle_name + '.', rsu.last_name + ', ' + rsu.first_name) as student_name, rsu.day_id
FROM roster_school_unattended rsu
GROUP BY student_name
ORDER BY rsu.day_id
does not work. What's the most elegant workaround?
EDIT: The result-set should have something like this
Muster, Hans | 2011-11-01
Muster, Hans | 2011-11-02
Williams, Clay | 2011-10-01
Williams, Clay | 2011-10-02
First the name is grouped by, then for each name there is a sorting of dates.

Doing a subselect would let you avoid having to type it twice:
select t.student_name, t.day_id
from (
select COALESCE (rsu.last_name + ', ' + rsu.first_name + ' ' + rsu.middle_name + '.', rsu.last_name + ', ' + rsu.first_name) as student_name,
rsu.day_id
from roster_school_unattended rsu ) t
group by t.student_name
order by t.day_id
But you've still got a problem with the day_id - it's not included in your grouping clause, so you won't be able to select it without using an aggregate (such as MAX).

You can use a subquery:
select student_name, day_id
from (SELECT COALESCE (rsu.last_name + ', ' + rsu.first_name + ' ' + rsu.middle_name + '.', rsu.last_name + ', ' + rsu.first_name) as student_name, rsu.day_id
FROM roster_school_unattended rsu
) as rows
GROUP BY student_name
ORDER BY day_id

EDIT: Whole thing replaced as the recent edit to the question shows that GROUP BY isn't needed...
Without any changes for 'elegance'...
SELECT
rsu.last_name + ', ' + rsu.first_name + COALESCE (' ' + rsu.middle_name + '.', '') as student_name,
rsu.day_id
FROM
roster_school_unattended AS [rsu]
ORDER BY
rsu.last_name + ', ' + rsu.first_name + COALESCE (' ' + rsu.middle_name + '.', ''),
rsu.day_id
Possible change for 'elegance'...
WITH formatted_rsu AS
(
SELECT rsu.last_name + ', ' + rsu.first_name + COALESCE (' ' + rsu.middle_name + '.', '') as student_name, rsu.day_id
FROM roster_school_unattended AS [rsu]
)
SELECT student_name, day_id
FROM formatted_rus
ORDER BY student_name, day_id
Another possible using APPLY...
SELECT formatted_rsu.student_name, rsu.day_id
FROM roster_school_unattended AS [rsu]
CROSS APPLY (SELECT rsu.last_name + ', ' + rsu.first_name + COALESCE (' ' + rsu.middle_name + '.', '') as student_name) AS [formatted_rsu]
ORDER BY formatted_rsu.student_name, rsu.day_id

I would go with:
SELECT
rsu.last_name
+ ', ' + rsu.first_name
+ COALESCE ( ' ' + rsu.middle_name + '.'
, ''
)
AS student_name
, rsu.day_id
FROM
roster_school_unattended AS rsu
ORDER BY
rsu.last_name
, rsu.first_name
, rsu.middle_name
, rsu.day_id

CROSS APPLY is your friend for aliasing non-windowed expressions:
SELECT this.student_name, MAX(rsu.day_id) AS day_id
FROM roster_school_unattended rsu
CROSS APPLY (
SELECT COALESCE (
rsu.last_name+', '+rsu.first_name+' '+rsu.middle_name+'.'
, rsu.last_name+', '+rsu.first_name
) AS student_name
) this
GROUP BY this.student_name
ORDER BY this.student_name, MAX(rsu.day_id)
(assumes SQL2005 and above).

Related

How to get the first letter of each word in SQL

I tried to run this query to get the initial letter of each word, and it worked for strings of 4 words, yet if the string has only two words, it duplicates the second word's initial.
select
substring(column_name, 1, 1) +
case
when 0 <> charindex(' ', column_name) + 1
then substring(column_name, charindex(' ',column_name) + 1, 1)
else ''
end +
case
when 0 <> charindex(' ', column_name, charindex(' ', column_name) + 1)
then substring(column_name, charindex(' ', column_name, charindex(' ', column_name) + 1) + 1, 1)
else ''
end +
case
when 0 <> charindex(' ', column_name, charindex(' ', column_name, charindex(' ', column_name) + 1) + 1)
then substring(column_name, charindex(' ', column_name, charindex(' ', column_name, charindex(' ', column_name) + 1) + 1) + 1, 1)
else ''
end
from table_name
You didn't specify which RDBMS you are using. This should work in SQL Server:
drop table if exists table_name
create table table_name (
column_name varchar(255)
)
insert table_name
values ('See Jane')
, ('See Jane run')
, ('See Jane run and jump over the lazy dog.')
select stuff((SELECT '' + t2.fc
from (
select left(str.value, 1) fc
, charindex(' ' + str.value + ' ', ' ' + t.column_name + ' ') idx
from string_split(t.column_name, ' ') str
) t2
order by t2.idx
FOR XML PATH('')
), 1, 0, '') as FirstChars
from table_name t
The idx column is used to order the ouptut because string_split does not promise to return the results in any particular order. Thanks to Aaron Bertrand - https://dba.stackexchange.com/questions/207274/string-split-and-ordered-results
Given the use of charindex in your question, I'm assuming you are using SQL Server. The CTE generates a tall view of your data using string_split function, with each letter on it's own row. We then select from it and group by id, and apply the string_agg function to place back into a single row.
Password guessing?
create table my_data (
id integer,
comments varchar(50)
);
insert into my_data (id, comments) values
(1, 'Thank goodness its friday'),
(2, 'I want 2 scoops of ice cream');
select * from my_data;
id
comments
1
Thank goodness its friday
2
I want 2 scoops of ice cream
with cte (id, first_char) as (
select id, substring(ss.value, 1, 1) as first_char
from my_data
cross apply string_split(comments, ' ')ss
)
select t.id,
string_agg(t.first_char, ',') as letters_delimited,
string_agg(t.first_char, '') as letters_not_delimited
from cte t
group by t.id
id
letters_delimited
letters_not_delimited
1
T,g,i,f
Tgif
2
I,w,2,s,o,i,c
Iw2soic
fiddle here
You can extend your approach with recursion
WITH cte_name AS (
select CONVERT(nvarchar(max), substring(column_name, 1, 1)) conc,
column_name n,
charindex(' ', column_name, 0) pos
from table_name
UNION ALL
select conc + substring(n, pos + 1, 1) as conc,
n,
charindex(' ', n, pos + 1) pos
from cte_name where pos > 0
)
SELECT *
FROM cte_name
where pos = 0;

I'm having trouble separating the first name from the middle name column in SQL Server 2017

I created this syntax to separate first name, middle name and last name from a column called invertornames. Just to note that the investor names are in arabic and their middle names are more than 3 words. It worked fine but the first name is also being included in the middle name as you can see below in the image
This is the query I wrote:
SELECT
SUBSTRING(investor_name, CHARINDEX(', ', investor_name) + 2, CASE WHEN CHARINDEX(' ', investor_name, CHARINDEX(', ', investor_name) + 2) = 0 THEN LEN(investor_name) + 1 ELSE CHARINDEX(' ', investor_name, CHARINDEX(', ', investor_name) + 2) END - CHARINDEX(', ', investor_name) - 2)AS FirstName,
RTRIM(LTRIM(REPLACE(REPLACE(investor_name,SUBSTRING(investor_name , 1, CHARINDEX(' ', investor_name) -1),''),REVERSE( LEFT( REVERSE(investor_name), CHARINDEX(' ', REVERSE(investor_name))-1 ) ),''))) AS MiddleName,
RIGHT(investor_name, CHARINDEX(' ', REVERSE(investor_name))) AS LastName
FROM
investornames
If you need any data to try it please let me know.
You may try this. I consider that First word is considered as Firstname, second word is considered as MiddleName and remaining word will considered as LastName.
For the case of arabic names. Software will not automatically detect that for which name calculation start from the front and for which it is taken in reverse. So I guess you need to maintain a flag for same.
In case of arabic name in the portion of cte use reverse to arrange them in left to right order instead of right to left order. And at the end use reverse function again to convert them into their original state.
Hope I am clear about what I am explaining. Sample code is following :-
; with cte as (
select 'Deepak kumar singh' as names
union
select 'Deep'
union
select 'deep kumar'
union
select 'Deepak kumar singh chandel')
SELECT
Reverse(ParseName(Replace(Reverse(names), ' ', '.'), 1)) As [FirstName]
, Reverse(ParseName(Replace(Reverse(names), ' ', '.'), 2)) As [MiddleName]
, case when len( Reverse(ParseName(Replace(Reverse(names), ' ', '.'), 3)))>0 then substring ( names ,
charindex ( Reverse(ParseName(Replace(Reverse(names), ' ', '.'), 2)) + ' ', names) + len(Reverse(ParseName(Replace(Reverse(names), ' ', '.'), 2)) + ' ') + 1
, len(names) - charindex ( Reverse(ParseName(Replace(Reverse(names), ' ', '.'), 2)) + ' ', names) + len(Reverse(ParseName(Replace(Reverse(names), ' ', '.'), 2)) + ' ')) else null end
As [LastName]
FROM (Select names from cte ) As [x]
Result of above query is:
FirstName MiddleName LastName
Deep NULL NULL
deep kumar NULL
Deepak kumar singh
deepak kumar singh chandel
Edit
Updated this ans check this
; with cte as (
select ' شركة عبدالمحسن عبدالعزيزالبابطين ' as names
union
select 'شركة'
union
select 'عبدالمحسن عبدالعزيز'
union
select 'البابطين')
, ct as (
select RTRIM(LTRIM(names)) as Names from cte )
SELECT
Reverse(ParseName(Replace(Reverse(names), ' ', '.'), 1)) As [FirstName]
, Reverse(ParseName(Replace(Reverse(names), ' ', '.'), 2)) As [MiddleName]
, case when len( Reverse(ParseName(Replace(Reverse(names), ' ', '.'), 3)))>0 then substring ( names ,
charindex ( Reverse(ParseName(Replace(Reverse(names), ' ', '.'), 2)) + ' ', names) + len(Reverse(ParseName(Replace(Reverse(names), ' ', '.'), 2)) + ' ') + 1
, len(names) - charindex ( Reverse(ParseName(Replace(Reverse(names), ' ', '.'), 2)) + ' ', names) + len(Reverse(ParseName(Replace(Reverse(names), ' ', '.'), 2)) + ' ')) else null end
As [LastName]
FROM (Select names from ct ) As [x]
Result
FirstName MiddleName LastName
???? ????????? ?????????????????
???? NULL NULL
???????? NULL NULL
????????? ????????? NULL
Here I am expecting on place of ? you'll get your result. BTW I've updated my query, have you tried this one. Just give one more try.

How to reduce execution time of SQL Select Query

TitemName and TshotName is the problem here
SELECT DISTINCT
tJobs.* ,
tCustomer.Name AS Customer_name ,
(SELECT tEmployee.First + ' ' + tEmployee.Last
FROM tEmployee
WHERE tEmployee.EmployeeID = tJobs.AccountExecutiveID) AS AccountExecutive,
(SELECT tEmployee.First + ' ' + tEmployee.Last
FROM tEmployee
WHERE tEmployee.EmployeeID = tJobs.AccountManagerID) AS AccountManager,
dbo.RetrunUserFavourite(tJobs.JobNumber, 33369, 'Employee') AS Favorites,
(SELECT COUNT(*)
FROM tShots
WHERE tShots.JobNumber = tJobs.JobNumber) AS shotcount,
SUBSTRING((SELECT ', ' + SKU + ', ' + Source + ', ' + ModelNumber
+ ', ' + Description
FROM tItems
WHERE tItems.CustomerID = tCustomer.CustomerID
FOR XML PATH('')), 3, 200000) titemName,
SUBSTRING((SELECT ', ' + ArtDirection + ', '
+ REPLACE(CONVERT(VARCHAR(5), AdDate, 110), '-',
'/')
FROM tShots
WHERE tShots.JobNumber = tJobs.JobNumber
FOR XML PATH('')), 3, 200000) TshotName
FROM
tJobs
INNER JOIN
tCustomer ON tCustomer.CustomerID = tJobs.CustomerID
WHERE
tCustomer.CustomerID = 68666
I strongly agree with M Ali's comments. Two points I would make. The first is not with regard to performance. But, instead of substring() use:
STUFF((SELECT ', ' + SKU + ', ' + Source + ', ' + ModelNumber+ ', ' + Description
FROM tItems
WHERE tItems.CustomerID = tCustomer.CustomerID
FOR XML PATH('')
), 1, 1, '') as titemName
That way, you don't need strange, meaningless numbers floating around the code.
Second, you may need indexes. Based on the highlighted performance problems, I would suggest:
tItems(CustomerID)
and:
tshots(JobNumber, ArtDirection, AdDate)
You have awful lot of string manipulation going on is your query, anyway a slightly improved version of your query would look something like...
Select DISTINCT
tJobs.*
, tCustomer.Name AS Customer_name
, AE.First + ' ' + AE.Last AS AccountExecutive
, AM.First + ' ' + AM.Last AS AccountManager
, dbo.RetrunUserFavourite(tJobs.JobNumber,33369,'Employee')AS Favorites
, TS.shotcount
, SUBSTRING(( SELECT ', ' + SKU + ', ' + Source + ', ' + ModelNumber+ ', ' + Description
FROM tItems
where tItems.CustomerID=tCustomer.CustomerID
FOR XML PATH('')), 3, 200000)titemName
, SUBSTRING(( SELECT ', ' + ArtDirection +', '+REPLACE(CONVERT(VARCHAR(5),AdDate,110), '-','/')
FROM tShots
where tShots.JobNumber=tJobs.JobNumber
FOR XML PATH('')), 3, 200000)TshotName
From tJobs
inner join tCustomer on tCustomer.CustomerID = tJobs.CustomerID
Left join tEmployee AE ON AE.EmployeeID = tJobs.AccountExecutiveID
Left join tEmployee AM ON AM.EmployeeID = tJobs.AccountManagerID
Left join (
SELECT JobNumber , Count(*) shotcount
FROM tShots
GROUP BY JobNumber
) TS ON TS.JobNumber = tJobs.JobNumber
WHERE tCustomer.CustomerID = 68666
A Couple of Pointers:
Having sub-queries in your select statement makes it very inefficient, because the sub-query is executed for each row returned by the outer query, a more sensible way of doing it would be to use joins.
You Also have a call to a User-Defined Scalar function dbo.RetrunUserFavourite() in your select , these scalar UDFs are also performance killers, again the same execution logic is applied here, they are also executed for each row returned by the outer query, a more sensible way would be to put the function logic/code inside a CTE and join your query to that CTE.
These comma delimited lists that you are creating on the fly for last two columns will be slow, maybe an Inline-Table-Valued function can give better performance here.

How To Use COALESCE() to Add Additional Characters

I have the following syntax on my SELECT Statement:
CONCAT(first_name, " ", COALESCE(middle_initial), " ", last_name) AS full_name
Obviously, what I get is the following:
For first_name='John' and middle_initial='A.' and last_name='Smith'
I get 'John A. Smith'
That is fine and is the desired result.
But I get an extra space for the following data (which I clearly understand why):
For first_name='John' and middle_initial='' and last_name='Smith'
I get 'John Smith'
Is there a way with COALESCE() to append " " if the condition returns a non-null value?
Thank you.
When middle_initial has '', you would want to:
SELECT CONCAT(first_name, ' ',
CASE
WHEN middle_initial is null OR middle_initial = '' then ''
ELSE CONCAT(middle_initial, ' ')
END
, last_name) AS full_name
SQLFiddle Example
COALESCE is used for checking NULL values, not handling empty strings, ''
select concat(first_name , ' ' ,
case when middle_initial is null then ''
when middle_initial = '' then ''
else concat(middle_initial, ' ') end ,
last_name)
this query asumes that first_name and last_name are not null nor empty string, in that case you can apply same logic to that fields
Try this
SELECT rtrim(Coalesce(first_name + ' ','')
+ Coalesce(nullif(middle_name,'') + ' ', '')
+ Coalesce(nullif(last_name,'') + ' ', ''))
FROM #table
SELECT rtrim(Coalesce('John' + ' ','')
+ Coalesce(nullif('A.','') + ' ', '')
+ Coalesce(nullif('Smith','') + ' ', ''))
SELECT rtrim(Coalesce('John' + ' ','')
+ Coalesce(nullif('','') + ' ', '')
+ Coalesce(nullif('Smith','') + ' ', ''))
I think best way is to do it like this. You can use such a constraction for any number of fields, variables and so on. Also it will correctly show you John or John A.
declare #Temp_Table table (first_name nvarchar(128), middle_initial nvarchar(128), last_name nvarchar(128))
insert into #Temp_Table
select 'John', null, 'Smith' union all
select 'John', 'A.', 'Smith' union all
select 'John', 'A.', null union all
select 'John', null, null
select
*,
stuff
(
isnull(' ' + nullif(first_name, ''), '') +
isnull(' ' + nullif(middle_initial, ''), '') +
isnull(' ' + nullif(last_name, ''), ''),
1, 1, ''
)
from #Temp_Table

simplifying a LEFT / REPLACE query

I have a query that is in dire need of being simplified. Here is part of the query:
SELECT
LEFT(MLIS.REQUESTOR_FIRST_NAME, CharIndex( ' ', MLIS.REQUESTOR_FIRST_NAME + ' ' ) - 1)
, CharIndex( ' ', LEFT(MLIS.REQUESTOR_FIRST_NAME, CharIndex( ' ', MLIS.REQUESTOR_FIRST_NAME + ' ' ) - 1) + ' ' ) - 1)
+REPLICATE(' ',25),25)+
LEFT(' '+REPLICATE(' ',20),20)+
LEFT(
LEFT(
LEFT(MLIS.REQUESTOR_LAST_NAME, CharIndex( ',', MLIS.REQUESTOR_LAST_NAME + ',' ) - 1)
, CharIndex( ',', LEFT(MLIS.REQUESTOR_LAST_NAME, CharIndex( ',', MLIS.REQUESTOR_LAST_NAME + ',' ) - 1) + ',' ) - 1)
the reason I am doing the replicates is because i am building a fixed length string. each column needs to be a fixed length.
in addition to the above query, for every occurrence of MLIS.REQUESTOR_FIRST_NAME and MLIS.REQUESTOR_LAST_NAME i need to do:
REPLACE(REPLACE(MLIS.REQUESTOR_FIRST_NAME,', MD',''),',MD','')
and
REPLACE(REPLACE(MLIS.REQUESTOR_LAST_NAME,', MD',''),',MD','')
How do I include these REPLACES in the query and simplify the entire thing?
thanks so much for your guidance and kind help.
select the common bits in a subquery... (you'll have a bit more)
SELECT
LEFT(REQUESTOR_FIRST_NAME, fname_idx - 1)
, CharIndex( ' ', LEFT(MLIS.REQUESTOR_FIRST_NAME, fname_idx - 1) + ' ' ) - 1)
..
FROM ( select CharIndex( ' ', MLIS.REQUESTOR_FIRST_NAME + ' ' ) fname_idx, REQUESTOR_FIRST_NAME from...
Using a subquery will help with the syntax. In addition, you can cast to a CHAR() to pad and truncate strings to a given length.
I think the following does what you want:
SELECT cast(fname as char(25)) + ' ' + cast(lname as char(25))
from (select replace(replace(LEFT(MLIS.REQUESTOR_FIRST_NAME,
CharIndex(' ', MLIS.REQUESTOR_FIRST_NAME + ' ' ) - 1
),
',MD', ''),
', MD', '') as fname,
replace(relpace(left(MLIS.REQUESTOR_LAST_NAME,
CharIndex(',', MLIS.REQUESTOR_LAST_NAME + ',' ) - 1),
CharIndex(',', LEFT(MLIS.REQUESTOR_LAST_NAME,
CharIndex( ',', MLIS.REQUESTOR_LAST_NAME + ',' ) - 1) + ','
) - 1
),
',MD', ''),
', MD', '') as lname
However, it is hard to follow the original query, and there might be a syntax error. This query is meant to give you some guidance on solving the problem. I would also put a cast after the concatenate to be sure the final string is the right length.