Making columns from one column's data in SQL Server - sql

I have a SQL Server table like this
ID amount type
1 10 material
1 5 spare parts
1 5 material
I need to make a query and get outputs like this
ID material spare parts
1 15 5
But I have too many IDS and types, so I need to add items dynamically whatever their count.

You are looking for a dynamic pivot. Basically this works selecting the list of types from the table, then building a query from that information. You can then execute the query with sp_executesql.
For your table structure:
declare #sql nvarchar(max);
select #sql = string_agg(
'sum(case when type = ''' + type + ''' then amount else 0 end) [' + type + ']',
', ')
from (select distinct type from mytable) t;
set #sql = N'select id, ' + #sql + ' from mytable group by id';
select #sql; -- debug
-- exec sp_executesql #sql; -- execute for real
For your sample data, this generates the following query (I added line breaks added for readability):
select
id,
sum(case when type = 'material' then amount else 0 end) [material],
sum(case when type = 'spare parts' then amount else 0 end) [spare parts]
from mytable
group by id
After execution, you get result:
id | material | spare parts
-: | -------: | ----------:
1 | 15 | 5
Demo on DB Fiddle

Related

Transforming an SQL table

I am trying to write a nice SQL query, but I am failing. I am trying to create a select statement that returns the following:
Date | Cash | Checking | Savings
--------------------------------------
May 2017 | 30 | 300 | 3000
Apr 2017 | 40 | 400 | 4000
My tables are like this:
Balances
* id
* date
* amount
* item_id
BalanceItems
* id
* name
So the column names (cash, checking and savings for example) are stored in BalanceItems. Basically I don't know how I can get the name from BalanceItems in the column header.
I am building this in Django, so ideas of how to elegantly do this there are also appreciated. These are the models:
class Balances(models.Model):
item = models.ForeignKey('BalanceItems', related_name='balances_balance_items')
date = models.DateField()
amount = models.DecimalField(max_digits=9, decimal_places=2)
class BalanceItems(models.Model):
name = models.CharField(max_length=100, unique=True)
If we know the expected values, we can use a pivot:
select [date], [Cash], [Checking], [Savings]
from
(
select [date]
, amount
, name
from Balances b
inner join BalanceItems bi
on bi.id = b.item_id
) source
pivot
(
sum(amount)
for name in ([Cash], [Checking], [Savings])
) pvt
order by [date]
Otherwise we need dynamic sql; though the solution's otherwise the same as the one above:
declare #sql nvarchar(max)
select #sql = coalesce(#sql + ', ', '') + quotename(name)
from #BalanceItems
set #sql = '
select [date], ' + #sql + '
from
(
select [date]
, amount
, name
from Balances b
inner join BalanceItems bi
on bi.id = b.item_id
) source
pivot
(
sum(amount)
for name in (' + #sql + ')
) pvt
order by [date]
'
exec (#sql)
It seems I might be able to use pivot to get my desired result but that command doesn't exist in sqlite. I don't want to use case statements because the amount of columns can change.
So I have ended up accepting that this one view is going to do a whole bunch of database calls and use Python to keep everything organized.

Querying a junction/link/bridge table, column data as header

I have a list of specialties (upwards of 20)
SpecialtyID Description
------------------------
1 Specialty1
2 Specialty2
3 Specialty3
I have a list of providers (upwards of 50)
ProviderID Name
------------------------
1 Tom
2 Maria
3 Pat
Each provider can have multiple specialties, each specialty can have multiple providers - so a many to many relationship.
I have a junction/link/bridge table called SpecialtyProvider and if I simply query the link table with the following query, I get the table below.
SELECT SpecialtyID, ProviderID FROM SpecialtyProvider
SpecialtyID ProviderID
------------------------
1 1
2 1
3 1
1 2
2 2
3 3
What I would like to do, is pull out the data formatted like so:
SpecialtyID ProviderID=1 ProviderID=2 ProviderID=3 ProviderID=x
-----------------------------------------------------------
1 true true NULL
2 true true NULL
3 true NULL true
Once I can format the data correctly, I'll be dumping this into an ASP ListView.
I am not quite sure how to proceed. I have read 100 posts about different variations of the PIVOT command, but where I don't have an aggregate function, I haven't been able to make any of the other examples/solutions/groupings make sense.
If you need to pivot without using an aggregate, you can usually just use MAX (you're essentially taking the MAX of a single value, which is just that same value).
select SpecialtyID, case when [1] is not null then 'true' end 'ProviderID=1',
case when [2] is not null then 'true' end 'ProviderID=2',
case when [3] is not null then 'true' end 'ProviderID=3'
from (
select s.SpecialtyID, s.Description, sp.ProviderID
from Specialty s
join SpecialtyProvider sp on sp.SpecialtyID = s.SpecialtyID
) x
pivot(
MAX(Description)
for ProviderID in ([1],[2],[3])
) pvt
SQL Fiddle
However, it's also possible to get the same results without using PIVOT at all:
select s.SpecialtyID,
Max(case when sp.ProviderID = 1 then 'true' end) 'ProviderID=1',
Max(case when sp.ProviderID = 2 then 'true' end) 'ProviderID=2',
Max(case when sp.ProviderID = 3 then 'true' end) 'ProviderID=3'
from Specialty s
join SpecialtyProvider sp on sp.SpecialtyID = s.SpecialtyID
group by s.SpecialtyID
I find this easier to read, and it will probably be faster as well.
SQL Fiddle
With all that said, you may want to reconsider your UI. Having a table 50 columns wide will be difficult for a user to process. It might make sense to filter the data so the user can only view specific portions of it. Also, if you're dealing with a variable number of providers, it may make sense to pull all the data up to the web server and process it in your ASP codebehind.
The following blog post introduces the concept of a dynamic pivot where you do not have to specify you columns so as to address the X factor for Providers. http://beyondrelational.com/modules/2/blogs/70/posts/10840/dynamic-pivot-in-sql-server-2005.aspx
I took it a bit further and also print out the generated SQL. Here is what I came up for to address your example above.
IF (OBJECT_ID(N'dynamic_pivot', N'P') IS NOT NULL)
DROP PROCEDURE dynamic_pivot
GO
CREATE PROCEDURE dynamic_pivot
(
#select VARCHAR(2000)
, #PivotCol VARCHAR(100)
, #Summaries VARCHAR(100)
, #GenerateScript BIT = 1
)
AS
BEGIN
SET NOCOUNT ON ;
DECLARE #pivot VARCHAR(MAX)
, #sql VARCHAR(MAX)
SELECT #select = REPLACE(#select, 'SELECT ',
'SELECT ' + #PivotCol + ' AS pivot_col, ')
CREATE TABLE #pivot_columns
(
pivot_column VARCHAR(100)
)
SELECT #sql = 'SELECT DISTINCT pivot_col FROM (' + #select + ') AS t'
INSERT INTO #pivot_columns
EXEC ( #sql
)
SELECT #pivot = COALESCE(#pivot + ',', '') + '[' + pivot_column + ']'
FROM #pivot_columns
SELECT #sql = '
SELECT *
FROM
(
' + #select + '
) AS t
PIVOT
(
' + #Summaries + ' for pivot_col in (' + #pivot + ')
) AS p'
PRINT #sql
EXEC(#sql)
END
GO
EXEC [dbo].[dynamic_pivot] #select = 'SELECT SpecialtyID, 1 AS hasSpecialty FROM SpecialtyProvider', -- varchar(2000)
#PivotCol = 'ProviderID', -- varchar(100)
#Summaries = 'COUNT(hasSpecialty)' -- varchar(100)
The resulting query that is displayed in your message window in SSMS is the following:
SELECT *
FROM
(
SELECT ProviderID AS pivot_col, SpecialtyID, 1 AS hasSpecialty FROM SpecialtyProvider
) AS t
PIVOT
(
COUNT(hasSpecialty) for pivot_col in ([1],[2],[3])
) AS p
You can modify this to give you the column names and values that are required.

Not able to combine multiple rows into single row based on certain conditions

In the image above, i have shown table structure i use to store result of student. However I need to select data in such a manner such that depending on particular FEID(examination ID),
I get marks obtained and subID of single student in single row. Something like below:
FEID SubID1 MarksObtained SubID2 MarksObtained SubID3 MarksObtained StdID
2 1 0 2 0 3 0 50
2 1 45 2 45 3 45 51
Result Column wont affect outcome as for a particular stdID and FEID it remains same for no matter how many SubID are there.
Basically I am storing each subject marks in single row and subjects are can be any number( more than 3 as in this case) , which is not known before hand. But for each I create one row to enter its marks
I tried sytax below .
DECLARE #cols nvarchar(MAX);
--get the list of subids from the table
SELECT #cols = SubjectName from tbSubjects where SubID IN(select distinct SubID from tbFinalMarks);
Declare #sql nvarchar(MAX) = 'SELECT StdId, FEID, ' + #cols + 'FROM
(
SELECT * FROM tbFinalMarks
)t
PIVOT
(
MAX(MarksObtained) FOR SubId IN (' + #cols + ')
)p';
Something like this will do it. It will also dynamically add new columns for new sub ids without you needing to worry about it.
DECLARE #cols nvarchar(MAX);
--get the list of subids from the table
SELECT #cols = COALESCE(#cols + ',', '') + '[' + CAST(SubId AS nvarchar) + ']' FROM (SELECT DISTINCT SubId FROM table);
Declare #sql nvarchar(MAX) = 'SELECT StdId, FEID, ' + #cols + 'FROM
(
SELECT * FROM table
)t
PIVOT
(
MAX(MarksObtained) FOR SubId IN (' + #cols + ')
)p';
EXECUTE sp_executesql #sql;
Although you can use pivot, I think the explicit aggregation approach is easier to construct:
select feid,
1 as SubId_1,
max(case when SubId = 1 then MarksObtained end) as MarksObtained_1,
2 as SubId_2,
max(case when SubId = 2 then MarksObtained end) as MarksObtained_2,
3 as SubId_3,
max(case when SubId = 3 then MarksObtained end) as MarksObtained_3,
stdid
from table t
group by feid, stdid;

Dynamic pivot data with multiple datatypes

I have a trick problem with a pivot table to make:
I have a table which looks like:
id table object name type nvarchar date int bit
1 1 2 name 1 tables NULL NULL NULL
2 1 2 name 1 columns NULL NULL NULL
3 1 2 name 1 datatypes NULL NULL NULL
4 1 2 name 1 _users NULL NULL NULL
1 1 3 active 3 NULL NULL NULL 1
2 1 3 active 3 NULL NULL NULL 1
3 1 3 active 3 NULL NULL NULL 1
4 1 3 active 3 NULL NULL NULL 1
the output should look like:
id name active
1 tables 1
2 columns 1
3 datatypes 1
4 _users 1
Based upon the "type" I should put the correct data from the column in it, these columns are formated in nvarchar, bit, datetime, int, ect.
The "id" is the row id, the "name, active" comes from the name column and the values from nvarchar, date, int and bit columns.
UPDATE: the columns like nvarchar, date, int and bit (and most other SQL formats) are actually contain this type of data. The column "type" gives which column contains the data to being used, so if "type" is "1", than I want to use the "nvarchar" if "type" is "3" than I want to use the "bit" which contains really a bit and not a nvarchar. In the Pivot I want to have the bit under "active" column, if I have in the example a 3th column (name) for example "activation_date" I want to see a third column with the value (type = 2) from the date column.
I am lost in this, please help
Assuming there's only one not null column for each row:
with cte as (
select
id,
name,
coalesce(
[nvarchar],
convert(nvarchar(max), [date], 120),
cast([int] as nvarchar(max)),
cast([bit] as nvarchar(max))
) as value
from Table1 as t
)
select
id,
max(case when [name] = 'name' then value end) as [name],
max(case when [name] = 'active' then value end) as [active]
from cte
group by id
sql fiddle demo
But I must warn you, this types of database schema is not best way to use SQL.
If you want to do this dynamically without hardcoding columns:
declare #stmt nvarchar(max)
select #stmt =
isnull(#stmt + ', ', '') +
'max(case when [name] = ''' + name + ''' then value end) as ' + quotename([name])
from (select distinct [name] from Table1) as t
select #stmt = '
with cte as (
select
id,
name,
coalesce(
[nvarchar],
convert(nvarchar(max), [date], 120),
cast([int] as nvarchar(max)),
cast([bit] as nvarchar(max))
) as value
from Table1 as t
)
select
id, ' + #stmt + '
from cte
group by id
'
exec sp_executesql
#stmt = #stmt
sql fiddle demo
If you have some Mapping table like this:
name value
--------------------
name nvarchar
active bit
you can use this query:
declare #stmt nvarchar(max)
select #stmt =
isnull(#stmt + ', ', '') +
'max(case when [name] = ''' + name + ''' then [' + value + '] end) as ' + quotename([name])
from Mapping
select #stmt = '
select
id, ' + #stmt + '
from Table1
group by id
'
exec sp_executesql
#stmt = #stmt
sql fiddle demo

Query Transposing certain rows into column names

I have a couple of tables which look like this
Table 1
user_id | name
-------------------------
x111 | Smith, James
x112 | Smith, Jane
etc..
Table 2
id | code | date | incident_code | user_id
-----------------------------------------------------------------
1 | 102008 | 10/20/2008 | 1 | x111
2 | 113008 | 11/30/2008 | 3 | x111
3 | 102008 | 10/20/2008 | 2 | x112
4 | 113008 | 11/30/2008 | 5 | x112
What i'd like to display is something like this
user_id | user_name | INCIDENT IN OCT 2008 | INCIDENT IN NOV 2008
------------------------------------------------------------------------------
x111 | Smith, John | 1 | 3
x112 | Smith, Jane | 2 | 5
etc..
The incident_code would be replaced by the actual description of the incident which is located in another table, but i thought i'd see how this would work first.
Some of the column headers would be static while others would be created based on the date.
Does anyone one know how i can do this using sql server 2005? Some examples would be very helpful.
Thanks in advance
Here's a solution which generates and runs the dynamic SQL with a PIVOT:
DECLARE #pivot_list AS VARCHAR(MAX)
--
;
WITH cols
AS ( SELECT DISTINCT
'INCIDENT IN ' + LEFT(UPPER(CONVERT(VARCHAR, [date], 107)),
3) + ' '
+ SUBSTRING(UPPER(CONVERT(VARCHAR, [date], 107)), 9, 4) AS col
FROM so926209_2
)
SELECT #pivot_list = COALESCE(#pivot_list + ', ', '') + '[' + col + ']'
FROM cols
--
DECLARE #template AS VARCHAR(MAX)
SET #template = 'WITH incidents AS (
SELECT [user_id],
incident_code,
''INCIDENT IN '' + LEFT(UPPER(CONVERT(VARCHAR, [date], 107)), 3)
+ '' '' + SUBSTRING(UPPER(CONVERT(VARCHAR, [date], 107)), 9, 4) AS col
FROM so926209_2
)
,results AS (
SELECT * FROM incidents PIVOT (MAX(incident_code) FOR col IN ({#pivot_list})) AS pvt
)
SELECT results.[user_id]
,so926209_1.[name]
,{#select_list}
FROM results INNER JOIN so926209_1 ON so926209_1.[user_id] = results.[user_id]
'
DECLARE #sql AS VARCHAR(MAX)
SET #sql = REPLACE(REPLACE(#template, '{#pivot_list}', #pivot_list), '{#select_list}', #pivot_list)
--PRINT #sql
EXEC (#sql)
Where so926209_1, so926209_2 are your table 1 and table 2
Note that if you have multiple incidents in a month for the same person, your example doesn't show how you want that handled. This example only takes the last incident in the month.
You want to Pivot
http://msdn.microsoft.com/en-us/library/ms177410.aspx
This sounds like a reporting task. Reporting, often referred to from a database perspective as OLAP, Online Aanalytical Processing, tends to differ quite frequently from "traditional" database access, OLTP (Online Transaction Processing) in that it is quite often made up of large aggregations of data spanning greater periods of time. Quite frequently, the kind of aggregation your looking for.
Use of a Pivot as Tetraneutron suggested will be sufficient for smaller data sets. However, as the volume of data you need to report on grows, you may need something more advanced. OLAP is provided for by SQL Server Analysis Services (SSAS), available in 2005 and 2008. Using SSAS you can create multidimensional data repositories that pre-aggregate data from either an OLTP database directly, or from an intermediary data warehouse database. Multidimensional data (usually referred to as cubes), provide a much faster way to access the kind of data you can get from a Pivot, without interfering with the performance of your standard transaction processing in your OLTP database.
If you have more than a small amount of data you need to report on, I recommend you check out SQL Server Analysis Services 2005, OLAP, Cubes, and MDX (Multidimensional Extensions for T-SQL.) There is a larger learnig curve to set up an OLAP Cube, but once it is set up, the benefits of having one can be huge if you have significant reporting needs.
A query like this would work:
select
u.User_id,
u.Name,
Okt2008Sum = sum(case when i.date between
'2008-10-01' and '2008-11-01' then 1 else 0 end),
Nov2008Sum = sum(case when i.date between
'2008-11-01' and '2008-12-01'then 1 else 0 end)
from #incidents i
inner join #users u on i.user_id = u.user_id
group by u.user_id, u.name
Depending on your client and how often you have to run it, you can generate this query. In SQL this would look like:
create table #months (
MonthName varchar(25),
StartDate datetime
)
insert into #months values ('Okt2008','2008-10-01')
insert into #months values ('Nov2008','2008-11-01')
declare #query varchar(8000)
select #query = 'select u.User_id, u.Name '
select #query = #query + ', ' + MonthName +
' = sum(case when i.date between ''' + cast(StartDate as varchar) +
''' and ''' + cast(dateadd(m,1,StartDate) as varchar) +
''' then 1 else 0 end) '
from #Months
select #query = #query + '
from #incidents i
inner join #users u on i.user_id = u.user_id
group by u.user_id, u.name'
exec (#query)