Reformatting SQL output - sql

I have data that looks like this
Name XX YY
alpha 10 77
beta 10 90
alpha 20 72
beta 20 91
alpha 30 75
beta 30 94
alpha 40 76
beta 40 95
If I use
select * from scores order by Name, XX
I will get
Name XX YY
alpha 10 77
alpha 20 72
alpha 30 75
alpha 40 76
beta 10 90
beta 20 91
beta 30 94
beta 40 95
At the moment, I'm retrieving the data in this form and using some C coding to format it like
Name xx=10 xx=20 xx=30 xx=40
alpha 77 72 75 76
beta 90 91 94 95
Assuming that there will always be entries for 10, 20, 30, 40 for every name, is something like this possible in SQL without creating a new table like in SQL Reformatting table columns

You need to use PIVOT to get your desired results. Before using PIVOT, some customization required in your value in column XX so that the final column output can meet your expectation.
SELECT * FROM
(
SELECT Name,'XX='+CAST(XX AS VARCHAR) XX,YY
FROM your_table
)AS P
PIVOT(
SUM(YY)
FOR XX IN ([XX=10],[XX=20],[XX=30],[XX=40])
) PP
Output-
Name XX=10 XX=20 XX=30 XX=40
alpha 77 72 75 76
beta 90 91 94 95
The same output can be also achieved with this following query-
SELECT Name,
[10] AS [XX=10],
[20] AS [XX=20],
[30] AS [XX=30],
[40] AS [XX=40]
FROM
(
SELECT Name, XX,YY
FROM your_table
)AS P
PIVOT(
SUM(YY)
FOR XX IN ([10],[20],[30],[40])
) PP

You could use dynamic PIVOT to achieve the same result which would be more robust,
CREATE TABLE #table1 (Name varchar(100), XX INT, YY INT)
INSERT INTO #table1 VALUES
('alpha',10,77),
('beta',10,90),
('alpha',20,72),
('beta',20,91),
('alpha',30,75),
('beta',30,94),
('alpha',40,76),
('beta',40,95)
DECLARE #pvt NVARCHAR(MAX) = '';
DECLARE #dynamicCol NVARCHAR(MAX) = '';
SELECT #pvt += ', ' +QUOTENAME([XX]) FROM #table1 GROUP BY [XX];
SELECT #dynamicCol += ', ' +QUOTENAME([XX]) + ' AS ' + QUOTENAME('XX=' + CAST([XX] AS VARCHAR(25))) FROM #table1 GROUP BY [XX];
SET #pvt = STUFF(#pvt,1,1,'')
SET #dynamicCol = STUFF(#dynamicCol,1,1,'')
EXEC ('
SELECT [Name],' + #dynamicCol+'
FROM #table1 a
PIVOT
(
SUM([YY])
FOR [XX] IN ('+ #pvt+')
) PIV');
The result is as below,
Name XX=10 XX=20 XX=30 XX=40
alpha 77 72 75 76
beta 90 91 94 95

Another solution with case
SELECT Name
,SUM(CASE when XX = '10' then YY else 0 END) AS 'xx=10'
,SUM( CASE when XX = '20' then YY else 0 END) AS 'xx=20'
,SUM( CASE when XX = '30' then YY else 0 END) AS 'xx=30'
,SUM( CASE when XX = '40' then YY else 0 END) AS 'xx=40'
FROM temp_0
group by Name

Related

How do I change repeated values in a column to rows and then populate the respective row values with the respective values

I have a table this way:
ID ATTR_NAME SUB_ATTR_NAME VALUE
1 ATTR-1 SUB-ATTR-1 23
2 ATTR-1 SUB-ATTR-2 32
3 ATTR-1 SUB-ATTR-3 25
4 ATTR-1 SUB-ATTR-4 28
5 ATTR-2 SUB-ATTR-1 78
6 ATTR-2 SUB-ATTR-2 45
7 ATTR-2 SUB-ATTR-3 48
8 ATTR-2 SUB-ATTR-4 41
9 ATTR-3 SUB-ATTR-1 47
10 ATTR-3 SUB-ATTR-2 12
11 ATTR-3 SUB-ATTR-3 16
12 ATTR-3 SUB-ATTR-4 18
But using SQL, I want a table this way:
SUB-ATTR-1 SUB-ATTR-2 SUB-ATTR-3 SUB-ATTR-4
ATTR-1 23 32 25 28
ATTR-2 78 45 48 41
ATTR-3 47 12 16 18
Please help I am a newbie to SQL
Sample Data
IF OBJECT_ID('tempdb..#Temp') IS NOT NULL
DROP TABLE #Temp
CREATE TABLE #Temp(ID INT, ATTR_NAME VARCHAR(20), SUB_ATTR_NAME VARCHAR(20),VALUE INT)
INSERT INTO #Temp
SELECT 1 ,'ATTR-1','SUB-ATTR-1', 23 UNION ALL
SELECT 2 ,'ATTR-1','SUB-ATTR-2', 32 UNION ALL
SELECT 3 ,'ATTR-1','SUB-ATTR-3', 25 UNION ALL
SELECT 4 ,'ATTR-1','SUB-ATTR-4', 28 UNION ALL
SELECT 5 ,'ATTR-2','SUB-ATTR-1', 78 UNION ALL
SELECT 6 ,'ATTR-2','SUB-ATTR-2', 45 UNION ALL
SELECT 7 ,'ATTR-2','SUB-ATTR-3', 48 UNION ALL
SELECT 8 ,'ATTR-2','SUB-ATTR-4', 41 UNION ALL
SELECT 9 ,'ATTR-3','SUB-ATTR-1', 47 UNION ALL
SELECT 10,'ATTR-3','SUB-ATTR-2', 12 UNION ALL
SELECT 11,'ATTR-3','SUB-ATTR-3', 16 UNION ALL
SELECT 12,'ATTR-3','SUB-ATTR-4', 18
SELECT * FROM #Temp
Using Dynamic Sql
DECLARE #Sql nvarchar(max),
#Col nvarchar(max),
#Col2 nvarchar(max)
SELECT #Col=STUFF((SELECT DISTINCT ', '+QUOTENAME(SUB_ATTR_NAME) FROM #Temp
FOR XML PATH ('')),1,1,'')
SELECT #Col2=STUFF((SELECT DISTINCT ', '+'MAX( '+QUOTENAME(SUB_ATTR_NAME)+' ) AS'+QUOTENAME(SUB_ATTR_NAME) FROM #Temp
FOR XML PATH ('')),1,1,'')
SET #Sql='
SELECT ATTR_NAME,'+#Col2+' FROM
(
SELECT * FROM #Temp
)AS SRC
PIVOT
(
SUM(VALUE) FOR SUB_ATTR_NAME IN ('+#Col+')
)AS PVT
GROUP BY ATTR_NAME'
PRINT #Sql
EXEC (#Sql)
Result
ATTR_NAME SUB-ATTR-1 SUB-ATTR-2 SUB-ATTR-3 SUB-ATTR-4
----------------------------------------------------------
ATTR-1 23 32 25 28
ATTR-2 78 45 48 41
ATTR-3 47 12 16 18
Demo :http://rextester.com/SURJ9296
You need to group your table by ATTR_NAME, then you can get sum of the SUB_ATTR
select attr_name ,
sum( case CASE
WHEN SUB_ATTR_NAME = 'SUB-ATTR-1' THEN VALUE
else 0 end) SUB-ATTR-1,
sum( case CASE
WHEN SUB_ATTR_NAME = 'SUB-ATTR-2' THEN VALUE
else 0 end) SUB-ATTR-2,
sum( case CASE
WHEN SUB_ATTR_NAME = 'SUB-ATTR-3' THEN VALUE
else 0 end) SUB-ATTR-3,
sum( case CASE
WHEN SUB_ATTR_NAME = 'SUB-ATTR-4' THEN VALUE
else 0 end) SUB-ATTR-4
END sum(value)
from TABLE
group by attr_name

Create different tables from 1 source table using Dynamic SQL

I have a sourcetable, from which I want to create different tables, using dynamic SQL.
Sourcetable:
ACCP_AcceptantID TransactionID Descrption Period AcceptantNumber
1 12 A 201801 16
1 13 AA 201801 16
2 21 B 201801 22
2 46 BB 201801 22
3 31 C 201801 54
3 38 CC 201801 54
4 94 D 201801 62
4 96 DD 201801 62
To be able to use a WHILE Loop to select each acceptant, I created a Table Acceptants:
SELECT DISTINCT ACCP_AcceptantNr
INTO Acceptants
FROM Sourcetable
ALTER TABLE #TussentabelAcceptanten ADD ID INT IDENTITY
ACCP_AcceptantNr ID
16 1
22 2
54 3
62 4
Desired Result (for each acceptantID, own table with content):
ACCP_AcceptantID TransactionID Descrption Period AcceptantNumber
1 12 A 201801 16
1 13 AA 201801 16
My query attempt:
DECLARE #SQL NVARCHAR(MAX),
#Acceptant NVARCHAR(40),
#Row INT = 1
WHILE #Row <= ( SELECT MAX(ID) FROM Acceptants)
BEGIN
SELECT #Acceptant = ACCP_AcceptantNr FROM Acceptants
SET #SQL = 'SELECT *
INTO MyDatabase.dbo.'+ Period + #Acceptant'
FROM #SourceTable
WHERE ACCP_AcceptantNr = '+ #Acceptant''
EXEC (#SQL)
SET #Row = #Row + 1
SET #SQL = ''
END
Error message: Incorrect syntax near '
Dynamic SQL always gets the better of me. I just don't get where to place the quotes. Suggestions would be great. Thanks.
You are missing the + symbols
SET #SQL = 'SELECT *
INTO MyDatabase.dbo.'+ Period + #Acceptant+'--Added +
FROM #SourceTable
WHERE ACCP_AcceptantNr = '+ #Acceptant -- Removed ''
Since you are Giving Period without an # symbol, I assume it is a column from some table, so add a selet from that table otherwise this will throw an error.
Like this
SELECT #SQL = 'SELECT *
INTO MyDatabase.dbo.'+ Period + #Acceptant+'--Added +
FROM #SourceTable
WHERE ACCP_AcceptantNr = '+ #Acceptant -- Removed ''
FROM YourTableName

SQL Loop to increment numbers

I'm struggling coming up with a way to solve this answer. I want to start at a specific value and keep increasing it by 1 every time a new line.
For example, if I have a table like so.
90
93
110
87
130
Etc..
I want to select the number 87 and then keep incrementing up from there but also read if the incremented number is there and skip it.
I am just struggling with trying to put the right logic together in my head. I know I need a while loop to keep reading through the table but I can't think of the proper way to go about it. Just looking for some suggestions to push me in the right direction.
Edit: I am using T-SQL for MSFT SQL Server 2012.
Here is an example of what the output should look like
90
93
110
87
130
88
89
91
92
94
It would skip over adding 90 and 93 because they already exist in the table.
I hope that makes sense to you guys.
I do it all in one recursive CTE and I make it so you can use order by and guarantee your results are returned in the correct order.
For the recursion, you can either choose and start and end number or #desiredNumberOfNewValues(keep in mind, it doesn't account for repeats). Let me know if you have any questions or need anything else.
DECLARE #yourTable TABLE (nums INT);
INSERT INTO #yourTable
VALUES (90),(93),(110),(87),(130);
DECLARE #Specific_Number INT = 87;
DECLARE #Last_Number INT = 94;
DECLARE #DesiredNumberOfNewValues INT = 7;
WITH CTE_Numbers
AS
(
SELECT 1 AS order_id,nums, 1 AS cnt
FROM #yourTable
UNION ALL
SELECT 2,
CASE
WHEN #Specific_Number + cnt NOT IN (SELECT * FROM #yourTable) --if it's not already in the table, return it
THEN #Specific_Number + cnt
ELSE NULL -- if it is in the table, return NULL
END,
cnt + 1
FROM CTE_Numbers
WHERE nums = #Specific_Number
--OR (cnt > 1 AND #Specific_Number + cnt < #Last_Number) --beginning and end(option 1)
OR (cnt > 1 AND cnt <= #DesiredNumberOfNewValues) --number of new values(option 2)
)
SELECT order_id,nums
FROM CTE_Numbers
WHERE nums IS NOT NULL
ORDER BY order_id,nums
Results:
order_id nums
----------- -----------
1 87
1 90
1 93
1 110
1 130
2 88
2 89
2 91
2 92
2 94
You might be able to do this with some modifications by using Numbers table. In SQL Server, you could do the following. Assuming Test has the data. To create Numbers table, please refer to the following link.
http://dataeducation.com/you-require-a-numbers-table/
SELECT n.Number,t.ColB
FROM Numbers n
LEFT JOIN Test t
ON n.Number = t.ColA
Where n.Number < (SELECT MAX(COLA) FROM Test t)
WITH Numbers_CTE (Number)
AS
(
SELECT 1 AS Number
UNION ALL
SELECT Number+1 FROM Numbers_CTE
)
SELECT top 10 *
FROM Numbers_CTE n
LEFT JOIN <TABLE> t ON t.ID=n.Number
WHERE t.ID IS NULL
AND n.Number BETWEEN 5 AND 100
There is a system table you can use for numbers called master..spt_values.
DECLARE sample TABLE
DECLARE #tbl TABLE(Id INT)
INSERT statement
INSERT INTO #tbl VALUES (90)
,(93)
,(110)
,(87)
,(130)
You can easily make a UNION to remove duplicates value you have in your table and ORDER it within a CTE
DECLARE #number INT = 87
;WITH C AS(
SELECT Id, Row_Id, ROW_NUMBER() OVER(PARTITION BY Id ORDER BY Id) AS Rn FROM (
SELECT 1 AS Row_Id, Id FROM #tbl
UNION
SELECT 2 AS Row_Id, number
FROM master..spt_values
WHERE [type] = 'P'
AND number >= #number
) t
)
SELECT Id FROM C
WHERE Rn = 1
ORDER BY Row_Id, Id
Output
Id
----
87
90
93
110
130
88
89
91
92
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
111
112
113
114
.
.
.
SQLFiddle

Average across multiple columns with varying data

I have 10 decimal columns and I would like to add a computed column to my table that contains the average of these 10. A complication is that not every record has all 10 columns filled in. Some records have 4 some have 8 and some have 10.
e.g.
ID D1 D2 D3 D4 D5 D6 D7 D8 D9 D10
1 12 19 13 14
2 32 53 34 54 65 34 12 09
3 41 54 33 61 71 12 09 08 08 12
How can I get the average of these where ID1 = 14.5, ID2 = 36.625 etc
I can't just do D1 + D2 + D3... / 10 as the 10 isn't always 10
The ideal would just be to do AVG(D1:D10) but clearly the world isn't ideal!
You can't use AVG aggregate function (because it works on rows) but you can calculate an average using the following query:
SELECT
(ISNULL(D1,0) + ISNULL(D2,0) +
ISNULL(D3,0) + ISNULL(D4,0) + ISNULL(D5,0) +
ISNULL(D6,0) + ISNULL(D7,0) + ISNULL(D8,0) +
ISNULL(D9,0) + ISNULL(D10,0)) /
CASE
WHEN
D1 IS NOT NULL
OR D2 IS NOT NULL
OR D3 IS NOT NULL
OR D4 IS NOT NULL
OR D5 IS NOT NULL
OR D6 IS NOT NULL
OR D7 IS NOT NULL
OR D8 IS NOT NULL
OR D9 IS NOT NULL
OR D10 IS NOT NULL
THEN
(
CASE
WHEN D1 IS NOT NULL THEN 1 ELSE 0
END +
CASE
WHEN D2 IS NOT NULL THEN 1 ELSE 0
END +
CASE
WHEN D3 IS NOT NULL THEN 1 ELSE 0
END +
CASE
WHEN D4 IS NOT NULL THEN 1 ELSE 0
END +
CASE
WHEN D5 IS NOT NULL THEN 1 ELSE 0
END +
CASE
WHEN D6 IS NOT NULL THEN 1 ELSE 0
END +
CASE
WHEN D7 IS NOT NULL THEN 1 ELSE 0
END +
CASE
WHEN D8 IS NOT NULL THEN 1 ELSE 0
END +
CASE
WHEN D9 IS NOT NULL THEN 1 ELSE 0
END +
CASE
WHEN D10 IS NOT NULL THEN 1 ELSE 0
END
)
ELSE 1
END
FROM yourtable
AVG for each id:
select id, avg(d) from
(
select id, id1 as d from tablename
union all
select id, id2 as d from tablename
union all
select id, id3 as d from tablename
union all
select id, id4 as d from tablename
union all
select id, id5 as d from tablename
union all
select id, id6 as d from tablename
union all
select id, id7 as d from tablename
union all
select id, id8 as d from tablename
union all
select id, id9 as d from tablename
union all
select id, id10 as d from tablename)
group by id
Use Values table valued constructor to unpivot the data then find average per ID. Try this
select id,avg(data) from Yourtable
cross apply
(values(D1), (D2), (D3), (D4), (D5), (D6) ,(D7), (D8), (D9) ,(D10)) cs (data)
group by id
Or if your want decimal values then use this.
select id,sum(data)/sum(case when data is not null then 1.0 else 0 end) from Yourtable
cross apply
(values(D1), (D2), (D3), (D4), (D5), (D6) ,(D7), (D8), (D9) ,(D10)) cs (data)
group by id

SQL Server query for finding the sum of 4 consecutive values

Can somebody help me in finding the sum of 4 consecutive values i.e rolling sum of last 4 values.
Like:
VALUE SUM
1 NULL
2 NULL
3 NULL
4 10
5 14
6 18
7 22
8 26
9 30
10 34
11 38
12 42
13 46
14 50
15 54
16 58
17 62
18 66
19 70
20 74
21 78
22 82
23 86
24 90
25 94
26 98
27 102
28 106
29 110
30 114
31 118
32 122
33 126
34 130
35 134
36 138
37 142
38 146
Thanks,
select sum(select top 4 Value from [table] order by Value Desc)
or, perhaps
select sum(value)
from [Table]
where Value >= (Max(Value) - 4)
I haven't actually tried either of those- and can't at the moment, but they should get you pretty close.
Quick attempt, which gets the results you've posted in your question (except the 1st 3 rows are not NULL). Assumes that VALUE field is unique and in ascending order:
-- Create test TABLE with 38 values in
DECLARE #T TABLE (Value INTEGER)
DECLARE #Counter INTEGER
SET #Counter = 1
WHILE (#Counter <= 38)
BEGIN
INSERT #T VALUES(#Counter)
SET #Counter = #Counter + 1
END
-- This gives the results
SELECT t1.VALUE, x.Val
FROM #T t1
OUTER APPLY(SELECT SUM(VALUE) FROM (SELECT TOP 4 VALUE FROM #T t2 WHERE t2.VALUE <= t1.VALUE ORDER BY t2.VALUE DESC) x) AS x(Val)
ORDER BY VALUE
At the very least, you should see the kind of direction I was heading in.
Assuming ID can give you the last 4 rows.
SELECT SUM([SUM])
FROM
(
SELECT TOP 4 [SUM] FROM myTable ORDER BY ID DESC
) foo
Each time you query it, it will read the last 4 rows.
If this is wrong (e.g. you want the sum of each consecutive 4 rows), then please give sample output
Following would work if your Value column is sequential
;WITH q (Value) AS (
SELECT 1
UNION ALL
SELECT q.Value + 1
FROM q
WHERE q.Value < 38
)
SELECT q.Value
, CASE WHEN q.Value >= 4 THEN q.Value * 4 - 6 ELSE NULL END
FROM q
otherwise you might use something like this
;WITH q (Value) AS (
SELECT 1
UNION ALL
SELECT q.Value + 1
FROM q
WHERE q.Value < 38
)
, Sequential (ID, Value) AS (
SELECT ID = ROW_NUMBER() OVER (ORDER BY Value)
, Value
FROM q
)
SELECT s1.Value
, [SUM] = s1.Value + s2.Value + s3.Value + s4.Value
FROM Sequential s1
LEFT OUTER JOIN Sequential s2 ON s2.ID = s1.ID - 1
LEFT OUTER JOIN Sequential s3 ON s3.ID = s2.ID - 1
LEFT OUTER JOIN Sequential s4 ON s4.ID = s3.ID - 1
Note that the table qin the examples is a stub for your actual table. The actual statement then becomes
;WITH Sequential (ID, Value) AS (
SELECT ID = ROW_NUMBER() OVER (ORDER BY Value)
, Value
FROM YourTable
)
SELECT s1.Value
, [SUM] = s1.Value + s2.Value + s3.Value + s4.Value
FROM Sequential s1
LEFT OUTER JOIN Sequential s2 ON s2.ID = s1.ID - 1
LEFT OUTER JOIN Sequential s3 ON s3.ID = s2.ID - 1
LEFT OUTER JOIN Sequential s4 ON s4.ID = s3.ID - 1