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.
Related
Say I have a table Schema.table with these columns
id | json_col
on the forms e.g
id=1
json_col ={"names":["John","Peter"],"ages":["31","40"]}
The lengths of names and ages are always equal but might vary from id to id (size is at least 1 but no upper limit).
How do we get an "exploded" table - a table with a row for each "names", "ages" e.g
id | names | ages
---+-------+------
1 | John | 31
1 | Peter | 41
2 | Jim | 17
3 | Foo | 2
.
.
I have tried OPENJSON and CROSS APPLY but the following gives any combination of names and ages which is not correct, thus I need to to a lot of filtering afterwards
SELECT *
FROM Schema.table
CROSS APPLY OPENJSON(Schema.table,'$.names')
CROSS APPLY OPENJSON(Schema.table,'$.ages')
Here's my suggestion
DECLARE #tbl TABLE(id INT,json_col NVARCHAR(MAX));
INSERT INTO #tbl VALUES(1,N'{"names":["John","Peter"],"ages":["31","40"]}')
,(2,N'{"names":["Jim"],"ages":["17"]}');
SELECT t.id
,B.[key] As ValueIndex
,B.[value] AS PersonNam
,JSON_VALUE(A.ages,CONCAT('$[',B.[key],']')) AS PersonAge
FROM #tbl t
CROSS APPLY OPENJSON(t.json_col)
WITH(names NVARCHAR(MAX) AS JSON
,ages NVARCHAR(MAX) AS JSON) A
CROSS APPLY OPENJSON(A.names) B;
The idea in short:
We use OPENJSON with a WITH clause to read names and ages into new json variables.
We use one more OPENJSON to "explode" the names-array
As the key is the value's position within the array, we can use JSON_VALUE() to read the corresponding age-value by its position.
One general remark: If this JSON is under your control, you should change this to an entity-centered approach (array of objects). Such a position dependant storage can be quite erronous... Try something like
{"persons":[{"name":"John","age":"31"},{"name":"Peter","age":"40"}]}
Conditional Aggregation along with applying CROSS APPLY might be used :
SELECT id,
MAX(CASE WHEN RowKey = 'names' THEN value END) AS names,
MAX(CASE WHEN RowKey = 'ages' THEN value END) AS ages
FROM
(
SELECT id, Q0.[value] AS RowArray, Q0.[key] AS RowKey
FROM tab
CROSS APPLY OPENJSON(JsonCol) AS Q0
) r
CROSS APPLY OPENJSON(r.RowArray) v
GROUP BY id, v.[key]
ORDER BY id, v.[key]
id | names | ages
---+-------+------
1 | John | 31
1 | Peter | 41
2 | Jim | 17
3 | Foo | 2
Demo
The first argument for OPENJSON would be a JSON column value, but not a table itself
I'm having quite a bit of trouble figuring out exactly how to rearrange a table. I have a large table that looks something like this:
+--------+-----------+
| NAME | ACCOUNT # |
+--------+-----------+
| Nike | 87 |
| Nike | 12 |
| Adidas | 80 |
| Adidas | 21 |
+--------+-----------+
And I want to rearrange it to look like this:
+------+--------+
| Nike | Adidas |
+------+--------+
| 87 | 80 |
| 12 | 21 |
+------+--------+
But I can't seem to figure out how. I tried using PIVOT, but that only works with aggregate functions. I tried using a FOR LOOP as well, but couldn't get it work just right.
You can do this in several ways, but all being by enumerating the rows. Here is an example using conditional aggregation:
select max(case when name = 'Nike' then account end) as Nike,
max(case when name = 'Adidas' then account end) as Adidas
from (select t.*,
row_number() over (partition by name order by account desc) as seqnum
from t
) t
group by seqnum;
Consider again a pivot solution but first adding a rownumber for rolling Name group counts. Below assumes an autonumber ID field:
SELECT * FROM
(
SELECT Name, "Account #",
(ROW_NUMBER() OVER(PARTITION BY Name ORDER BY ID)) GrpRowNum
/* ALT: (SELECT Count(*) FROM Table1 sub
* WHERE sub.Name = Table1.Name AND sub.ID <= Table1.ID) GrpRowNum */
FROM Table1
)
PIVOT
(
SUM("Account #")
FOR Name IN ('Nike', 'Adidas')
)
ORDER BY RowNum;
However, for your ~200 items, you cannot easily render the Pivot's IN clause without various workarounds including PIVOT XML output or stored procedures with PL/SQL. Similarly, you could use general purpose coding (Java, PHP, Python, R) to retreive SELECT DISTINCT Name FROM Table1 resultset in vector/array, joining element values (collapsing or imploding arrays) with quotes and comma separators, and dropping the entire list in IN clause.
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
Let say I got table like that
Name | Stage | Date
-------------------
A | 1st | 03092014
A | 2nd | 04092014
A | 3rd | 05092014
B | 1st | 06092014
B | 2nd | 08092014
C | 1st | 03092014
I wonder how to write SQL code wich would concat rows with same names and I will get something like that
Name | Stage | Date
----------------------+-----------------------------
A | 1st , 2nd, 3rd | 03092014 04092014 05092014
B | 1st, 2nd | 06092014 08092014
C | 1st | 03092014
Do I need to run through table with for cycle or is there better way to do that?
UPD:
I found out that I need to use this queries in Excel
You can use GROUP_CONCAT for this:
SELECT Name
, GROUP_CONCAT(Stage) AS Stages
, GROUP_CONCAT(Date) AS Dates
FROM my_table
GROUP BY Name;
With respect to your question - I am assuming you are using MS SQL Server 2008 or higher to get he desired output
I would suggest to use CROSS APPLY here to concat the data -
Assumed Your Table name - temptable
SELECT distinct tblMain.Name, substring(stages, 1, len(stages)-1) as [Stage],substring(dates, 1, len(dates)-1) as [Date]
FROM temptable tblMain
CROSS APPLY (
SELECT LTRIM(RTRIM(Stage)) + ','
FROM temptable tblDup1 WITH(NOLOCK)
WHERE tblDup1.Name= tblMain.Name
FOR XML PATH('')
) t1 (stages)
CROSS APPLY (
SELECT LTRIM(RTRIM(Date)) + ' '
FROM temptable tblDup2 WITH(NOLOCK)
WHERE tblDup2.Name= tblMain.Name
FOR XML PATH('')
) t2 (dates)
Working FIDDLE OUTPUT
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