Add Int Column Values Similar To Count - sql

I have a data structure similiar to:
Person | Counter
Mary 1
Mary 2
John 5
John 6
I am trying to Group By on the Person column, and add up the values for the counter, for each person. Meaning, I am trying to return:
Person - Mary - Has 3 Counters
Person - John - Has 11 Counters
This is my current effort, however it is not working:
SELECT ('Person - '+ ISNULL(NULLIF([Person],''),'NOT ASSIGNED') + 'Has') as Name,
COUNT(*) Match, (SELECT COUNT(*) + 'Counters' FROM [MyTable] ) Total
FROM [MyTable]

Here you go:
DECLARE #DATA TABLE (Person VARCHAR(25), [Counter] INT)
INSERT INTO #DATA
SELECT 'Mary',1 UNION
SELECT 'Mary',2 UNION
SELECT 'John',5 UNION
SELECT 'John',6
SELECT 'Person - ' + ISNULL(Person,'Not Assigned') + ' - has ' + CAST(SUM([Counter]) AS VARCHAR) + ' counters'
from #DATA
GROUP BY Person

What you want is a group by clause just as you stated, so something based on this:
SELECT Person, SUM(Counter)
FROM MyTable
GROUP BY Person
I didn't include the extra text from your original query to make it a bit more compact.

Cheruvian's answer might be what you want, I can't tell exactly by your example, here is what I came up with just in case.
SELECT 'Person - ' + ISNULL(NULLIF(Person, ''), 'NOT ASSIGNED') + ' - Has ' + SUM(Counter) + ' Counters' As ReportOutput
FROM MyTable
GROUP BY Person
ORDER BY Person;

You should be using an aggregate function for this.
SELECT ('Person - '+ ISNULL(NULLIF([Person],''),'NOT ASSIGNED') + 'Has') as Name,
COUNT(*) As Match,
Sum(Counter) As Count
FROM [MyTable]
Group By Person
You were also missing an As in the second line. The Sum Aggregate function will Sum all of the values for each group, in this case your counter.

I would do this using a GROUP BY in a subselect, and then build the string using the results. Something like this:
SELECT ('Person - '+ myperson + ' Has ' + CAST(mycount AS VARCHAR) + ' Counters')
FROM (SELECT ISNULL(NULLIF([Person],''),'NOT ASSIGNED') myperson, sum(counter) mycount
FROM [MyTable]
GROUP BY ISNULL(NULLIF([Person],''),'NOT ASSIGNED')) a

Related

How to Get ##ROWCOUNT from a Single Query in a UNION - Create Trailer record with RowCount

I need to create a HEADER and a TRAILER record. The TRAILER needs to include the RowCount of the 'main' query. How Can I get the ##Rowcount of the query and save it to variable , to be included in the TRAILER.
See this.
--- Var to save Count
declare #cnt int
-- HEADER RECORD
Select Cast('H' as Char(2)) +
Cast('MyFile' as Char(30))
+ CONVERT(Char(8),GetDate(),112)
union all
-- MAIN Query that I need the Count of
Select top 10 lastname from CUSTOMERS
set #cnt = ##ROWCOUNT
union all <--ERROR obviously
-- TRAILER record
Select Cast('T' as Char(2)) +
CONVERT(Char(9),GetDate(),112) +
Right(Replicate('0',9) + Cast(#cnt as VarChar(9)),9)
thx in advance
You can use a CTE to reuse the definition of the main query:
WITH Query AS (
SELECT top 10 lastname from CUSTOMERS
)
SELECT X.Result
FROM (
SELECT Cast('H' as Char(2)) + Cast('MyFile' as Char(30))
+ CONVERT(Char(8),GetDate(),112) AS Result, 1 AS Position
UNION ALL
SELECT *, 2 AS Position FROM Query
UNION ALL
Select Cast('T' as Char(2)) + CONVERT(Char(9),GetDate(),112) + Right(Replicate('0',9)
+ Cast((SELECT COUNT(*) FROM Query) as VarChar(9)),9) AS Result, 3 AS Position
) X ORDER BY X.Position
However, the CTE will be evaluated twice; if your query is complex and time-consuming, you might want to use a temporary table:
SELECT TOP 10 lastname INTO #Temp FROM CUSTOMERS
SELECT X.Result
FROM (
SELECT Cast('H' as Char(2)) + Cast('MyFile' as Char(30))
+ CONVERT(Char(8),GetDate(),112) AS Result, 1 AS Position
UNION ALL
SELECT *, 2 AS Position FROM #Temp
UNION ALL
Select Cast('T' as Char(2)) + CONVERT(Char(9),GetDate(),112) + Right(Replicate('0',9)
+ Cast((SELECT COUNT(*) FROM #Temp) as VarChar(9)),9) AS Result, 3 AS Position
) X ORDER BY X.Position
Try this following script-
-- HEADER RECORD
SELECT CAST('H' AS CHAR(2)) + CAST('MyFile' AS CHAR(30)) + CONVERT(CHAR(8), GETDATE(), 112)
UNION ALL
-- MAIN Query that I need the Count of
SELECT lastname FROM CUSTOMERS
UNION ALL
-- TRAILER record
SELECT CAST('T' AS CHAR(2)) + CONVERT(CHAR(9), GETDATE(), 112) + RIGHT(REPLICATE('0', 9) + CAST(
(
SELECT COUNT(lastname) FROM CUSTOMERS
) AS VARCHAR(9)), 9);

Manipulating duplicate values?

I have a table, with an ID, FirstName & Lastname.
I'm selecting that using the following query:
SELECT USER_ID as [ID], First_P + ' ' + Last_P as FullName FROM Persons
It's working fine. I'm basically having a list of ID's and full names.
Full names could be the same. How is it possible for me to find them and add the ID on the Full name cell as well? only when the names are the same.
Example:
1 John Wick (1)
50 John Wick (50)
I haven't found any similar questions to be honest, at least not for MSSQL. So If there are any, feel free to link me.
please take a look my answer. I used nested query to identify number of duplicated names
SELECT
ID,
IIF(NUMBEROFDUPS =1, NAME, CONCAT(NAME, ' (', ID, ')')) AS NAME
FROM
(
SELECT
ID,
CONCAT(First_P, ' ', Last_P) AS NAME,
COUNT(*) OVER (PARTITION BY First_P,Last_P) AS NUMBEROFDUPS
FROM
Table1
) tmp;
You can use outer apply to group the items via First_P + ' ' + Last_P
and then add case for multiple items.
The select stuff should look like:
SELECT USER_ID as [ID], p1.First_P + ' ' + p1.Last_P + case when cnt.FullName is not null
then '(' + p2.[sum] + ')' else '' end as FullName FROM Persons p1
outer apply (select First_P + ' ' + Last_P as FullName,count(1) as [sum]
from Persons p2
where p2.First_P + ' ' + p2.Last_P = p1.First_P + ' ' + p1.Last_P
group by First_P + ' ' + Last_P
having count(1) > 1) cnt

Adding SELECT COUNT(*) subclause trashes performance in SQL Server

I'm building a query and the latest step involved adding a SELECT COUNT(*) FROM [modification] sub-clause to allow me to detect the last row of the query, but it destroys the performance:
SELECT CONCAT(
IIF(row_number() OVER (ORDER BY forecastId) % 50000 = 1,
CONCAT('INSERT INTO modification (userId, epochTime, ',
'forecastId, description, auxText, auxDate) VALUES ('),
' ('),
userId, ',',
epochTime, ',',
forecastId, ',',
'''', dbo.encode4MySql(description), ''',',
'''', dbo.encode4MySql(auxText), ''',',
'''', CONVERT(VARCHAR(20), auxDate, 120), ''')',
IIF(row_number() OVER (ORDER BY forecastId) % 50000 = 0
OR row_number() OVER (ORDER BY forecastId) =
(SELECT COUNT(*) FROM modification),
'; COMMIT;', ','))
FROM modification
ORDER BY forecastId;
If you can't see what I'm doing, I'm building INSERT () VALUES (),(),(),... statements of 50000 rows at a time.
Please restrict suggestions for completely alternative approaches to the comments. I'm looking for a way to find the last row number here without it slowing the query down massively - which it does.
I'm not massively familiar with query plans but can post one here if it helps. I've tried a lot of things from related questions here, but nothing that I can get to work.
One other option would be to order by forecastId desc in the final or:
IIF(row_number() OVER (ORDER BY forecastId) % 50000 = 0
OR row_number() OVER (ORDER BY forecastId desc) = 1,
'; COMMIT;', ','))
You have a rather complicated expression, so SQL Server may not optimize it. Move the logic to the FROM clause:
FROM (SELECT m.*, COUNT(*) OVER () as num_rows
FROM modification m
) m
And then use num_rows in the rest of the query.
Assuming you don't want to change the current design, you could just add an extra UNION ALL step at the end. By looking at your query, it looks like the only purpose of changing the query is to add a COMMIT at the end.
CURRENT QUERY
UNION ALL
SELECT 'COMMIT;';
Let me know if that works for you.
***********UPDATE*********
I thought this query is easier to troubleshoot. See if it will perform any better. You would have to plugin the CTE part for your table.
SELECT BusinessEntityID,JobTitle,HireDate INTO dbo.TestTable FROM [HumanResources].[Employee]
SELECT TOP 0 BusinessEntityID,JobTitle,HireDate INTO dbo.TestTable2 FROM [HumanResources].[Employee]
SET NOCOUNT ON
WITH CTE AS (SELECT ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) Col,
' (' +
CAST(BusinessEntityID AS VARCHAR(20)) +
', ''' +
JobTitle +
''', ''' +
CONVERT(VARCHAR(20), HireDate, 120) +
''')' Query
FROM TestTable
)
SELECT
CASE
WHEN COL % 50 = 0 THEN ', ' + Query + ' COMMIT;'
WHEN COL % 50 = 1 THEN ' INSERT INTO dbo.TestTable2 (BusinessEntityID, JobTitle, HireDate) VALUES ' + Query
ELSE ', ' + Query
END
FROM CTE
UNION ALL SELECT 'COMMIT;'

obtain an average of days an issue is open

I have a table with a 'call open date' column (actual_log_date) which I convert, using
convert(char(10), actual_log_date, 103) 'Call Logged'
to show calls logged on 01/02/2012 for example.
There is also a close time column (close_time) for which I run the same convert,
convert(char(10), close_time, 103) 'Call Closed'
to show calls closed on 12/02/2012.
Now, I need to produce a script to get the average number of days calls have been open. I can run
(select datediff(dd, c.actual_log_date, c.close_time)) as 'Days Open'
to create the column which shows the call was open for 12 days or whatever, but now need to count all the records and obtain the average sum at the end of the results, which is where I am stuck!
I forgot to say I can't create a temp table as I only have read only rights.
Sorry, I'm new here so not sure on the protocol but here is the script if it's easier to answer seeing what I'm working with;
select
convert(char(10),actual_log_date,103) 'Call Logged',
call_number 'Call #',
short_problem 'Issue Description',
Customer = (select surname + ', ' + first_name from i.ar_user_attributes where ref = user_ref ),
Officer = (select surname + ', ' + first_name from i.su_help_centre where ref = resolve_officer),
Department = (select name from i.su_support_group where ref = resolve_group),
convert(char(10),close_time,103) 'Closed',
(select datediff(hh,c.actual_log_date,c.close_time)) as 'Hours Open',
(select datediff(dd,c.actual_log_date,c.close_time)) as 'Days Open'
from
i.cl_call_logging c
where
c.resolve_time between
convert(datetime,convert(varchar,month(dateadd(m,-1,getdate()))) + '/01/' + convert(varchar,year(dateadd(m,-1,getdate())))) and
convert(datetime,convert(varchar,month(getdate())) + '/01/' + convert(varchar,year(getdate())))
and c.resolve_group in ('48', '60')
Thanks again!
You can use a with query, like this:
insert into #temp (CallOpen, CallClosed)
values
('2012-01-02', '2012-02-12'),
('2012-01-05', '2012-02-12'),
('2012-01-07', '2012-02-05'),
('2012-01-14', '2012-02-03'),
('2012-02-10', '2012-03-15')
;with T1 as
(
select
CallOpen,
CallClosed,
DATEDIFF(dd, callopen, callclosed) as DaysOpen
from #temp
)
select AVG(daysopen) from T1
Using your full script, try this:
;with T1 as
(
select
convert(char(10),actual_log_date,103) 'Call Logged',
call_number 'Call #',
short_problem 'Issue Description',
Customer = (select surname + ', ' + first_name from i.ar_user_attributes where ref = user_ref ),
Officer = (select surname + ', ' + first_name from i.su_help_centre where ref = resolve_officer),
Department = (select name from i.su_support_group where ref = resolve_group),
convert(char(10),close_time,103) 'Closed',
(select datediff(hh,c.actual_log_date,c.close_time)) as 'Hours Open',
(select datediff(dd,c.actual_log_date,c.close_time)) as 'Days Open'
from #cl_call_logging c
where
c.resolve_time between
convert(datetime,convert(varchar,month(dateadd(m,-1,getdate()))) + '/01/' + convert(varchar,year(dateadd(m,-1,getdate())))) and
convert(datetime,convert(varchar,month(getdate())) + '/01/' + convert(varchar,year(getdate())))
and c.resolve_group in ('48', '60')
)
select AVG([Days Open]) from T1

Parse SQL field into multiple rows

How can I take a SQL table that looks like this:
MemberNumber JoinDate Associate
1234 1/1/2011 A1 free A2 upgrade A31
5678 3/15/2011 A4
9012 5/10/2011 free
And output (using a view or writing to another table or whatever is easiest) this:
MemberNumber Date
1234-P 1/1/2011
1234-A1 1/1/2011
1234-A2 1/1/2011
1234-A31 1/1/2011
5678-P 3/15/2011
5678-A4 3/15/2011
9012-P 5/10/2011
Where each row results in a "-P" (primary) output line as well as any A# (associate) lines. The Associate field can contain a number of different non-"A#" values, but the "A#"s are all I'm interested in (# is from 1 to 99). There can be many "A#"s in that one field too.
Of course a table redesign would greatly simplify this query but sometimes we just need to get it done. I wrote the below query using multiple CTEs; I find its easier to follow and see exactly whats going on, but you could simplify this further once you grasp the technique.
To inject your "P" primary row you will see that I simply jammed it into Associate column but it might be better placed in a simple UNION outside the CTEs.
In addition, if you do choose to refactor your schema the below technique can be used to "split" your Associate column into rows.
;with
Split (MemberNumber, JoinDate, AssociateItem)
as ( select MemberNumber, JoinDate, p.n.value('(./text())[1]','varchar(25)')
from ( select MemberNumber, JoinDate, n=cast('<n>'+replace(Associate + ' P',' ','</n><n>')+'</n>' as xml).query('.')
from #t
) a
cross apply n.nodes('n') p(n)
)
select MemberNumber + '-' + AssociateItem,
JoinDate
from Split
where left(AssociateItem, 1) in ('A','P')
order
by MemberNumber;
The XML method is not a great option performance-wise, as its speed degrades as the number of items in the "array" increases. If you have long arrays the follow approach might be of use to you:
--* should be physical table, but use this cte if needed
--;with
--number (n)
--as ( select top(50) row_number() over(order by number) as n
-- from master..spt_values
-- )
select MemberNumber + '-' + substring(Associate, n, isnull(nullif(charindex(' ', Associate + ' P', n)-1, -1), len(Associate)) - n+1),
JoinDate
from ( select MemberNumber, JoinDate, Associate + ' P' from #t
) t (MemberNumber, JoinDate, Associate)
cross
apply number n
where n <= convert(int, len(Associate)) and
substring(' ' + Associate, n, 1) = ' ' and
left(substring(Associate, n, isnull(nullif(charindex(' ', Associate, n)-1, -1), len(Associate)) - n+1), 1) in ('A', 'P');
Try this new version
declare #t table (MemberNumber varchar(8), JoinDate date, Associate varchar(50))
insert into #t values ('1234', '1/1/2011', 'A1 free A2 upgrade A31'),('5678', '3/15/2011', 'A4'),('9012', '5/10/2011', 'free')
;with b(f, t, membernumber, joindate, associate)
as
(
select 1, 0, membernumber, joindate, Associate
from #t
union all
select t+1, charindex(' ',Associate + ' ', t+1), membernumber, joindate, Associate
from b
where t < len(Associate)
)
select MemberNumber + case when t = 0 then '-P' else '-'+substring(Associate, f,t-f) end NewMemberNumber, JoinDate
from b
where t = 0 or substring(Associate, f,1) = 'A'
--where t = 0 or substring(Associate, f,2) like 'A[1-9]'
-- order by MemberNumber, t
Result is the same as the requested output.
I would recommend altering your database structure by adding a link table instead of the "Associate" column. A link table would consist of two or more columns like this:
MemberNumber Associate Details
-----------------------------------
1234 A1 free
1234 A2 upgrade
1234 A31
5678 A4
Then the desired result can be obtained with a simple JOIN:
SELECT CONCAT(m.`MemberNumber`, '-', 'P'), m.`JoinDate`
FROM `members` m
UNION
SELECT CONCAT(m.`MemberNumber`, '-', IFNULL(a.`Associate`, 'P')), m.`JoinDate`
FROM `members` m
RIGHT JOIN `members_associates` a ON m.`MemberNumber` = a.`MemberNumber`