how to convert multiple rows to single row? - sql

How to get sql with Query
I have three tables in the form below
Table A
ID | Title |Count
--- |-------- |-----
1 |Mouse | 50
2 |pen | 60
Table B
ID | CompName|
--- |---------|
1 |Comp1 |
2 | Comp2 |
3 |Comp3 |
Table T
|---------------------|
|IDA | IDB | CountT|
|-------|-----|-------|
|1 | 1 | 5 |
|2 | 1 | 6 |
|1 | 2 | 7 |
+---------------------+
I want to make such a report
| object | Copm1 | Comp2 | Comp3 |Sum|remaining |
|--------|-------|-------|-------|---|--------- |
| Mouse | 5 | 7 | 0 | 12| 38 |
| pen | 6 | 0 | 0 | 6 | 54 |
My answer to my question
I was able to get the final answer using the PIVOT function
DECLARE #SQLQuery AS NVARCHAR(MAX)
DECLARE #PivotColumns AS NVARCHAR(MAX)
SELECT #PivotColumns= COALESCE(#PivotColumns + ',','') +
QUOTENAME(CompName) from B
set #SQLQuery=N'select pvt.title as object, ' + #PivotColumns + '
FROM
(select title, CountT,CompName
from T
inner join A on T.IDA = A.ID
inner join B on B.ID = T.IDA) AS src PIVOT
(
max(CountT)
FOR CompName IN (' + #PivotColumns + ')
) AS pvt;'
EXEC sp_executesql #SQLQuery

select a.title as object,
sum(case when b.id=1 then T.countT else 0 end) as Comp1,
sum(case when b.id=2 then T.countT else 0 end) as Comp2,
sum(case when b.id=3 then T.countT else 0 end) as Comp3,
sum(t.countt) as 'Sum',
max(a.count)-sum(t.countt) as Remaining
from TableT t
inner join tableA A on a.id=t.IDA
inner join TableB b on b.id=t.IDB
group by a.Title
http://rextester.com/l/sql_server_online_compiler

Related

How do I create this pivot SQL query?

I have the following tables in our SQL Server 2012 instance:
tblASSETS
------------------------------------
| ASSETID | ASSETTYPE | NAME |
|---------|-----------|------------|
| 1 | A | Printer A |
| 2 | A | Printer B |
| 3 | A | Printer C |
| 4 | B | Laptop A |
------------------------------------
tblASSETTYPES
--------------------------------------
| ASSETTYPE | TYPENAME | ICON |
|-----------|----------|-------------|
| A | Printer | Printer.png |
| B | Laptop | Laptop.png |
--------------------------------------
tblASSETCUSTOM
-------------------------------------------------------------
| CUSTOMID | ASSETID | MAKE | MODEL | PRINTEDPAGES |
|----------|---------|------|----------------|--------------|
| 1 | 1 | HP | Laserjet 4v | 530 |
| 2 | 2 | HP | Laserjet 4v | 10000 |
| 3 | 3 | HP | Officejet 1050 | NULL |
| 4 | 4 | HP | Probook 430 G3 | NULL |
-------------------------------------------------------------
tblOIDDATA
---------------------------------------------
| OIDDATAID | ASSETID | LABEL | DATA |
|-----------|---------|--------------|------|
| 1 | 1 | Black copies | 430 |
| 2 | 1 | Color copies | 110 |
| 3 | 2 | Black copies | 5300 |
| 4 | 2 | Scans | 450 |
---------------------------------------------
I want to build a query which returns all printers and all their details as columns. I already created this QUERY:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT ',' + QUOTENAME(label)
from tblOIDData
group by label
order by label
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'Select * from ( Select Top 1000000 tAT.icon As icon, tat.typename as [ASSET TYPE] ,tA.Name as [ASSET NAME], tac.Model as [DEVICE MODEL], snmp.label as label, TRY_CONVERT(INT, TRY_CONVERT(NVARCHAR(100), snmp.data)) as PageCount, TA.Printepages as [PRINTED PAGES] from tblAssets as tA, tblAssetCustom as tAC, tsysAssetTypes as tAT, tblOIDData as SNMP where tA.AssetType = tAT.AssetType AND tAT.Typename = ''Printer'' AND tAC.AssetID = tA.AssetID AND snmp.AssetID = tA.AssetID ) as s PIVOT ( sum(PageCount) for [LABEL] IN (' + #cols + ') ) AS pvt'
execute(#query);
This almost give the desired result. The only thing I'm facing is that ASSETID 3 (Printer C) is not in the result. Probably because it is not in the tblOIDData table.
How can I include this Asset in my results also?
You could use a LEFT JOIN in your dynamic sql.
set #query = N'SELECT *
FROM
(
SELECT TOP 1000000
tAT.icon AS [icon],
tat.typename AS [ASSET TYPE],
tA.Name AS [ASSET NAME],
tac.Model AS [DEVICE MODEL],
snmp.label AS [label],
TRY_CONVERT(INT, TRY_CONVERT(NVARCHAR(100), snmp.data)) AS [PageCount],
TA.Printepages AS [PRINTED PAGES]
FROM tblAssets AS tA
JOIN tblAssetCustom AS tAC ON tAC.AssetID = tA.AssetID
JOIN tsysAssetTypes AS tAT ON tAT.AssetType = tA.AssetType
LEFT JOIN tblOIDData AS SNMP ON snmp.AssetID = tA.AssetID
WHERE tAT.Typename = ''Printer''
) AS src
PIVOT
(
SUM([PageCount])
FOR [label] IN (' + #cols + N')
) AS pvt';

Transforming Data From 2 Tables into 1

I'm working with a database that allows the storage of "Custom Property" fields with each record in an "Item" table. This is done by having preset fields called [CustomString00] through [CustomString199], [CustomNumber00] through [CustomNumber199] and [CustomDate00] through [CustomDate199] in the Item table. There is another table called the "CustomProperty" table that assigns the name to each custom field and the column to use in the Item table. Here is how it looks.
Item:
| Id | CustomString00| ... | CustomString199 | CustomNumber00 | ... | CustomNumber199 | CustomDate00 | ... | CustomDate199 |
| 1 | 'IN REPAIR' | ... | NULL | 78.4 | ... | NULL | 2017-03-04 | ... | NULL |
| 2 | 'FINISHED' | ... | NULL | 68.5 | ... | NULL | 2017-03-05 | ... | NULL |
| 3 | 'WIP' | ... | NULL | NULL | ... | NULL | 2017-03-07 | ... | NULL |
CustomProperty:
| Name | Type| ColumnName |
| 'Status' | 0 | 'CustomString00' |
| 'Temperature' | 1 | 'CustomNumber00' |
| 'Made Date' | 2 | 'CustomDate00' |
For each Custom Property that is defined, there will be a record in the CustomProperty table that will indicate what data type it is and which column to use for that property. Currently, there could be up to 200 Custom Properties defined for each type, ie, 200 Text, 200 Date and 200 Numeric. The user defines the Custom Properties as they need them. If a user is only using 55 total custom properties, then a lot of the fields in the Item table will not be used.
I would like to create a view that is more 'friendly' so that our users can create their own reports to show these properties. This view would use these two tables to create a new table that looked like this:
| Id | Status | Temperature | Made Date |
| 1 | 'IN REPAIR' | 78.4 | 2017-03-04 |
| 2 | 'FINISHED' | 68.5 | 2017-03-05 |
| 3 | 'WIP' | NULL | 2017-03-07 |
This view should show a column for each property that is defined in the Custom Property table. For This example, there are only 3 Custom Properties defined, so 3 fields are shown in this view. If all 600 Custom Properties were defined, then there would be 600 fields in this view. If there is a value stored for that Custom Property in the Item table, then that value is shown. If there is no value then a NULL would be shown for that property (as shown in Temperature for Item 3).
Using Dynamic SQL I've got some results, but not what I'm looking for. I've made a query that Unpivots the Custom Property fields and returns a result of Items like this:
| Id | CPName | CPTextValue | CPNumberValue | CPDateValue |
| 1 | 'Status' | 'IN REPAIR' | NULL | NULL |
| 1 | 'Temperature' | NULL | 78.4 | NULL |
| 1 | 'Made Date' | NULL | NULL | 2017-03-04 |
| 2 | 'Status' | 'FINISHED ' | NULL | NULL |
| 2 | 'Temperature' | NULL | 68.5 | NULL |
| 2 | 'Made Date' | NULL | NULL | 2017-03-05 |
| 3 | 'Status' | 'WIP' | NULL | NULL |
| 3 | 'Made Date' | NULL | NULL | 2017-03-07 |
My query is getting pretty complicated, so I'm wondering if I'm taking the wrong approach. Here is what I've done so far.
DECLARE #textcolsUnpivot AS NVARCHAR(MAX),
#datecolsUnpivot AS NVARCHAR(MAX),
#numbercolsUnpivot AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #textcolsUnpivot
= stuff((select ','+quotename(columnname)
from customproperty
where custompropertytype = 0
order by columnname
for xml path('')), 1, 1, '')
select #datecolsUnpivot
= stuff((select ','+quotename(columnname)
from customproperty
where custompropertytype = 1
order by columnname
for xml path('')), 1, 1, '')
select #numbercolsUnpivot
= stuff((select ','+quotename(columnname)
from customproperty
where custompropertytype = 2
order by columnname
for xml path('')), 1, 1, '')
set #query
= 'select id, CPName, CPTextValue, NULL as CPDateValue, NULL as CPNumberValue from
(select id, CPTextValue, CPCol from item
unpivot
(
CPTextValue
for CPCol in ('+ #textcolsunpivot +')
) unpiv ) as pv
inner join
(select columnname, name as CPName, custompropertytype from customproperty) as cp
on cp.columnname = pv.CPCol
union
select id, CPName, NULL, CPDateValue, NULL from
(select id, CPDateValue, CPCol from item
unpivot
(
CPDateValue
for CPCol in ('+ #datecolsunpivot +')
) unpiv ) as pv
inner join
(select columnname, name as CPName, custompropertytype from customproperty) as cp
on cp.columnname = pv.CPCol
union
select id, CPName, NULL, NULL, CPNumberValue from
(select id, CPNumberValue, CPCol from item
unpivot
(
CPNumberValue
for CPCol in ('+ #numbercolsunpivot +')
) unpiv ) as pv
inner join
(select columnname, name as CPName, custompropertytype from customproperty) as cp
on cp.columnname = pv.CPCol
'
exec sp_executesql #query;
For additional clarification, the schema of the tables are:
Item:
Id - pk, (it's actually a GUID, but I'm using an int for this example.), not null
CustomString00 through CustomString199 - nvarchar(max), null
CustomDate00 through CustomDate199 - datetime, null
CustomNumber00 through CustomNumber199 - float, null
CustomProperty:
Name - nvarchar(100),not null
Type - int, not null
ColumnName - nvarchar(50), not null
If I was to continue my current approach, I think I need to now PIVOT the results of my previous query to put it in the form that I'm looking for. Is this correct?

SUM values in SQL starting from a specific point in another table

I have a table that lists the index/order, the name, and the value. For example, it looks like this:
TABLE1:
ID | NAME | VALUE
1 | A | 2
2 | B | 5
3 | C | 2
4 | D | 7
5 | E | 0
Now, I have another table that has a random list of NAMEs. It'll just show either A, B, C, D, or E. Depending on what the NAME is, I wanted to calculate the SUM of all the values that it will take to get to E. Does that make sense?
So if for example, my table looks like this:
TABLE2:
NAME
D
B
A
I'd want another column next to NAME that'll show the sum. So D would have 7 because the next event is E. B would have to be the sum of 5, 2, and 7 because B is 5, and C is 2, and D is 7. And A would have the sum of 2, 5, 3, and 7 and so on.
Hopefully this is easy to understand.
I actually don't have much at all aside from joining the two tables and getting the current value of the NAME. But I wasn't sure how to increment and so on and keep adding?
SELECT T2.NAME, T1.VALUE
FROM Table1 T1
LEFT JOIN Table2 T2 ON T1.NAME = T2.NAME
Is doing this even possible? Or am I wasting my time? Should I be referring to actual code to do this? Or should I make a function?
I wasn't sure where to start and I was hoping someone could help me out.
Thank you in advance!
The query is in two parts; this is hard to see at first, so I'll walk through each step.
Step 1: Obtain the rolling sum
Join table1 to itself for any letters greater than itself:
select *
from table1 t1
inner join table1 t2 on t2.name >= t1.name
order by t1.name
This produces the following table
+ -- + ---- + ----- + -- + ---- + ----- +
| id | name | value | id | name | value |
+ -- + ---- + ----- + -- + ---- + ----- +
| 1 | A | 2 | 1 | A | 2 |
| 1 | A | 2 | 2 | B | 5 |
| 1 | A | 2 | 3 | C | 2 |
| 1 | A | 2 | 4 | D | 7 |
| 1 | A | 2 | 5 | E | 0 |
| 2 | B | 5 | 2 | B | 5 |
| 2 | B | 5 | 3 | C | 2 |
| 2 | B | 5 | 4 | D | 7 |
| 2 | B | 5 | 5 | E | 0 |
| 3 | C | 2 | 3 | C | 2 |
| 3 | C | 2 | 4 | D | 7 |
| 3 | C | 2 | 5 | E | 0 |
| 4 | D | 7 | 4 | D | 7 |
| 4 | D | 7 | 5 | E | 0 |
| 5 | E | 0 | 5 | E | 0 |
+ -- + ---- + ----- + -- + ---- + ----- +
Notice that if we group by the name from t1, we can get the rolling sum by summing the values from t2. This query
select t1.name,
SUM(t2.value) as SumToE
from table1 t1
inner join table1 t2
on t2.name >= t1.name
group by t1.name
gives us the rolling sums we want
+ ---- + ------ +
| name | sumToE |
+ ---- + ------ +
| A | 16 |
| B | 14 |
| C | 9 |
| D | 7 |
| E | 0 |
+ ---- + ------ +
Note: This is equivalent to using a windowed function that sums over a set, but it is much easier to visually see what you're doing via this joining technique.
Step 2: Join the rolling sum
Now that you have this rolling sum for each letter, you simply join it to table2 for the letters you want
select t1.*
from table2 t2
inner join (
select t1.name,
SUM(t2.value) as SumToE
from table1 t1
inner join table1 t2
on t2.name >= t1.name
group by t1.name
) t1 on t1.name = t2.name
Result:
+ ---- + ------ +
| name | sumToE |
+ ---- + ------ +
| A | 16 |
| B | 14 |
| D | 7 |
+ ---- + ------ +
As gregory suggests, you can do this with a simple windowed function, which (in this case) will sum up all the rows after and including the current one based on the ID value. Obviously there are a number of different ways in which you can slice your data, though I'll leave that up to you to explore :)
declare #t table(ID int,Name nvarchar(50),Val int);
insert into #t values(1,'A',2),(2,'B',5),(3,'C',2),(4,'D',7),(5,'E',0);
select ID -- The desc makes the preceding work the right way. This is
,Name -- essentially shorthand for "sum(Val) over (order by ID rows between current row and unbounded following)"
,Val -- which is functionally the same, but a lot more typing...
,sum(Val) over (order by ID desc rows unbounded preceding) as s
from #t
order by ID;
Which will output:
+----+------+-----+----+
| ID | Name | Val | s |
+----+------+-----+----+
| 1 | A | 2 | 16 |
| 2 | B | 5 | 14 |
| 3 | C | 2 | 9 |
| 4 | D | 7 | 7 |
| 5 | E | 0 | 0 |
+----+------+-----+----+
CREATE TABLE #tempTable2(name VARCHAR(1))
INSERT INTO #tempTable2(name)
VALUES('D')
INSERT INTO #tempTable2(name)
VALUES('B')
INSERT INTO #tempTable2(name)
VALUES('A')
CREATE TABLE #tempTable(id INT, name VARCHAR(1), value INT)
INSERT INTO #temptable(id,name,value)
VALUES(1,'A',2)
INSERT INTO #temptable(id,name,value)
VALUES(2,'B',5)
INSERT INTO #temptable(id,name,value)
VALUES(3,'C',2)
INSERT INTO #temptable(id,name,value)
VALUES(4,'D',7)
INSERT INTO #temptable(id,name,value)
VALUES(5,'E',0)
;WITH x AS
(
SELECT id, value, name, RunningTotal = value
FROM dbo.#temptable
WHERE id = (SELECT MAX(id) FROM #temptable)
UNION ALL
SELECT y.id, y.value, y.name, x.RunningTotal + y.value
FROM x
INNER JOIN dbo.#temptable AS y ON
y.id = x.id - 1
)
SELECT x.id, x.value, x.name, x.RunningTotal
FROM x
JOIN #tempTable2 t2 ON
x.name = t2.name
ORDER BY x.id
DROP TABLE #tempTable
DROP TABLE #tempTable2

SQL server: Transpose Rows to Columns (n:m relationship)

After trying it myself for some hours now I need to ask for help. I only did some basic SQL until now.
I want to solve the following:
(I have translated a couple of things for you to understand the context)
I have three tables:
Workers (Mitarbeiter in German - mitID)
| mitID | Name | FamName | DOB | abtIDref |
|-------|--------|---------|------------|----------|
| 1 | Frank | Sinatra | 12.12.1915 | 1 |
| 2 | Robert | Downey | 4.4.1965 | 2 |
INFO: abtIDref is an 1:n relation for the Workplace, but not involved here
Skills (Faehigkeiten in German - faeID)
| faeID | Descr | time | cost |
|-------|-------|------|------|
| 1 | HV | 2 | 0 |
| 2 | PEV | 1 | 0 |
| 3 | Drive | 8 | 250 |
| 4 | Nex | 20 | 1200 |
Link-List
| linkID | mitIDref | feaIDref | when |
|--------|----------|----------|------------|
| 1 | 2 | 1 | 27.07.2014 |
| 2 | 2 | 2 | 01.01.2016 |
| 3 | 2 | 3 | 20.01.2016 |
| 4 | 1 | 3 | 05.06.2015 |
| 5 | 1 | 4 | 02.11.2015 |
The desired result is:
| mitID | Name | FamName | DOB | abtIDref | HV | PEV | Drive | Nex |
|-------|--------|---------|------------|----------|-----------|------------|------------|------------|
| 1 | Frank | Sinatra | 12.12.1915 | 1 | | | 05.06.2015 | 02.11.2015 |
| 2 | Robert | Downey | 4.4.1965 | 2 | 27.7.2014 | 01.01.2016 | 20.01.2015 | |
Alternative it could be:
| mitID | Name | FamName | DOB | abtIDref | HV | PEV | Drive | Nex |
|-------|--------|---------|------------|----------|----|-----|-------|-----|
| 1 | Frank | Sinatra | 12.12.1915 | 1 | | | x | x |
| 2 | Robert | Downey | 4.4.1965 | 2 | x | x | x | |
The goal is that users/admins can add up new skills and someone can see on this resultlist, if a person has this skill.
What did i try:
I've come across multiple examples of dynamic SQL and the pivot function, but I don't know how to use it in my case, because I don't run a function like AVG() or MIN().
I tried it like this:
DECLARE #columns AS VARCHAR(MAX);
DECLARE #sql AS VARCHAR(MAX);
select #columns = substring((Select DISTINCT ',' + QUOTENAME(faeID) FROM mdb_Fähigkeiten FOR XML PATH ('')),2, 1000);
SELECT #sql = 'SELECT * FROM mdb_Mitarbeiter
PIVOT
(
MAX(Value)
FOR mitID IN( ' + #columns + ' )
);';
execute(#sql);
And a second approach was:
declare #collist nvarchar(max)
SET #collist = stuff((select distinct ',' + QUOTENAME(Question)
FROM #t1 -- your table here
FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)'),1,1,'')
select #collist
declare #q nvarchar(max)
set #q = '
select *
from (
select
Vorname, Bezeichnung, faeIDref
from (
select #t1.*, #t2.Answer, #t2.parent
from #t1
inner join #t2 on #t1.QID = #t2.QID
) as x
) as source
pivot (
max(Answer)
for Question in (' + #collist + ')
) as pvt
'
exec (#q)
But TBH I don't get the functions found.
I hope you can provide me with some guidance what I have to change (or even if I can) achieve this.
I believe the query below is what you are looking for. Adjust the column and table names as needed to fit your database.
DECLARE #sql AS NVARCHAR(MAX)
DECLARE #cols AS NVARCHAR(MAX)
SELECT #cols= ISNULL(#cols + ',','') + QUOTENAME(Descr)
FROM Faehigkeiten ORDER BY faeID
SET #sql = N'
SELECT mitID, Name, FamName, DOB, abtIDref, ' + #cols + '
FROM (
SELECT mitID, Name, FamName, DOB, abtIDref, [when], descr
FROM Mitarbeiter m
JOIN [Link-List] l ON m.mitID = l.mitIDref
JOIN Faehigkeiten f ON f.faeID = l.feaIDref
) a
PIVOT(MAX([when]) FOR descr IN (' + #cols + ')) p'
EXEC sp_executesql #sql

Custom report who lines is columns in SQL Server

I need make one custom report in SQL Server. I have one table called ProductsTable, in this table have the products tables of prices.
| id | description |
| 1 | TABLE A |
| 2 | TABLE B |
| 3 | TABLE C |
| 4 | TABLE D |
Now, i have the table ProductsTablePrices with all products prices and his tables.
| id | idproduct | idtable | price |
| 1 | 1 | 1 | 1.00 |
| 1 | 1 | 2 | 1.50 |
| 1 | 1 | 3 | 2.00 |
| 1 | 1 | 4 | 5.00 |
And finally, i have the Products table.
| id | name |
| 1 | Paper |
I need create one select to get one result like this...
| name | TABLE A | TABLE B | TABLE C | TABLE D |
| Paper | 1.00 | 1.50 | 2.00 | 5.00 |
Thanks!
Try this:
SELECT
P.name,
SUM(CASE WHEN PT.description = 'TABLE A' THEN PP.price END) [TABLE A],
SUM(CASE WHEN PT.description = 'TABLE B' THEN PP.price END) [TABLE B],
SUM(CASE WHEN PT.description = 'TABLE C' THEN PP.price END) [TABLE C],
SUM(CASE WHEN PT.description = 'TABLE D' THEN PP.price END) [TABLE D]
FROM Products P
JOIN ProductsTablePrices PP
ON P.id = PP.idproduct
JOIN ProductsTable PT
ON PP.idtable = PT.id
GROUP BY P.name
I Use that to resolve my problem.
DECLARE #NameColumnTable VARCHAR(100)
DECLARE #CountColumns INT
DECLARE #ColumnsPivot varchar(6000)
DECLARE #Flag INT
SELECT #CountColumns = count(*) FROM ProductsTable
SET #Flag = 0
WHILE (#Flag <= #CountColumns)
BEGIN
SELECT #NameColumnTable = Name FROM ProductsTable
WHERE id = #Flag
ORDER BY Name ASC
SET #ColumnsPivot = ISNULL(#ColumnsPivot,'') + 'SUM(CASE WHEN PT.description = ''' + #NameColumnTable + ''' THEN PP.price END) [' + #NameColumnTable + ']'
IF #Flag <> #CountColumns
BEGIN
SET #ColumnsPivot = #ColumnsPivot + ','
END
SET #ColumnsPivot = #ColumnsPivot + CHAR(13)
SET #Flag = #Flag + 1
END
DECLARE #SQLFINAL VARCHAR(5000)
SET #SQLFINAL = 'SELECT
P.name,
' + #ColumnsPivot + '
FROM Products P
JOIN ProductsTablePrices PP
ON P.id = PP.idproduct
JOIN ProductsTable PT
ON PP.idtable = PT.id
GROUP BY P.name
ORDER BY P.name'
EXEC(#SQLFINAL)
I don't know if is a good practice, but works great and performance as well.
Thanks!