Calculate in a table with different cells - SQL Server / T-SQL - sql

I have in my database (SQL Server 2008 R2) a table like this:
ID......Team...........Name......Age
102 Barcelona Mike 15
103 Barcelona Peter 10
104 Barcelona Jacke 10
105 Barcelona Jonas 10
106 Real Madrid Michael 20
107 Real Madrid Terry 26
108 Chelsea James 26
109 Chelsea Arthur 23
110 Chelsea Spence 22
How can I loop the field 'Team' and know that, there are records like Barcelona, Real Madrid and Chelsea.
After that I want to calculate the sum of the team player of each team.
For Barcelona: -> 10 + 10 + 10 + 15 = 45
For Real Madrid: -> 20 + 26 = 46
For Chelsea: -> 26 + 23 + 22 = 71
Fill each result in a separate variable.
The whole calculation should be done in a stored procedure.
The second thing, if I have a table like this:
ID......Team...........Name......HeaderGoal......FreeKickGoal
104 Barcelona Mike 2 1
105 Barcelona Peter 1 0
106 Real Madrid Michael 0 1
107 Real Madrid Terry 0 1
108 Chelsea James 0 0
109 Chelsea Arthur 2 3
110 Chelsea Spence 4 0
How can I loop the field 'Team' and know that, there are records like Barcelona, Real Madrid and Chelsea.
After that I want to calculate the sum of all Goals of each team with the goal type HeaderGoal and FreeKickGoal.
Example for
-> Barcelona: 2+1+1 = 4
-> Real Madrid: 1+1 = 2
-> Chelsea: 2 + 3 + 4 = 9
Fill each result in a separate variable.
The whole calculation should be done in a stored procedure.
I hope you can help me!
BK_

If I understood your question correctly it looks like what you want are aggregates for each group, something that is easily accomplished with the GROUP BY clause.
For the first query you would use:
SELECT team, SUM(age) AS 'Sum of the team'
FROM table
GROUP BY team
This will give this result:
Team Sum of the team
-------------------- ---------------
Barcelona 45
Chelsea 71
Real Madrid 46
and for the second:
SELECT team, SUM(headergoal + freekickgoal) AS 'Sum of goals'
FROM table
GROUP BY team
which will give this result:
Team Sum of goals
-------------------- ------------
Barcelona 4
Chelsea 9
Real Madrid 2
In your example data you list the desired result for the first part for Chelsea as 45 but I guess that is just a typo as you omitted one of Chelseas rows in the calculation?
As for turning it into a stored procedure I can just tell you that it's easy and refer you to the documentation as I won't do all the work for you...
Edit: added merge intoas a response to a comment:
To insert the result of the second query into an existing table you can use either a simple INSERT statement like this:
INSERT table_with_team_and_goals
SELECT team, SUM(headergoal + freekickgoal)
FROM table
GROUP BY team
or MERGE INTO which might be better if you intend to run the query many times (the target table will then be updated if the team already exist in it):
MERGE INTO table_with_team_and_goals AS target
USING (SELECT Team, SUM(headergoal + freekickgoal) AS goals FROM table_with_goals GROUP BY team) AS source
ON target.team=source.team
WHEN MATCHED THEN
UPDATE SET goals = source.goals
WHEN NOT MATCHED THEN
INSERT (Team, Goals)
VALUES (source.team, source.goals);

SELECT TEAM , Name, COUNT(TEAM) As GoalsPerTeam, COUNT(NAME) As GoalPerPlayer
FROM TABLE
GROUP BY TEAM , Name
This query will give you tolal goals scored by per player and per team .

-- Sum age by team
SELECT Team, SUM(Age) SumAge
FROM
(
SELECT Id, Team, Name, Age FROM
(
VALUES
(102, 'Barcelona' , 'Mike' , 15),
(103, 'Barcelona' , 'Peter' , 10),
(104, 'Barcelona' , 'Jacke' , 10),
(105, 'Barcelona' , 'Jonas' , 10),
(106, 'Real Madrid', 'Michael', 20),
(107, 'Real Madrid', 'Terry' , 26),
(108, 'Chelsea' , 'James' , 26),
(109, 'Chelsea' , 'Arthur' , 23),
(110, 'Chelsea' , 'Spence' , 22)
) AS X(Id, Team, Name, Age)
) X
GROUP BY Team
-- Sum goals by team
SELECT Team, SUM(HeaderGoal + FreeKickGoal) Goals
FROM
(
SELECT Id, Team, Name, HeaderGoal, FreeKickGoal FROM
(
VALUES
(104, 'Barcelona' , 'Mike' , 2, 1),
(105, 'Barcelona' , 'Peter' , 1, 0),
(106, 'Real Madrid', 'Michael', 0, 1),
(107, 'Real Madrid', 'Terry' , 0, 1),
(108, 'Chelsea' , 'James' , 0, 0),
(109, 'Chelsea' , 'Arthur' , 2, 3),
(110, 'Chelsea' , 'Spence' , 4, 0)
) AS X(Id, Team, Name, HeaderGoal, FreeKickGoal)
) X
GROUP BY Team

Related

How to pivot specific columns while grouping other columns (SQL)

I am working on a SQL query in Azure Databricks Environment, where considering the following dataset:
CREATE OR REPLACE TABLE tb_user_info
(
clientid INT,
visitid STRING,
channel STRING,
conversion INT,
index INT,
value STRING
);
INSERT INTO tb_user_info VALUES
(123, 'abc123', 'google', 1, 11, '1250'),
(123, 'abc123', 'google', 1, 22, '25000'),
(123, 'abc123', 'google', 1, 33, '1K and 3K'),
(456, 'def456', 'facebook', 3, 11, '2860'),
(456, 'def456', 'facebook', 3, 22, '78000'),
(456, 'def456', 'facebook', 3, 33, '3K and 5K');
SELECT * FROM tb_user_info ORDER BY clientid, index
clientid
visitid
channel
conversion
index
value
123
abc123
google
1
11
1250
123
abc123
google
1
22
25000
123
abc123
google
1
33
1K and 3k
456
def456
facebook
3
11
2860
456
def456
facebook
3
22
78000
456
def456
facebook
3
33
3K and 5k
I want to get the following output:
clientid
visitid
channel
conversion
salary (index=11)
savings (index=22)
salary range (index=33)
123
abc123
google
1
1250
25000
1K and 3k
456
def456
facebook
1
2860
78000
3K and 5k
where the columns clientid, visitid, channel and conversion are grouped and the columns index and value are the columns that are pivoted.
I've tried using the Pivot function and I read this Documentation but I haven't been successful.
Could you help me with how can I solve this task?
I am not sure what actual problem you have encounted, I write one query, it seems work normally,
SELECT * FROM (
SELECT clientid, visitid , channel , conversion , ind , value
FROM tb_user_info
) ss
PIVOT (
max(value)
FOR ind in (
[11] ,[22] ,[33]
)
) as a

How to SQL PIVOT this data by day?

With the following schema and table, showing example output of the table, I'm trying to "PIVOT" this data to have rows consisting of the SERIAL and AREA, with each column being the COMPLIANCE for each "NIGHTOF" record. Ideal output shown below as well. I can not wrap my head around the PIVOT syntax no matter how many times I read examples...
Schema and table:
SELECT [CONTID], [AREA], [NIGHTOF], [COMPLIANCE] FROM ComplianceScores
Regular Output:
CONTID NIGHTOF AREA COMPLIANCE
001 2014-01-01 Room 2 28
001 2014-01-01 Room 2 18
001 2014-01-01 Room 2 20
003 2014-01-02 Room 1 18
003 2014-01-02 Room 1 70
003 2014-01-02 Room 1 80
008 2014-01-03 Room 1 0
009 2014-01-04 Room 1 35
Ideal output:
CONTID AREA 2014-01-01 2014-01-02 2014-01-03
001 Room 2 28 18 20
003 Room 1 18 70 80
I agree with #Sparky that your data and output don't match.
I think your data was meant to be like:
create table ComplianceScores (
CONTID char(3),
NIGHTOF date,
AREA varchar(10),
COMPLIANCE int
)
insert ComplianceScores (CONTID, NIGHTOF, AREA, COMPLIANCE)
values ('001', '2014-01-01', 'Room 2', 28)
, ('001', '2014-01-02', 'Room 2', 18)
, ('001', '2014-01-03', 'Room 2', 20)
, ('003', '2014-01-01', 'Room 1', 18)
, ('003', '2014-01-02', 'Room 1', 70)
, ('003', '2014-01-03', 'Room 1', 80)
And I think you're looking for this SQL:
select ContId, Area, [2014-01-01], [2014-01-02], [2014-01-03]
from (select Area, ContId, NightOf, Compliance
from ComplianceScores) SourceTable
pivot
(
sum(Compliance)
for NightOf in ([2014-01-01], [2014-01-02], [2014-01-03])
) pt;

How to combine table and aggregated results in SQL

I have a table of indicators (e.g. number of students) for schools in a number of districts.
I need to aggregate those indicators by districts, and I would like to collate those aggregates at the bottom of my initial table:
I have :
district school students
District1 School1 10
District2 School2 20
District1 School3 30
District2 School4 40
District1 School5 50
District2 School6 60
I would like to eventually get in the same table:
district school students
District1 School1 10
District2 School2 20
District1 School3 30
District2 School4 40
District1 School5 50
District2 School6 60
District1 Total 90
District2 Total 120
I have tried
SELECT district, school, students
FROM enrolment
UNION
SELECT district, "Total" as school, sum(students)
FROM enrolment
GROUP BY district;
but I'm actually pulling many indicators from many different queries, and aggregating on two additional levels (states and country), so it's very slow. Is there a most efficient way of doing this?
If you are using SQL-Server you can use ROLLUP:
SAMPLE DATA:
CREATE TABLE #enrolment(district VARCHAR(25)
, school VARCHAR(25)
, students INT);
INSERT INTO #enrolment
VALUES
('District1'
, 'School1'
, 10),
('District2'
, 'School2'
, 20),
('District1'
, 'School3'
, 30),
('District2'
, 'School4'
, 40),
('District1'
, 'School5'
, 50),
('District2'
, 'School6'
, 60);
QUERY:
SELECT district
, ISNULL(school, 'Total') AS school
, SUM(students) AS students
FROM #enrolment
GROUP BY ROLLUP(district, school);
RESULT:

Converting query with aggregate functions to a group by

What I am trying to do is extract what and how many orders are ordered by customers.
I am able to get all the data but what I want is to group it based on a TrackingID unique to each customer, and thus get only one row per customer, regardless of how many items ordered.
The Code I currently have is
Select OT.TrackingID As FW_ID
,( Select
SUBSTRING(CT.Name, 1, CHARINDEX(' ', CT.Name) - 1)
Where LEN(CT.Name) - LEN(REPLACE(CT.Name, ' ', '')) > 0
) As Forename
,( Select
SUBSTRING(CT.Name, CHARINDEX(' ', CT.Name) + 1, 8000)
Where LEN(CT.Name) - LEN(REPLACE(CT.Name, ' ', '')) > 0
) As Surname
,( Select CAST(1 as VARCHAR) + ' p1 male'
Where OT.ArticleNr = 1
And CT.GroupNr IN (2,5)) As Amount_male_t1
,( Select CAST(1 as VARCHAR) + ' p1 female'
Where OT.ArticleNr = 2
And CT.GroupNr IN (2,5)) As Amount_female_t1
,( Select CAST(1 as VARCHAR) + ' p2 male'
Where OT.ArticleNr = 1
And CT.GroupNr IN (3,6)) As Amount_male_t2
,( Select CAST(1 as VARCHAR) + ' p2 female'
Where OT.ArticleNr = 2
And CT.GroupNr IN (3,6)) As Amount_female_t2
From OrderTable As OT
JOIN CustomerTable As CT
ON OT.CustomerNr = CT.CustomerNr
JOIN CampaignTable As CT
ON OT.TrackingID = CT.TrackingID
Where CT.GroupNr IN (2,3,5,6)
And OT.NewOrder = 1
An example of what I can get from this is
FW_ID Forename Surname Amount_male_t1 Amount_female_t1 Amount_male_t2 Amount_female_t2
101 John Doe 1 p1 male NULL NULL NULL
101 John Doe NULL 1 p1 female NULL NULL
102 Steve Boss NULL NULL 1 p2 male NULL
102 Steve Boss NULL NULL 1 p2 male NULL
And what I want is
FW_ID Forename Surname Amount_male_t1 Amount_female_t1 Amount_male_t2 Amount_female_t2
101 John Doe 1 p1 male 1 p1 female NULL NULL
102 Steve Boss NULL NULL 2 p2 male NULL
Problem is that when I use Group By on OT.TrackingID I get an error when using MAX() on the names due to them being aggregated already and errors when trying to turn the package counters into COUNT() funktions.
Help would be most appreciated.
The joined tables looks something like this
OrderTable:
TrackingID CustomerNr OrderNr ArticleNr NewOrder OrderDate
101 10054 25 1 1 2014-06-09
101 10054 24 2 1 2014-06-09
102 10036 23 1 1 2014-06-08
102 10036 22 1 1 2014-06-07
103 10044 21 2 0 2014-06-06
CustomerTable
CustomerNr Name Adress ZipCode CustomerCreatedDate
10054 John Doe Upstreet 123456 2013-05-18
10036 Steve Boss Downstreet 234567 2014-06-07
10044 Eric Cartman Sidestreet 345678 2014-02-21
CampaignTable
TrackingID GroupNr ProductDescription
101 2 Group 2 & 5 are offered package 1
102 3 Group 3 & 6 are offered package 2
103 5 Group 2 & 5 are offered package 1
NOTE: If someone could give advice as to why my question is downvoted that would be most appreciated. I don't quite know what I've done wrong.
One approach would be to use a view, as mentioned. You can do this in-line in the query, and it doesn't need to be saved in the schema.
I've made a demo to show how this can be done with the sample table data you provided. From here if you wanted to change the representation of the data to a single line, you can just pivot it as shown here.
SELECT SplitNames.Forename, SplitNames.LastName FROM CustomerTable
INNER JOIN
(
SELECT CustomerNr,
SUBSTRING(CT.Name, 1, CHARINDEX(' ', CT.Name) - 1) As Forename,
SUBSTRING(CT.Name, CHARINDEX(' ', CT.Name), LEN(CT.Name) - 1) As LastName
FROM CustomerTable CT
) SplitNames ON CustomerTable.CustomerNr = SplitNames.CustomerNr
In general you should try to use as few subqueries in the select statement as possible, as it makes it impossible to properly aggregate any of your results.
You could use your query as a view and then use that view for a new query using distinct and count clauses.
I'm sorry, I understand this is not the best solution since it has two steps and maybe could be solve in a better way, but I can't find anything better, at moment, without any example data on a my db.

Updating a column based on values in related rows

I have a table like the following in SQL Server 2008
AMID TierLevel
-------- -------------
999 GOLD
1000 SILVER
1000 GOLD
1000 PLATINUM
1000 BRONZE
1001 GOLD
1001 SILVER
1002 SILVER
1003 GOLD
Now I want to Update this table like the following
AMID TierLevel
-------- -------------
999 GOLD
1000 PLATINUM
1000 PLATINUM
1000 PLATINUM
1000 PLATINUM
1001 GOLD
1001 GOLD
1002 SILVER
1003 GOLD
Here the conditions are
I want unique Tier value for the same AMID, and the Tier values should be selected in priority base like
PLATINUM
GOLD
SILVER
BRONZE
It means if it is having the highest one present in its Tier values then select that. Like I shown in the second table, Platinum for 1000 and Gold for 1001..
please help me with this
Thanks,
Harry
DECLARE #amid TABLE (Amid INT, TierLevel VARCHAR(20));
INSERT #amid VALUES
(999 ,'GOLD'),
(1000,'SILVER'), (1000,'GOLD'), (1000,'PLATINUM'), (1000,'BRONZE'),
(1001,'GOLD'), (1001,'SILVER'),
(1002,'SILVER'), (1003,'GOLD');
;WITH [priority](r, n) AS
(
SELECT 1, 'PLATINUM'
UNION ALL SELECT 2, 'GOLD'
UNION ALL SELECT 3, 'SILVER'
UNION ALL SELECT 4, 'BRONZE'
),
per_amid(amid, h) AS
(
SELECT a.amid, MIN(p.r)
FROM #amid AS a
INNER JOIN [priority] AS p
ON a.TierLevel = p.n
GROUP BY a.amid
)
UPDATE a
SET TierLevel = p.n
FROM #amid AS a
INNER JOIN per_amid AS pa
ON a.Amid = pa.amid
INNER JOIN [priority] AS p
ON pa.h = p.r
-- added where clause to address question brought up on other answer
WHERE a.TierLevel <> p.n;
SELECT Amid, TierLevel FROM #amid;
Results:
Amid TierLevel
---- ---------
999 GOLD
1000 PLATINUM
1000 PLATINUM
1000 PLATINUM
1000 PLATINUM
1001 GOLD
1001 GOLD
1002 SILVER
1003 GOLD
I would do this using a temporary table to store the rank of each Tier:
DECLARE #Rank TABLE (ID INT NOT NULL PRIMARY KEY, Name VARCHAR(10) NOT NULL)
INSERT #Rank VALUES (1, 'PLATINUM'), (2, 'GOLD'), (3, 'SILVER'), (4, 'BRONZE')
;WITH T AS
( SELECT AMID, TierLevel, MIN(ID) OVER(PARTITION BY AMID) [MinID]
FROM #T
INNER JOIN #Rank
ON Name = TierLevel
)
UPDATE T
SET TierLevel = Name
FROM T
INNER JOIN #Rank
ON ID = MinID
WHERE TierLevel <> Name
This was done using the following sample data:
CREATE TABLE #T (AMID INT, TierLevel VARCHAR(10))
INSERT #T VALUES
(999, 'GOLD'),
(1000, 'SILVER'),
(1000, 'GOLD'),
(1000, 'PLATINUM'),
(1000, 'BRONZE'),
(1001, 'GOLD'),
(1001, 'SILVER'),
(1002, 'SILVER'),
(1003, 'GOLD')