Pivot table and combine records onto 1 line - tsql

I've tried many different ways to pivot a table to show all records on 1 row. I've provided my query for the closest solution I came up with. It'll probably be easier if I illustrate what I need. Since there can be an unlimited number of teacher survey questions the query has to be dynamic. I've modified the column names to make it easier to read.
teacherSurveyQuestions
TSQID CID Order OQReference Stem
1 1011 1 q1_rb blabla
2 1011 2 q2_rb blabla
3 1011 3 q2a_cb blabla
teacherSurveyUserID
TSUID firstName lastName UID
1 Bob Smith 1027
2 Tom Jones 1034
teacherSurveyAnswers
TSAID UID TSQID TSUID Response
1 1027 1 1 Bob 1
2 1027 2 1 Bob 2
3 1027 3 1 Bob 3
4 1034 1 2 Tom 1
5 1034 2 2 Tom 2
6 1034 3 2 Tom 3
Now I need this data to look like this:
firstName lastName q1_rb q2_rb q2a_cb
Bob Smith Bob 1 Bob 2 Bob 3
Tom Jones Tom 1 Tom 2 Tom 3
Here's what I have so far that kind of works except all the responses are NULL
declare #query as nvarchar(max),
#colsPivot as nvarchar(max)
select #colsPivot = stuff((select ','
+ quotename(OQReference)
from teacherSurveyQuestions tsq
where tsq.CID = 1011
order by tsq.Order
for xml path(''), type
).value('.', 'nvarchar(max)')
,1,1,'')
set #query
= 'select *
from
(
select firstName, lastName, value, col +''_''+ CAST(rn as varchar(10)) as col
from
(
select
tsu.TSUID
,tsu.firstName
,tsu.lastName
,tsq.OQReference
,tsa.Response
,ROW_NUMBER() over(partition by tsu.TSUID order by tsq.Order) rn
from teacherSurveyQuestions tsq
inner join teacherSurveyAnswers tsa on tsa.TSQID = tsq.TSQID
inner join teacherSurveyUsers tsu on tsu.TSUID = tsa.TSUID
where tsq.CID = 1011
) x
unpivot
(
value
for col in (OQReference)
) u
) x1
pivot
(
max(value)
for col in ('+ #colspivot +')
) p'
exec(#query)
Result of query:
firstName lastName q1_rb q2_rb q2a_cb
Bob Smith NULL NULL NULL
Tom JOnes NULL NULL NULL

Try this
declare #query as nvarchar(max),
#colsPivot as nvarchar(max)
select #colsPivot = stuff((select ','
+ quotename(OQReference)
from teacherSurveyQuestions tsq
where tsq.CID = 1011
order by tsq.[Order]
for xml path(''), type
).value('.', 'nvarchar(max)')
,1,1,'')
set #query
= 'select firstName, lastName,'+ #colspivot +'
from
(
select firstName, lastName,Response, value
from
(
select
tsu.TSUID
,tsu.firstName
,tsu.lastName
,tsq.OQReference
,tsa.Response
,ROW_NUMBER() over(partition by tsu.TSUID order by tsq.[Order]) rn
from teacherSurveyQuestions tsq
inner join teacherSurveyAnswers tsa on tsa.TSQID = tsq.TSQID
inner join teacherSurveyUserID tsu on tsu.TSUID = tsa.TSUID
where tsq.CID = 1011
) x
unpivot
(
value
for col in (OQReference)
) u
) x1
pivot
(
max(Response)
for value in ('+ #colspivot +')
) p'
exec(#query)
SQL FIDDLE DEMO

Related

Require result with of multiple customers along with city name

we have following requirement
I need customer_name which are in Active status and attribute_name=City and attribute_value in(Indore,Mumbai) and result should return count less than=2
It means from result for Indore i should get 2 results out of 3 and for Mumbai i should get 1 out of 1.
I tried below 2 ways but getting all the rows for customers which are in city Indore and Mumbai
Customer_table has below details
customer_id customer_name customer_Status
------------------------------------------
1 ABC Active
2 XYZ Active
3 PQR NA
4 ABCD Active
4 ABCDE Active
customer_details table has below details
customer_id attribute_name attribute_value
------------------------------------------
1 City Indore
1 Phone Number 9100000000
1 Country India
2 City Mumbai
2 Phone Number 9100000001
2 Country India
3 City Delhi
3 Phone Number 9100000002
3 Country India
4 City Mumbai
4 Phone Number 9100000003
4 Country India
5 City Mumbai
5 Phone Number 9100000004
5 Country India
Code:-
select attribute_value, r.customer_name from customer_details res
join lateral (
select customer_name from Customer_table
where res.customer_id=customer_id
and customer_Status= 'Active'
limit 2
) r on true
where attribute_name= 'City' and attribute_value in ('Indore','Mumbai');
Code:-
SELECT s.customer_name,attribute_value
FROM (
SELECT *, row_number() OVER (PARTITION BY customer_id ) AS rn
FROM customer_details
WHERE attribute_name= 'City' and attribute_value in ('Indore','Mumbai')
) e
JOIN Customer_table s USING (customer_id)
WHERE rn <= 2
and and customer_Status= 'Active'
ORDER BY customer_id, e.rn;
Please try this way.
select customer_name,attribute_value from (
select ct.customer_name,attribute_value,row_number() OVER (PARTITION BY attribute_value ) AS rn
from customer_table ct ,customer_details cd
where ct.customer_status = 'Active' and ct.customer_id = cd.customer_id
and attribute_name='City' and attribute_value in('Indore','Mumbai')
) as t
where rn <= 2
This design will kill any performance you hope to get around customers. The first thing I would do in drop the customer_details table and the columns phone_number, city, and country to the customers table. Lacking that I would create a view that joined customers to customer_details having all the columns in the view.
create view Customer_Standard as
select c.cust_id, c.name, c.status, ph.attribute_value phone_number, ct.attribute_value city, cn.attribute_value country
from customers c
left join customer_details ph on (ph.cust_id = c.cust_id and ph.attribute_name = 'Phone Number')
left join customer_details ct on (ct.cust_id = c.cust_id and ct.attribute_name = 'City')
left join customer_details cn on (cn.cust_id = c.cust_id and cn.attribute_name = 'Country') ;
Then the query you want becomes:
select cust_id, name, status, phone_number, city, country
from (select cs.*, row_number() over (partition by city order by cust_id) rn
from Customer_Standard cs
) cust
where city in ('Indore','Mumbai')
and rn<3;

SQL adding numbers column

Let's say I have a table:
Table1
ID | Table2_ID | Title
1 1 Breaking_Bad
2 1 Breaking_Bad
3 2 Simpsons
4 1 House_Of_Cards
I want to rename the title by adding '_XX' (where XX is a number) to only to those entries that are the same title and have the same Table2_ID.
So end results would be
Table1
ID | Table2_ID | Title
1 1 Breaking_Bad_01
2 1 Breaking_Bad_02
3 2 Simpsons
4 1 House_Of_Cards
How could I do this with TSQL?
You can do this with
WITH T
AS (SELECT *,
COUNT(*) OVER (PARTITION BY Table2_ID, Title) AS Cnt,
ROW_NUMBER() OVER (PARTITION BY Table2_ID, Title ORDER BY ID) AS RN
FROM Table1)
UPDATE T
SET Title = Title + '_' + FORMAT(RN, 'D2')
WHERE Cnt > 1;
SQL Fiddle
Or if you are on a version without FORMAT
SET Title = Title + CASE WHEN RN < 10 THEN '_0' ELSE '_' END + CAST(RN AS VARCHAR(10))
Will this work in SQL Server? The padding to 2 digits would be straightforward.
UPDATE TABLE1 A SET TITLE = TITLE + '_' + (SELECT COUNT(*) FROM TABLE1 B WHERE A.TITLE=B.TITLE AND A.ID<=B.ID) WHERE A.ID IN (SELECT B.ID FROM TABLE1 B
WHERE A.Id<>B.ID and A.TITLE=B.TITLE)

TSQL query to return values from a table where there are multiple rows with same ID into a single row but each unique value in a different column

I'm trying to return values from a table so that I get 1 row per purchaseID and return multiple columns with Buyers First and Last Names.
E.G
I have a table with the following Data
| PurchaseID | FirstName | LastName|
|---------1------- | ----Joe------ | ---Smith----|
|---------1------- | -----Peter--- | ---Pan------|
|---------2------- | ----Max------|---Power----|
|---------2------- | -----Jack---- | ---Frost----|
I'm trying to write a query that returns the values like so
| PurchaseID | Buyer1FirstName | Buyer1LastName | Buyer2FirstName |Buyer2LastName|
|--------1---------|------------Joe--------- |--------Smith----------|---------Peter-----------|--------Pan------------|
|--------2---------|-------------Max--------|---------Power--------|---------Jack -----------|---------Frost----------|
I've been looking online but because I'm not sure how to explain in words what I want to do, I'm not having much luck. I'm hoping with a more visual explanation someone could point me in the right direction.
Any help would be awesome.
You can use ROW_NUMBER as the below:
DECLARE #Tbl TABLE (PurchaseID INT, FirstName VARCHAR(50), LastName VARCHAR(50))
INSERT INTO #Tbl
VALUES
(1, 'Joe', 'Smith'),
(1, 'Peter', 'Pan'),
(2, 'Max', 'Power'),
(2, 'Jack', 'Frost'),
(2, 'Opss', 'Sspo')
;WITH CTE
AS
(
SELECT
*, ROW_NUMBER() OVER (PARTITION BY PurchaseID ORDER BY PurchaseID) RowId
FROM #Tbl
)
SELECT
A.PurchaseID,
MIN(CASE WHEN A.RowId = 1 THEN A.FirstName END) Buyer1FirstName,
MIN(CASE WHEN A.RowId = 1 THEN A.LastName END ) Buyer1LastName ,
MIN(CASE WHEN A.RowId = 2 THEN A.FirstName END) Buyer2FirstName ,
MIN(CASE WHEN A.RowId = 2 THEN A.LastName END )Buyer2LastName,
MIN(CASE WHEN A.RowId = 3 THEN A.FirstName END) Buyer3FirstName ,
MIN(CASE WHEN A.RowId = 3 THEN A.LastName END )Buyer3LastName,
MIN(CASE WHEN A.RowId = 4 THEN A.FirstName END) Buyer4FirstName ,
MIN(CASE WHEN A.RowId = 4 THEN A.LastName END )Buyer4LastName
FROM
CTE A
GROUP BY
A.PurchaseID
Result:
PurchaseID Buyer1FirstName Buyer1LastName Buyer2FirstName Buyer2LastName Buyer3FirstName Buyer3LastName Buyer4FirstName Buyer4LastName
----------- ------------------- -------------------- -------------------- ------------------ ------------------- ----------------- ------------------- --------------
1 Joe Smith Peter Pan NULL NULL NULL NULL
2 Max Power Jack Frost Opss Sspo NULL NULL

TSQL select one or many rows to join

This is question similar to: TSQL select rows by one from 2 conditions, but it is different in results that I would like to have
I have a table like so:
ORDER_ID CODE1 CODE2 CODE3 STATUS TYPE SUM GROUP
1 '001' 'BIGP' NULL 4 'company' 120 48
2 '002' 'BIGP' NULL 1 'priv' 100 20
3 '001' NULL NULL 6 'priv' 50 49
4 '002' NULL 'L' 1 'company' 1253 22
and second table like so:
ADDRESS_ID ORDER_ID ZIP TYPE ADD_DATE CATEGORY VERIFIED
1 1 '15-125' 'K1' '2010-01-01' 'CLIENT' 1
2 1 '22-022' 'D1' '2010-01-02' 'SYSTEM' 1
3 2 '16-159' 'D2' '2010-01-02' 'SYSTEM' 1
4 2 '15-125' 'D2' '2010-02-01' 'CLIENT' 0
Third and fourth table contains zip codes and city names like so:
ZIP CITY
'15-125' 'Warszawa'
'22-022' 'Koszalin'
'16-159' 'Krakow'
'15-125' 'Lublin'
For every order that has
status not in (4,6)
code1 between '002' and '005'
(code2=null and code3=null) or (code2 in ('BIGA', 'BIGP') and code3=null) or (code2=NULL and code3 = 'L')
If code1 ='002' AND group IN (48,59,60,87) I must choose a single address
(big thanks to Nikola Markovinović):
SELECT TOP 1000 o.order_Id
, a.Address_Id
, a.Zip
--, *
FROM orders o
CROSS APPLY
(
select TOP 1
a.Address_Id,
a.Zip
from address a
WHERE a.order_Id = o.order_Id
ORDER BY case a.Type
when 'D2' then 1
when 'K1' then 2
else 3
end,
a.ADD_DATE
) a
WHERE
o.Status NOT IN (4, 6)
AND code1='002'
AND group IN (48,59,60,87)
AND ((code2 IS NULL AND code3 IS NULL) OR (code2 IN ('BIGA', 'BIGP') AND code3 IS NULL) OR (code2 IS NULL AND code3 = 'L'))
For all other orders that meet top criteria and got code1 ='002' AND group NOT IN (48,59,60,87) I must select all addresses for those orders that have verified=1
After collecting those addresses I will be able to check if a specific post company can deliver my mail to those addresses (I will check in another table containing zip codes)
I was thinking about making union all, taking first select and doing union with second that will return all addresses for code1 ='002' AND group NOT IN (48,59,60,87).
But maybe it is possible to do it without union all?
This it the final result I would like to get:
CODE1 TYPE COUNT_OF_ORDERS COUNT_OF_ADDRESSES COMPANY1 OTHER
'001' 'NORMAL' 125 150 110 40
'002' 'NORMAL' 100 122 100 22
'003' 'NORMAL' 150 110 100 10
'004' 'NORMAL' 200 220 220 0
'005' 'NORMAL' 220 240 210 30
'005' 'PRIORITY' 100 110 110 0
'SX1' 'PRIORITY' 100 100 20 80
So if my type is 'normal' I must check if that address for order exists in table having normal zip codes, if it has type 'priority' I must check in table with priority codes.
If code exists in specific table I add +1 to COMPANY1 column, if not to OTHER, so that sum of those columns must be sum of my addresses.
This is query that I've managed to do (with help of #Nikola Markovinović)
SELECT TOP 1000 o.order_Id
, a.Address_Id
, a.Zip
--, *
FROM orders o
CROSS APPLY
(
select TOP 1
a.Address_Id,
a.Zip
from address a
WHERE a.order_Id = o.order_Id
AND code1='002'
AND o.[group] IN (48,59,60,87)
ORDER BY case a.Type
when 'D2' then 1
when 'K1' then 2
else 3
end,
a.ADD_DATE
UNION ALL
select
a.Address_Id,
a.Zip
from address a
WHERE a.order_Id = o.order_Id
AND ((code1='002' AND o.[group] NOT IN (48,59,60,87)) OR code1 IN ('001', '003', '004', '005'))
--I'm not shure of that top line, it work's but mayby it con de written better
AND Verified = 1
) a
WHERE
o.Status NOT IN (4, 6)
AND ((code2 IS NULL AND code3 IS NULL)
OR (code2 IN ('BIGA', 'BIGP') AND code3 IS NULL)
OR (code2 IS NULL AND code3 = 'L'))
You might filter addresses easily ([group] IN (48,59,60,87) OR Verified = 1), but tweaking TOP 1 would make things ridiculous (TOP (case when [group] IN (48,59,60,87) then 1 else (select count(*) from addresses where order_Id = o.order_Id) end). So I propose that you do union all but for adresses only:
SELECT TOP 1000 o.order_Id
, a.Address_Id
, a.Zip
--, *
FROM orders o
CROSS APPLY
(
select TOP 1
a.Address_Id,
a.Zip
from address a
WHERE a.order_Id = o.order_Id
AND o.[group] IN (48,59,60,87)
ORDER BY case a.Type
when 'D2' then 1
when 'K1' then 2
else 3
end,
a.ADD_DATE
UNION ALL
select
a.Address_Id,
a.Zip
from address a
WHERE a.order_Id = o.order_Id
AND o.[group] NOT IN (48,59,60,87)
AND Verified = 1
) a
WHERE
o.Status NOT IN (4, 6)
AND code1='002'
AND ((code2 IS NULL AND code3 IS NULL)
OR (code2 IN ('BIGA', 'BIGP') AND code3 IS NULL)
OR (code2 IS NULL AND code3 = 'L'))
P.S. If order might not have an address replace CROSS APPLY with OUTER APPLY.

TSQL recursive CTE threaded sorting

I have the following table:
ID parentID name
1 0 car1
2 1 tire
3 2 rubber
4 0 car2
5 2 nut
6 3 black
To help with testing...
CREATE TABLE #TT (ID int
,ParentID int
,Name varchar(25)
)
INSERT #TT
SELECT 1,0,'car1' UNION ALL
SELECT 2,1,'tire' UNION ALL
SELECT 3,2,'rubber' UNION ALL
SELECT 4,0,'car2' UNION ALL
SELECT 5,2,'nut' UNION ALL
SELECT 6,3,'black'
I'm trying to create a "threaded" hierarchy, but I want to list the child nodes under their parents like so:
ID parentID name
1 0 car1
2 1 tire
3 2 rubber
6 3 black
5 2 nut
4 0 car2
If I use a recursive CTE like this one...
;WITH Features
AS
(
SELECT *
FROM #TT
WHERE ParentID = 0
UNION ALL
SELECT F.*
FROM #TT AS F
INNER JOIN Features
ON F.ParentID = Features.ID
)
SELECT *
FROM Features
I end up with this...
ID parentID name
1 0 car1
4 0 car2
2 1 tire
3 2 rubber
5 2 nut
6 3 black
any ideas? Thank you in advance.
You can build a tree path as you go along, and order it by that
Something like
DECLARE #TT TABLE(ID int, ParentID int, Name varchar(25))
INSERT #TT
SELECT 1,0,'car1' UNION ALL
SELECT 2,1,'tire' UNION ALL
SELECT 3,2,'rubber' UNION ALL
SELECT 4,0,'car2' UNION ALL
SELECT 5,2,'nut' UNION ALL
SELECT 6,3,'black'
SELECT *
FROM #TT
;WITH Features AS (
SELECT *,
CAST(ID AS VARCHAR(MAX)) + '/' AS TreePath
FROM #TT
WHERE ParentID = 0
UNION ALL
SELECT tt.*,
f.TreePath + CAST(tt.ID AS VARCHAR(10)) + '/'
FROM #TT tt INNER JOIN
Features f ON tt.ParentID = f.ID
)
SELECT *
FROM Features
ORDER BY TreePath
Try adding an ORDER BY clause such as:
SELECT * FROM Features
ORDER BY parentID