How to combine two records? - sql

I have a table that looks like this
ID | Value | Type
-----------------------
1 | 50 | Travel
1 | 25 | Non-Travel
1 | 25 | Non-Travel
1 | 25 | Non-Travel
1 | 50 | Travel
1 | 75 | Non-Travel
How can I query this to make the output rearrange to this?
ID | Travel | Non-Travel
------------------------
1 | 100 | 150
The query to actually get the first table I posted has many joins and a BIT column in one of the tables where 0 or NULL is non-travel and 1 is travel. So I have something like this:
SELECT
[ID]
,CASE WHEN [IsTravel] IN (0,NULL) THEN ISNULL(SUM([VALUE]),0) END AS 'NonTravel'
,CASE WHEN [IsTravel] = 1 THEN ISNULL(SUM([VALUE]),0) END AS 'Travel'
FROM
...
However the result ends up showing this
ID | Travel | Non-Travel
------------------------
1 | 100 | NULL
1 | NULL | 150
How can I edit my query to combine the rows to show this result?
ID | Travel | Non-Travel
------------------------
1 | 100 | 150
Thanks in advance.

select ID,
SUM(CASE WHEN Type = 'Travel' THEn value ELSE 0 END) [Travel],
SUM(CASE WHEN Type = 'NonTravel' THEn value ELSE 0 END) [NonTravel]
from #Table1
GROUP BY ID

You need to wrap each of your conditionals in aggregations such as MAX(), and GROUP BY other columns to roll up the values and remove the NULL. Something like this:
SELECT
[ID]
,MAX(CASE WHEN [IsTravel] IN (0,NULL) THEN ISNULL(SUM([VALUE]),0) END) AS 'NonTravel'
,MAX(CASE WHEN [IsTravel] = 1 THEN ISNULL(SUM([VALUE]),0) END) AS 'Travel'
FROM
...
GROUP BY [ID]
If the logic gets too cluttered or confusing (don't know without seeing your whole current query) then drop those results into a temp table or CTE and do the simple MAX() and GROUP BY from there.

You can use pivot as below:
Select * from (
Select Id, [Value], [Type] from yourtable ) a
pivot (sum([Value]) for [Type] in ([Travel],[Non-Travel]) ) p
Output as below:
+----+------------+--------+
| Id | Non-Travel | Travel |
+----+------------+--------+
| 1 | 150 | 100 |
+----+------------+--------+
For dynamic list of Travel types you can do dynamic query as below:
Declare #cols1 varchar(max)
Declare #query nvarchar(max)
Select #cols1 = stuff((select Distinct ','+QuoteName([Type]) from #traveldata for xml path('')),1,1,'')
Set #query = ' Select * from (
Select Id, [Value], [Type] from #traveldata ) a
pivot (sum([Value]) for [Type] in (' + #cols1 + ') ) p '
Exec sp_executesql #query

Related

SQL sort by closest string match in multiple columns

I have a search parameter that I am trying to search on multiple columns using the "Like % InputParam %" pattern matching which gives me the following result. ie - Matching OrderID and Ref no to the input parameter.
Consider I have the following table -
OrderId | Name | Ref No |
12345 | XYZ | 120545 |
1205 | ABC | 451003 |
00120505 | CDE | 000174 |
Here OrderID, Ref no are strings and the input query = '1205'. I want the result to be sorted from the most matched to the least matched.
Where most matched is the most accurate match like 1205 = 1205 here
and Least matched is a substring like 00120505 = 1205.
Output -
OrderId | Name | Ref no |
1205 | ABC | 451003 |
12345 | XYZ | 120545 |
00120505 | CDE | 000174 |
You can do it by defining a computed column e.g. MATCH_SCORE with a value that depends on comparisons between OrderId and the value you are looking for; and then use ORDER BY MATCH_SCORE.
Working example:
DECLARE #tbl TABLE (OrderId VARCHAR(10), Name VARCHAR(10), RefNo VARCHAR(10))
INSERT INTO #tbl VALUES ('12345','XYZ','120545'), ('1205','ABC','451003'), ('00120505','CDE','000174')
DECLARE #v VARCHAR(10) = '1205'
;WITH data AS
(SELECT *,
CASE WHEN OrderId = #v THEN 1
WHEN OrderId LIKE #v + '%' THEN 2
WHEN OrderId LIKE '%' + #v + '%' THEN 3
ELSE 4
END AS MATCH_SCORE
FROM #tbl
)
SELECT * FROM data ORDER BY MATCH_SCORE
Output:
OrderId
Name
RefNo
MATCH_SCORE
1205
ABC
451003
1
00120505
CDE
000174
3
12345
XYZ
120545
4
I used a Common Table Expression to construct data, which defines a temporary named result set that is used by the SELECT that follows it.
You need to define what you mean by the distance between two strings. I'll use #peter-b 's definition in the example below. Once you know how to measure how close a string is to the search parameter, you can transpose the columns to rows with cross apply (LATERAL is the standard name), and use the min distance to order the rows.
with t (orderid, refno) as (
select '12345','120545'
union all
select '1205','451003'
)
select t.orderid, t.refno
, min(case when u.s = '1205' then 1
when u.s like '1205'+'%' then 2
when u.s like '%' + u.s + '%' then 3
else 4
end) as distance
from t
cross apply (
select t.orderid
union all
select t.refno
) as u (s)
group by t.orderid, t.refno
order by 3
;
orderid refno distance
1205 451003 1
12345 120545 2
Fiddle

Pivoting table throws error: "only simple column names allowed here"

I have a dataset, example:
Master table:
x------x----------x-----------------x-----------x--------x
| Chasisnumber | Messagename | PaintML | Result
x------x--------------------x-------x-----------x--------x
| A123 | Message1 | 10 | OK
| A123 | Message2 | 70 | NOK
B123 | Message1 | 10 | OK
B123 | Message2 | 50 | OK
x------x--------------------x-------x-----------x--------x
I want to get:
Chasisnumber, PaintML-Message1 ,Result-Message1,PaintML-Message2,Result-Message2
| A123 | 10 | OK | 70 | NOK
B123 | 10 | OK | 50 | OK
This can be done with pivot. Can someone help me out?
Example:
select *
from
(Select chasis, message, paint, result
from paint_b) src
pivot
(
src.Paint, src.result for src.MessageName in
('Message1',
'Message2')
) piv;
Like i mentioned, a cross tab seems like a better idea:
--Sample Data
WITH VTE AS(
SELECT V.Chasisnumber,
V.Messagename,
V.PaintML,
V.Result
FROM (VALUES('A123','Message1',10,'OK'),
('A123','Message2',70,'NOK'),
('B123','Message1',10,'OK'),
('B123','Message2',50,'OK')) V(Chasisnumber,Messagename,PaintML,Result))
--Solution
SELECT V.Chasisnumber,
MAX(CASE V.Messagename WHEN 'Message1' THEN V.PaintML END) AS PaintML1,
MAX(CASE V.Messagename WHEN 'Message2' THEN V.PaintML END) AS PaintML2,
MAX(CASE V.Messagename WHEN 'Message1' THEN V.Result END) AS Result1,
MAX(CASE V.Messagename WHEN 'Message2' THEN V.Result END) AS Result2
FROM VTE V
GROUP BY V.Chasisnumber;
The following query should do what you want:
create table #tmp (Chasisnumber VARCHAR(10),Messagename VARCHAR(25), PaintML INT,Result VARCHAR(10))
insert into #tmp values
('A123','Message1',10,'OK'),
('A123','Message2',70,'NOK'),
('B123','Message1',10,'OK'),
('B123','Message2',50,'OK')
select * from (
select Chasisnumber, unpiv.val, unpiv.col+'-'+unpiv.Messagename as col
from (select Chasisnumber,Messagename,cast(PaintML as varchar(10)) PaintML,Result from #tmp) tmp
unpivot (val for col in (PaintML,Result)) unpiv )a
pivot (max(val) for col in ([PaintML-Message1],[Result-Message1],[PaintML-Message2],[Result-Message2])) piv

SQL SELECT: concatenated column with line breaks and heading per group

I have the following SQL result from a SELECT query:
ID | category| value | desc
1 | A | 10 | text1
2 | A | 11 | text11
3 | B | 20 | text20
4 | B | 21 | text21
5 | C | 30 | text30
This result is stored in a temporary table named #temptab. This temporary table is then used in another SELECT to build up a new colum via string concatenation (don't ask me about the detailed rationale behind this. This is code I took from a colleague). Via FOR XML PATH() the output of this column is a list of the results and is then used to send mails to customers.
The second SELECT looks as follows:
SELECT t1.column,
t2.column,
(SELECT t.category + ' | ' + t.value + ' | ' + t.desc + CHAR(9) + CHAR(13) + CHAR(10)
FROM #temptab t
WHERE t.ID = ttab.ID
FOR XML PATH(''),TYPE).value('.','NVARCHAR(MAX)') AS colname
FROM table1 t1
...
INNER JOIN #temptab ttab on ttab.ID = someOtherTable.ID
...
Without wanting to go into too much detail, the column colname becomes populated with several entries (due to multiple matches) and hence, a longer string is stored in this column (CHAR(9) + CHAR(13) + CHAR(10) is essentially a line break). The result/content of colname looks like this (it is used to send mails to customers):
A | 10 | text1
A | 11 | text11
B | 20 | text20
B | 21 | text21
C | 30 | text30
Now I would like to know, if there is a way to more nicely format this output string. The best case would be to group the same categories together and add a heading and empty line between different categories:
*A*
A | 10 | text1
A | 11 | text11
*B*
B | 20 | text20
B | 21 | text21
*C*
C | 30 | text30
My question is: How do I have to modify the above query (especially the string-concatenation-part) to achieve above formatting? I was thinking about using a GROUP BY statement, but this obviously does not yield the desired result.
Edit: I use Microsoft SQL Server 2008 R2 (SP2) - 10.50.4270.0 (X64)
Declare #YourTable table (ID int,category varchar(50),value int, [desc] varchar(50))
Insert Into #YourTable values
(1,'A',10,'text1'),
(2,'A',11,'text11'),
(3,'B',20,'text20'),
(4,'B',21,'text21'),
(5,'C',30,'text30')
Declare #String varchar(max) = ''
Select #String = #String + Case when RowNr=1 Then Replicate(char(13)+char(10),2) +'*'+Category+'*' Else '' end
+ char(13)+char(10) + category + ' | ' + cast(value as varchar(25)) + ' | ' + [desc]
From (
Select *
,RowNr=Row_Number() over (Partition By Category Order By Value)
From #YourTable
) A Order By Category, Value
Select Substring(#String,5,Len(#String))
Returns
*A*
A | 10 | text1
A | 11 | text11
*B*
B | 20 | text20
B | 21 | text21
*C*
C | 30 | text30
This should return what you want
Declare #YourTable table (ID int,category varchar(50),value int, [desc] varchar(50))
Insert Into #YourTable values
(1,'A',10,'text1'),
(2,'A',11,'text11'),
(3,'B',20,'text20'),
(4,'B',21,'text21'),
(5,'C',30,'text30');
WITH Categories AS
(
SELECT category
,'**' + category + '**' AS CatCaption
,ROW_NUMBER() OVER(ORDER BY category) AS CatRank
FROM #YourTable
GROUP BY category
)
,Grouped AS
(
SELECT c.CatRank
,0 AS ValRank
,c.CatCaption AS category
,-1 AS ID
,'' AS Value
,'' AS [desc]
FROM Categories AS c
UNION ALL
SELECT c.CatRank
,ROW_NUMBER() OVER(PARTITION BY t.category ORDER BY t.Value)
,t.category
,t.ID
,CAST(t.value AS VARCHAR(100))
,t.[desc]
FROM #YourTable AS t
INNER JOIN Categories AS c ON t.category=c.category
)
SELECT category,Value,[desc]
FROM Grouped
ORDER BY CatRank,ValRank
The result
category Value desc
**A**
A 10 text1
A 11 text11
**B**
B 20 text20
B 21 text21
**C**
C 30 text30

Can we use two pivot in a single query?

Declare #tbl table(SessionId varchar(max),ItemID_FK int,Roles varchar(max))
insert into #tbl
select distinct SessionID,ItemID_FK,Roles from tbl_Answers where ID_FK=#ID
SELECT ItemID_PK,ItemName,case when [Role1] IS NULL then 0 else [Role1] end as [Role1],
case when [Role2] IS NULL then 0 else [Role2] end as [Role2],
case when [Role3] IS NULL then 0 else [Role3] end as [Role3],
case when [Role4] IS NULL then 0 else [Role4] end as [Role4],
case when [Role5] IS NULL then 0 else [Role5] end as [Role5],
case when [Role6] IS NULL then 0 else [Role6] end as [Role6],
case when [Role7] IS NULL then 0 else [Role7] end as [Role7]
FROM
(
select items.ItemID_PK ,items.ItemName,count(ans.Roles) as cntRoles,ans.Roles from tbl_Items items Full join #tbl ans
on items.ItemID_PK=ans.ItemID_FK where items.ID_FK= #ID group by Roles,ItemName , items.ItemID_PK
) d PIVOT
(
max(cntRoles)
FOR Roles IN ([Role1],[Role2],[Role3],[Role4],[Role5],[Role6],[Role7])
) AS pvt order by ItemID_PK
I used the above stored procedure and got the output as
+----------+----------+-----+-----+-----+-----+-----+-----+-----+
|ItemID_PK |ItemName |Role1|Role2|Role3|Role4|Role5|Role6|Role7|
+----------+----------+-----+-----+-----+-----+-----+-----+-----+
| 111 | aaaaa | 6 | 5 | 0 | 5 | 1 | 4 | 2 |
| 222 | bbbbb | 1 | 1 | 7 | 2 | 0 | 3 | 1 |
+----------+----------+-----+-----+-----+-----+-----+-----+-----+
I have another query and got the following output.
Select Category,Answer,Roles
from tbl_Answers where ID_FK=1 and Category='OtherText'
+---------+--------+-----+
|Category |Answer |Roles|
+---------+--------+-----+
|OtherText| xxx |Role1|
|OtherText| yyy |Role1|
|OtherText| zzz |Role2|
|OtherText| xzx |Role3|
+---------+--------+-----+
I need to merge the above two outputs to generate the result as
+----------+----------+-----+-----+-----+-----+-----+-----+-----+
|ItemID_PK |ItemName |Role1|Role2|Role3|Role4|Role5|Role6|Role7|
+----------+----------+-----+-----+-----+-----+-----+-----+-----+
| 111 | aaaaa | 6 | 5 | 0 | 5 | 1 | 4 | 2 |
| 222 | bbbbb | 1 | 1 | 7 | 2 | 0 | 3 | 1 |
| Null | Othertext| xxx | zzz | xzx | saa | | xxx | |
| Null | Othertext| yyy | | | zxz | | | |
+----------+----------+-----+-----+-----+-----+-----+-----+-----+
How to combine the second query to the first pivot query to get the result mentioned above?
Thanks in advance.
You could just use UNION ALL to combine the two results, you would need to convert the roles from the top query from int to VARCHAR though:
DECLARE #ID INT = 1;
WITH Ans AS
( SELECT DISTINCT SessionID,ItemID_FK,Roles
FROM tbl_Answers
WHERE ID_FK = #ID
), PivotData AS
( SELECT items.ItemID_PK,
items.ItemName,
cntRoles = COUNT(ans.Roles),
ans.Roles
FROM tbl_Items items
FULL JOIN Ans
ON items.ItemID_PK = ans.ItemID_FK
WHERE items.ID_FK = #ID
GROUP BY Roles,ItemName, items.ItemID_PK
)
SELECT ItemID_PK,
ItemName,
[Role1] = CAST(ISNULL([Role1], 0) AS VARCHAR(255)),
[Role2] = CAST(ISNULL([Role2], 0) AS VARCHAR(255)),
[Role3] = CAST(ISNULL([Role3], 0) AS VARCHAR(255)),
[Role4] = CAST(ISNULL([Role4], 0) AS VARCHAR(255)),
[Role5] = CAST(ISNULL([Role5], 0) AS VARCHAR(255)),
[Role6] = CAST(ISNULL([Role6], 0) AS VARCHAR(255)),
[Role7] = CAST(ISNULL([Role7], 0) AS VARCHAR(255))
FROM PivotData
PIVOT
( MAX(cntRoles)
FOR Roles IN ([Role1],[Role2],[Role3],[Role4],[Role5],[Role6],[Role7])
) AS pvt
UNION ALL
SELECT ItemID_PK = NULL,
ItemName = Category,
[Role1] = ISNULL([Role1], ''),
[Role2] = ISNULL([Role2], ''),
[Role3] = ISNULL([Role3], ''),
[Role4] = ISNULL([Role4], ''),
[Role5] = ISNULL([Role5], ''),
[Role6] = ISNULL([Role6], ''),
[Role7] = ISNULL([Role7], '')
FROM ( SELECT Category,Answer,Roles
FROM tbl_Answers
WHERE ID_FK = 1
AND Category = 'OtherText'
) pd
PIVOT
( MAX(Answer)
FOR Roles IN ([Role1],[Role2],[Role3],[Role4],[Role5],[Role6],[Role7])
) AS pvt
ORDER BY ItemID_PK;
Note, I have changed this expression:
case when [Role2] IS NULL then 0 else [Role2] end
to
ISNULL([Role2], 0)
as the effect is the same, but it is much shorter. I have also removed the table variable, and just placed the same query within a Common Table Expression, as it seems redundant to fill a table variable then only refer to it once. You are removing the use of indexes and statistics on the actual table and gaining no benefit for it.
Use a UNION to combine the two pivots.

Convert Access TRANSFORM/PIVOT query to SQL Server

TRANSFORM Avg(CASE WHEN [temp].[sumUnits] > 0
THEN [temp].[SumAvgRent] / [temp].[sumUnits]
ELSE 0
END) AS Expr1
SELECT [temp].[Description]
FROM [temp]
GROUP BY [temp].[Description]
PIVOT [temp].[Period];
Need to convert this query for sql server
I have read all other posts but unable to convert this into the same
Here is the equivalent version using the PIVOT table operator:
SELECT *
FROM
(
SELECT
CASE
WHEN sumUnits > 0
THEN SumAvgRent / sumUnits ELSE 0
END AS Expr1,
Description,
Period
FROM temp
) t
PIVOT
(
AVG(Expr1)
FOR Period IN(Period1, Period2, Period3)
) p;
SQL Fiddle Demo
For instance, this will give you:
| DESCRIPTION | PERIOD1 | PERIOD2 | PERIOD3 |
---------------------------------------------
| D1 | 10 | 0 | 20 |
| D2 | 100 | 1000 | 0 |
| D3 | 50 | 10 | 2 |
Note that When using the MS SQL Server PIVOT table operator, you have to enter the values for the pivoted column. However, IN MS Access, This was the work that TRANSFORM with PIVOT do, which is getting the values of the pivoted column dynamically. In this case you have to do this dynamically with the PIVOT operator, like so:
DECLARE #cols AS NVARCHAR(MAX);
DECLARE #query AS NVARCHAR(MAX);
SELECT #cols = STUFF((SELECT distinct
',' +
QUOTENAME(Period)
FROM temp
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'');
SET #query = ' SELECT Description, ' + #cols + '
FROM
(
SELECT
CASE
WHEN sumUnits > 0
THEN SumAvgRent / sumUnits ELSE 0
END AS Expr1,
Description,
Period
FROM temp
) t
PIVOT
(
AVG(Expr1)
FOR Period IN( ' + #cols + ')
) p ';
Execute(#query);
Updated SQL Fiddle Demo
This should give you the same result:
| DESCRIPTION | PERIOD1 | PERIOD2 | PERIOD3 |
---------------------------------------------
| D1 | 10 | 0 | 20 |
| D2 | 100 | 1000 | 0 |
| D3 | 50 | 10 | 2 |