SQL - Group by Elements of Comma Delineation - sql

How can I group by a comma delineated list within a row?
Situation:
I have a view that shows me information on support tickets. Each ticket is assigned to an indefinite number of resources. It might have one name in the resource list, it might have 5.
I would like to aggregate by individual names, so:
| Ticket ID | resource list
+-----------+----------
| 1 | Smith, Fred, Joe
| 2 | Fred
| 3 | Smith, Joe
| 4 | Joe, Fred
Would become:
| Name | # of Tickets
+-----------+----------
| Fred | 3
| Smith | 2
| Joe | 3
I did not design the database, so I am stuck with this awkward resource list column.
I've tried something like this:
SELECT DISTINCT resource_list
, Count(*) AS '# of Tickets'
FROM IEG.vServiceIEG
GROUP BY resource_list
ORDER BY '# of Tickets' DESC
...which gives me ticket counts based on particular combinations, but I'm having trouble getting this one step further to separate that out.
I also have access to a list of these individual names that I could do a join from, but I'm not sure how I would make that work. Previously in reports, I've used WHERE resource_list LIKE '%' + #tech + '%', but I'm not sure how I would iterate through this for all names.
EDIT:
This is my final query that gave me the information I was looking for:
select b.Item, Count(*) AS 'Ticket Count'
from IEG.vServiceIEG a
cross apply (Select * from dbo.Split(REPLACE(a.resource_list, ' ', ''),',')) b
Group by b.Item
order by 2 desc

Check this Post (Function Definition by Romil) for splitting strings into a table:
How to split string and insert values into table in SQL Server
Use it this way :
select b.Item, Count(*) from IEG.vServiceIEG a
cross apply (
Select * from dbo.Split (a.resource_list,',')
) b
Group by b.Item
order by 2 desc

Related

Group data on different key-value pairs in a string

I have a table LOG that contains a field NOTES. Table LOG also contains a field NrofItems. This is on Azure SQL. NOTES is a string that contains key-value pairs separated by semicolons. The order of the key-value pairs is random. The keys are known.
Example of three records:
NOTES | NrofItems
"customer=customer1;code=blablabla;application=SomeApplication" | 23
"code=adfadfadf;customer=customer99;application=AlsoApplication" | 33
"code=xyzxyzxyz;application=AlsoApplication;customer=customer1" | 13
"code=blablabla;customer=customer1;application=SomeApplication" | 2
I need to sum the value of NrofItems per customer per application per... like this:
customer1 | blablabla | SomeApplication | 25
customer1 | xyzxyzxyz | AlsoApplication | 13
customer99 | adfadfadf | AlsoApplication | 33
I would like to be able to use one or more of the key-value pairs to make groupings.
I do know how to to it for one grouping but how for more?
See this URL to see how to do it for one grouping: Group By on part of string
Hmmm. For this, I'm thinking that extracting the customer and application separately is a convenient way to go:
select c.customer, a.application, sum(nrofitems)
from t outer apply
(select top (1) stuff(s.value, 1, 10, '') as customer
from string_split(t.notes, ';') s
where s.value like 'customer=%'
) c outer apply
(select top (1) stuff(s.value, 1, 12, '') as application
from string_split(t.notes, ';') s
where s.value like 'application=%'
) a
group by c.customer, a.application;
Here is a db<>fiddle.

Novice seeking help, Max Aggregate not returning expected results

I'm still very new to MS-SQL. I have a simple table and query that that is getting the best of me. I know it will something fundamental I'm overlooking.
I've changed the field names but the idea is the same.
So the idea is that every time someone signs up they get a RegID, Name, and Team. The names are unique, so for below yes John changed teams. And that's my trouble.
Football Table
+------------+----------+---------+
| Max_RegID | Name | Team |
+------------+----------+---------+
| 100 | John | Red |
| 101 | Bill | Blue |
| 102 | Tom | Green |
| 103 | John | Green |
+------------+----------+---------+
With the query at the bottom using the Max_RegID, I was expecting to get back only one record.
+------------+----------+---------+
| Max_RegID | Name | Team |
+------------+----------+---------+
| 103 | John | Green |
+------------+----------+---------+
Instead I get back below, Which seems to include Max_RegID but also for each team. What am I doing wrong?
+------------+----------+---------+
| Max_RegID | Name | Team |
+------------+----------+---------+
| 100 | John | Red |
| 103 | John | Green |
+------------+----------+---------+
My Query
SELECT
Max(Football.RegID) AS Max_RegID,
Football.Name,
Football.Team
FROM
Football
GROUP BY
Football.RegID,
Football.Name,
Football.Team
EDIT* Removed the WHERE statement
The reason you're getting the results that you are is because of the way you have your GROUP BY clause structured.
When you're using any aggregate function, MAX(X), SUM(X), COUNT(X), or what have you, you're telling the SQL engine that you want the aggregate value of column X for each unique combination of the columns listed in the GROUP BY clause.
In your query as written, you're grouping by all three of the columns in the table, telling the SQL engine that each tuple is unique. Therefore the query is returning ALL of the values, and you aren't actually getting the MAX of anything at all.
What you actually want in your results is the maximum RegID for each distinct value in the Name column and also the Team that goes along with that (RegID,Name) combination.
To accomplish that you need to find the MAX(ID) for each Name in an initial data set, and then use that list of RegIDs to add the values for Name and Team in a secondary data set.
Caveat (per comments from #HABO): This is premised on the assumption that RegID is a unique number (an IDENTITY column, value from a SEQUENCE, or something of that sort). If there are duplicate values, this will fail.
The most straight forward way to accomplish that is with a sub-query. The sub-query below gets your unique RegIDs, then joins to the original table to add the other values.
SELECT
f.RegID
,f.Name
,f.Team
FROM
Football AS f
JOIN
(--The sub-query, sq, gets the list of IDs
SELECT
MAX(f2.RegID) AS Max_RegID
FROM
Football AS f2
GROUP BY
f2.Name
) AS sq
ON
sq.Max_RegID = f.RegID;
EDIT: Sorry. I just re-read the question. To get just the single record for the MAX(RegID), just take the GROUP BY out of the sub-query, and you'll just get the current maximum value, which you can use to find the values in the rest of the columns.
SELECT
f.RegID
,f.Name
,f.Team
FROM
Football AS f
JOIN
(--The sub-query, sq, now gets the MAX ID
SELECT
MAX(f2.RegID) AS Max_RegID
FROM
Football AS f2
) AS sq
ON
sq.Max_RegID = f.RegID;
Use row_number()
select * from
(SELECT
Football.RegID AS Max_RegID,
Football.Name,
Football.Team, row_number() over(partition by name order by Football.RegID desc) as rn
FROM
Football
WHERE
Football.Name = 'John')a
where rn=1
simply you can edit your query below way
SELECT *
FROM
Football f
WHERE
f.Name = 'John' and
Max_RegID = (SELECT Max(Football.Max_RegID) where Football.Name = 'John'
)
or
if sql server simply use this
select top 1 * from Football f
where f.Name = 'John'
order by Max_RegID desc
or
if mysql then
select * from Football f
where f.Name = 'John'
order by Max_RegID desc
Limit 1
You need self join :
select f1.*
from Football f inner join
Football f1
on f1.name = f.name
where f.Max_RegID = 103;
After re-visit question, the sample data suggests me subquery :
select f.*
from Football f
where name = (select top (1) f1.name
from Football f1
order by f1.Max_RegID desc
);

one to many select in sql

Well i have two tables first for employees and the second for emails, i want to see the employees with all his emails
table employees:
id | name | idE
1 | name1 | 1
2 | name2 | 2
3 | name3 | 3
table email:
id | idE | email
1 | 1 | e1#email.com
2 | 1 | e2#email.com
3 | 2 | e#email2.com
4 | 3 | e#email3.com
and i have this query:
select e.id, e.name, m.email from employees as e inner join email as m on p.idE = m.idE
and this is the result:
id | name | email
1 | name1 | e1#email.com
1 | name1 | e2#email.com
2 | name2 | e#email2.com
3 | name3 | e#email3.com
i dont need employee 1 twice i just need both emails in the same row
Hi guys i modified my query to:
select em.name,
STUFF(
(select ',' + e.Email from tbl_Employees as m inner join tbl_Email as e on m.idE = e.idE for xml path(''))
,1,1,'') as emails
from tbl_employees as em
but now my problem is that i get this in each column of email:
e1#email.com,e2#email.com,e#email2.com,e#email3.com,
how can i do it to get only the emails of each employee?
instead of having every single email in each column
for example:
name | email
name1 | e1#email.com,e2#email.com,
name2 | e#email2.com,
name3 | e#email3.com,
pd: sorry if i have some mistakes im still learning english
Although what you're asking for is possible, and I'm sure you'll get several answers, your question is a good example of be careful what you wish for ! You're asking the DBMS to supply some presentation-level functionality. You're usually better off letting SQL find the data you need, and handling presentation issues in your application.
SELECT returns a table of information, and the value of each column in a row is normally "atomic" in the sense that it holds a single value that SQL can compare to other values. The tables in your database have the same property if they adhere to first normal form.
One of the virtues of SQL is that "everything is a table": you can stack SELECTs one atop the other.
select * from (select a, b from T where a > b) as Z
Each inner one produces another table that the next outer one can act on.
Functions like group_concat (as an example) produce a non-atomic column. It's only a short step from there to code like:
select * from ( select group_concat(a) as a_list from T ) as Z
where a_list like '%Henry%'
The programmer is forced into search strings instead of direct comparison. The query becomes less precise and thus error-prone.
The fail-safe approach, in your example, is to read the results one by one from the DBMS, and concatenate a per-user address list by watching the idE column. It will keep your query simpler, and if you later decide to present them another way, you can do that without diving into the database logic.
this is my query after the changes
select em.name,
STUFF(
(select ',' + e.Email from tbl_Employees as m inner join
tbl_Email as e on m.idE = e.idE
where em.idE = e.idE
for xml path('')
) ,1,1,'') as emails
from tbl_employees as em
and the results of this query
name | emails
name1 | e1#email.com,e2#email.com
name2 | e#email2.com
name3 | e#email3.com
thank you for the answers!

How do I return rows in groups by certain values?

I want my query to return the rows of a table in groups where a column contains specific values. After I got the rows ordered in the groups I want to be able to order them by name.
Example Table
- Id - Name - Group
- 1 George Group_2_1
- 2 Alfred Group_2_2
- 3 Eric Group_3
- 4 Mary Group_1_2
- 5 Jon Group_1_1
I want them ordered by their group and after that ordered by their name
- Id - Name - Group
- 1 Jon Group_1_1
- 2 Mary Group_1_2
- 3 Alfred Group_2_2
- 4 George Group_2_1
- 5 Eric Group_3
I found this SQL-Query-Snippet
ORDER BY CASE WHEN Group LIKE '%Group_1%' THEN 1 ELSE 2 END, Group
but it is not enough. The result is only grouped by the first group (obviously) but I can't extend it to order the second group because it is in the same column.
Please don't get confused by the example.
I just want to be able to group certain rows and put them in front of the results. I want a result that has all rows containing group 1 in the top, containing group 2 in the middle and containing group 3 in the bottom.
The values are not "Group_1_1" or something like that. They are just some strings and I want certain strings to be always in the first row (group 1) and some always below group 1
The problem here seems to be that some of your group names have an extra underscore, otherwise you could just order by the Group and all would be good. You could probably do something like this to work around this?
WITH Data AS (
SELECT 'Group1_1' AS Value
UNION
SELECT 'Group_3_2' AS Value
UNION
SELECT 'Group_2_2' AS Value
UNION
SELECT 'Group_3_1' AS Value
)
SELECT * FROM Data ORDER BY CASE WHEN Value LIKE 'Group_%' THEN SUBSTRING(Value, 7, 10) ELSE SUBSTRING(Value, 6, 10) END;
Results:
Value
Group1_1
Group_2_2
Group_3_1
Group_3_2
---- EDIT ----
Okay, seeing as your example isn't really an "example" it sounds like you are going to need a really, REALLY long case statement. You could do something like this (using the original Group_1_1, Group_2_2 codes) that would extend to different values. The key is that a CASE statement works from left to right and a value is assigned to the first case that matches:
ORDER BY
CASE
WHEN [Group] = 'Group_1_1' THEN 1
WHEN [Group] = 'Group_1_2' THEN 2
WHEN [Group] LIKE 'Group_1_%' THEN 3
WHEN [Group] = 'Group_2_1' THEN 4
WHEN [Group] = 'Group_2_2' THEN 5
WHEN [Group] LIKE 'Group_2_%' THEN 6
etc.
END;
Obviously that's very generic and depends on what the actual values are in your database.
Edits for mssql
If there is ANY instance of 3 underscores then the following simply won't work. However if there is the possibility of Group_12_6 or Group_21_1 then this approach may be worth trying.
It removes Group_ or Group from the string, leaving 1_1 or 12_6 or 21_1 then it replaces the remaining underscore with . giving 1.1 or 12.6 or 21.1 and casts this to decimal.
All utterly dependent of the consistency of those group names.
SELECT
id
, name
, [Group]
FROM YourData
ORDER BY
CAST(REPLACE(REPLACE(REPLACE([Group], 'Group_', ''), 'Group', ''), '_', '.') AS decimal(12,3))
, name
I'm really hoping you do not have a column called [Group] but if you do it has to be referenced as [Group] or "Group". Test result:
| ID | NAME | GROUP |
|----|--------|-----------|
| 1 | Jon | Group_1_1 |
| 2 | Mary | Group_1_2 |
| 4 | George | Group_2_1 |
| 3 | Alfred | Group_2_2 |
| 5 | Eric | Group_3 |
see http://sqlfiddle.com/#!3/e95b07/1

How to join (without concatenating) two result sets of two fields together as one long ‘list’

What SQL Code do I require to perform the following? :
I have one table (lets arbitrarily call the table 'Names'):
ID | Name1 | Name2
---+--------+-------
1 | Fred | Jack
2 | Jack | Jim
3 | Jill | Fred
4 | Jim | Jack
etc
What I'd like is to produce is a single list of Name1 and Name2 (I don't even care about Grouping or Ordering) as such, but I would like to keep the original 'ID' association with the name:
ID | Names
---+------
1 | Fred
1 | Jack
2 | Jill
2 | Jim
3 | Jack
3 | Jim
4 | Fred
4 | Jack
Why do I want to do this? Because it looks easy and as a SQL coder I should probably be able to perform this task, but I can't figure out a solution that will create this output. Further more I've only manage to find people with the desire to concatenate the fields, which is a simple task, but I'm not interested in concatenation.
Additional Question: Would the SQL query be vastly different if Name1 field was in a different table to Name2? (if it is different, what would it look like?)
Additional Question: Would the SQL query be simpler if we didn't care about the ID field? If so, what would that look like.
You can use this form to include the id, and give you a specific ordering by ID.
SELECT n.id, n.name1 FROM names n
UNION
SELECT m.id, m.name2 from names m
ORDER BY id ASC;
If it were in a different table, the use of UNION doesn't have to change, since we're bringing the results from the table together, and ordering them by ID. This doesn't mean that the data is related, though.
SELECT n.id, n.name FROM name_one n
UNION
SELECT m.id, m.name from name_two m
ORDER BY id ASC;
If we didn't care about the ID field, it would be ever so lightly simpler - it's just selecting one column at that point.
SELECT ID, Name1 as Names FROM person
UNION
SELECT ID, Name2 as Names FROM person
?
. . What you mean by "without concatenating"?