Reverse col and rows in SQL - sql

I have to create query, to reverse rows and cols correct. I am using MS SQL SERVER 2016.
This is what I have:
Row_ID | Group_ID | Group_Status | MemberRole | name
2807 | 10568 | accept | chairman | Rajah
2808 | 10568 | accept | member | Vaughan
2812 | 10568 | accept | secretary | Susan
This is what I need:
Group_ID | Status | Chairman | Secretary | Member1 | Member2 | Member3 | ... | Member20
10568 | Accept | Rajah | Susan | Vaughan | Kane | Oprah | ... | Imelda
(users with member role can be between 0-20)
Probably I should use pivot, but I have no idea how.
Ok, I have this code:
SELECT *
FROM
(
SELECT group_id,
group_status,
memberRole,
name
FROM DataGroup
) dataSource PIVOT(MAX(name) FOR memberRole IN([chairman],
[secretary],
[member])) pivotTab;
But I losing rows with members (get only one member), how to extract them to columns?

You can try this with a unioned query:
Some mockup (please provide such a dummy table with your sample data yourself in your next question):
DECLARE #mockup TABLE(Row_ID INT,Group_ID INT,Group_Status VARCHAR(100),MemberRole VARCHAR(100),[name] VARCHAR(100));
INSERT INTO #mockup VALUES
(2807,10568,'accept','chairman','Rajah')
,(2808,10568,'accept','member','Vaughan')
,(2812,10568,'accept','secretary','Susan')
,(2899,10568,'accept','member','Onemore');
--The query
SELECT p.*
FROM
(
SELECT Group_ID
,Group_Status
,[name]
,MemberRole
FROM #mockup
WHERE MemberRole IN('chairman','secretary')
UNION ALL
SELECT Group_ID
,Group_Status
,[name]
,CONCAT('Member',ROW_NUMBER() OVER(PARTITION BY Group_ID ORDER BY Row_ID))
FROM #mockup
WHERE MemberRole='member'
) t
PIVOT
(
MAX([name]) FOR MemberRole IN(Chairman,Secretary,Member1,Member2,Member3 /*add as many as you need*/)
) p;
The result
Group_ID Group_Status Chairman Secretary Member1 Member2 Member3
10568 accept Rajah Susan Vaughan Onemore NULL
In short:
The first part of the query will Just pick the two fix names.
The second part will pick the members and number them sorted by their Row_ID.
The PIVOT will then transform this to a single row, using the column MemberRole for the new column names.
You will have to think about some more things:
What if not all the lines are accepted?
What of there are many groups?
If you need help, you can comeback with a new question. Happy Coding!

I would simply use conditional aggregation:
select group_id, group_status,
max(case when member_role = 'chairman' then name end) as chairman,
max(case when member_role = 'secretary' then name end) as secretary,
max(case when member_role = 'member' and seqnum = 1 then name end) as member_01,
max(case when member_role = 'member' and seqnum = 2 then name end) as member_02,
. . .
from (select m.*,
row_number() over (partition by group_id, member_role order by row_id) as seqnum
from #mockup m
) m
group by group_id, group_status;
I find conditional aggregation to be much more flexible than pivot. This is an example of the situation where the query is simpler.

Related

Select all values (all rows) in one row Oracle

I get multiple rows after executing the select-query.
But I need to get all the values of these rows in one row.
̶C̶o̶u̶n̶t̶ ̶o̶f̶ ̶r̶o̶w̶s̶ ̶i̶s̶ ̶u̶n̶k̶n̶o̶w̶n̶ ̶(̶b̶e̶f̶o̶r̶e̶ ̶t̶h̶e̶ ̶̶̶s̶e̶l̶e̶c̶t̶̶̶-̶q̶u̶e̶r̶y̶ ̶i̶s̶ ̶e̶x̶e̶c̶u̶t̶e̶d̶)̶
For example:
|----------|-----------|
| **Name** | **Value** |
|----------|-----------|
| Alex | 150 |
|----------|-----------|
| Peter | 220 |
|----------|-----------|
| Katty | 34 |
|----------|-----------|
I want to get:
|-----------|-----------|-----------|-----------|-----------|-----------|
| **Col_1** | **Col_2** | **Col_3** | **Col_4** | **Col_5** | **Col_6** |
|-----------|-----------|-----------|-----------|-----------|-----------|
| Alex | 150 | Peter | 220 | Katty | 34 |
|-----------|-----------|-----------|-----------|-----------|-----------|
Oracle 11g.
UPDATE: I realized that with an unknown number of rows, the task is difficult, so I can assume that the number of rows will be known.
To pivot over a fixed number of column, one option uses row_number() and conditional aggregation:
select
max(case when rn = 1 then name end) name1,
max(case when rn = 1 then value end) value1,
max(case when rn = 2 then name end) name2,
max(case when rn = 2 then value end) value2,
...
from (
select t.*, row_number() over(order by id) rn
from mytable t
) t
You need a column that defines the ordering of the rows in the original dataset (and of the columns in the resultset): I assumed id.
You might be better off putting the values into a string or JSON column. For instance, you can aggregate the names and values into separate strings:
select list_agg(name, ',') within group (order by name) as names,
list_agg(value, ',') within group (order by name) as values
from t;
Or into a single string:
select list_agg(name || ':' || value, ',') within group (order by name) as name_values
from t;
Note: The maximum length of strings in Oracle for this purpose is 2000 characters. So this only works on a small amount of data.

Get values on 1 line based on unique identifier

I'm trying to write a query to get the values of a table placed onto a single line based on a specific key.
table.ID | table.ACCOUNT |
==================================
12345 | 456789 |
12345 | ABCDEF |
12345 | HIJKLM |
For example, I want to get all the ACCOUNTs for ID 12345 (above) onto one line so it looks like what is below.
table.ID | table.ACCOUNT1 | table.ACCOUNT2 | table.ACCOUNT3 |
====================================================================
12345 | 456789 | ABCDEF | HIJKLM |
I think I want to join the table to itself but I keep getting the same values in the 2nd and 3rd ACCOUNT fields (i.e. 456789 shows up in all 3).
If you don't really need separate columns for all of the accounts, consider using an aggregate function such as string_agg in Postgres:
SELECT id, string_agg(account, ',') FROM table GROUP BY id
This will produce a result with two columns, the id and a string containing all of the accounts for the id separated by , characters.
If you know there are at most three accounts per id, you can pivot the data. In most databases, you can use row_number() and conditional aggregation:
select id,
max(case when seqnum = 1 then account end) as account_1,
max(case when seqnum = 2 then account end) as account_2,
max(case when seqnum = 3 then account end) as account_3
from (select t.*
row_number() over (partition by id order by id) as seqnum
from t
) t
group by id;
Unlike a string aggregation method, this puts the values into separate columns.

Rotate columns to rows for joined tables

I have two tables similar to shown below (just leaving out fields for simplicity).
Table lead :
id | fname | lname | email
---------------------------------------------
1 | John | Doe | jd#test.com
2 | Mike | Johnson | mj#test.com
Table leadcustom :
id | leadid | name | value
-------------------------------------------------
1 | 1 | utm_medium | cpc
2 | 1 | utm_term | fall
3 | 1 | subject | business
4 | 2 | utm_medium | display
5 | 2 | utm_term | summer
6 | 2 | month | may
7 | 2 | color | red
I have a database that captures leads for a wide variety of forms that often have many different form fields. The first table gets the basic info that I know is on each form. The second table captures all other forms fields that were sent over so it can really contain a lot of different fields.
What I am trying to do is to do a join where I can grab all fields from lead table along with utm_medium and utm_term from leadcustom table. I don't need any additional fields even if they were sent over.
Desired results :
id | fname | lname | email | utm_medium | utm_term
---------------------------------------------------------------------------
1 | John | Doe | jd#test.com | cpc | fall
2 | Mike | Johnson | mj#test.com | display | summer
Only way I know I could do this is to grab all lead data and then for each record make more calls to get leadcustom data I am looking for but I know there has to me a more efficient way of getting this data.
I appreciate any help with this and it is not something I can change the way I capture that data and table formats.
If your columns are fixed, you can do this with group by + case + max like this:
select
fname,
lname,
email,
max(case when name = 'utm_medium' then value end) as utm_medium,
max(case when name = 'utm_term' then value end) as utm_term
from
lead l
join leadcustom c
on l.id = c.leadid
group by
fname,
lname,
email
The case will assign value from the leadcustom table when it matches the given name, otherwise it will return null, and max will pick take the assigned value if it exists over the null.
You can test this in SQL Fiddle
The other way to do this is to use pivot operator, but that syntax is slightly more complex -- or at least this is more easy for me.
Unless I interpret your question incorrectly - in which case I'm happy to be corrected - you could achieve your goal with a simple left join where you join on ID of the first table:
select ld.*, ldcust.utm_medium, ldcust.utm_term
from lead ld
left join leadcustom ldcust
on ld.id = ldcust.leadid
You can use a cte or a derived table to solve this:
cte:
;with cte as
(
select leadid, [name], [value]
from leadcustom
where name in('utm_medium', 'display')
)
select id, fname, lname, email, [name], [value]
from lead
inner join cte on(id = leadid)
Derived table:
select id, fname, lname, email, [name], [value]
from lead
inner join
(
select leadid, [name], [value]
from leadcustom
where name in('utm_medium', 'display')
) derived on(id = leadid)
and since suslov used JamesZ's fiddle, I will use it too...
declare #t table (Id int,fname varchar(10),lname varchar(10),email varchar(20))
insert into #t(Id,fname,lname,email)values (1,'john','doe','jd#test.com'),(2,'mike','johnson','mj#test.com')
declare #tt table (id int,leadid int,name varchar(10),value varchar(10))
insert into #tt(id,leadid,name,value)values
(1,1,'utm_medium','cpc'),
(2,1,'utm_term','fall'),
(3,1,'subject','business'),
(4,2,'utm_medium','display'),
(5,2,'utm_term','summer'),
(6,2,'month','may'),(7,2,'color','red')
select Id,fname,lname,
email,
[utm_medium],
[utm_term]
from (
select t.Id,
t.fname,
t.lname,
t.email,
tt.name,
tt.value
from #t t JOIN #tt tt
ON t.Id = tt.leadid)R
PIVOT(MAX(value) for name IN([utm_medium],[utm_term]))P
You can try with pivot and join:
select [id]
, [fname]
, [lname]
, [email]
, [utm_medium]
, [utm_term]
from ( select t2.*
, t1.[name]
, t1.[value]
from [leadcustom] t1
join [lead] t2 on t2.[id] = t1.[leadid]
) t
pivot (
max([value])
for [name] in ([utm_medium], [utm_term])
) pt
pivot rotates the joined table-valued expression, by turning the unique values from [value] column in the expression into [utm_medium] and [utm_term] columns in the output, and performs fake aggregation with max function (it works so because a corresponding column can have multiple values for one unique pivoted column, in this case, [name] for [value]).
SQLFiddle

SQL: Putting an individuals distinct diagnosis into one horizontal row

I'm using Microsoft SQL Server 2008 for a mental health organization.
I have a table that lists all of out clients and their diagnoses, but each diagnoses that a client has is in a new row. I want them all to be in a single row listed out horizontally with the date for each diagnosis. Some people have just one diagnosis, some have 20, some have none.
Here's an example of how my data sort of looks now (only with a lot few clients, we have thousands):
And Here's the format I'd like it to end up:
Any solutions you could offer or hints in the right direction would be great, thanks!
In order to get the result, I would first unpivot and then pivot your data. The unpivot will take your date and diagnosis columns and convert them into rows. Once the data is in rows, then you can apply the pivot.
If you have a known number of values, then you can hard-code your query similar to this:
select *
from
(
select person, [case#], age,
col+'_'+cast(rn as varchar(10)) col,
value
from
(
select person,
[case#],
age,
diagnosis,
convert(varchar(10), diagnosisdate, 101) diagnosisDate,
row_number() over(partition by person, [case#]
order by DiagnosisDate) rn
from yourtable
) d
cross apply
(
values ('diagnosis', diagnosis), ('diagnosisDate', diagnosisDate)
) c (col, value)
) t
pivot
(
max(value)
for col in (diagnosis_1, diagnosisDate_1,
diagnosis_2, diagnosisDate_2,
diagnosis_3, diagnosisDate_3,
diagnosis_4, diagnosisDate_4)
) piv;
See SQL Fiddle with Demo.
I am going to assume that you will have an unknown number of diagnosis values for each case. If that is the case, then you will need to use dynamic sql to generate the result:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT ',' + QUOTENAME(col+'_'+cast(rn as varchar(10)))
from
(
select row_number() over(partition by person, [case#]
order by DiagnosisDate) rn
from yourtable
) t
cross join
(
select 'Diagnosis' col union all
select 'DiagnosisDate'
) c
group by col, rn
order by rn, col
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT person,
[case#],
age,' + #cols + '
from
(
select person, [case#], age,
col+''_''+cast(rn as varchar(10)) col,
value
from
(
select person,
[case#],
age,
diagnosis,
convert(varchar(10), diagnosisdate, 101) diagnosisDate,
row_number() over(partition by person, [case#]
order by DiagnosisDate) rn
from yourtable
) d
cross apply
(
values (''diagnosis'', diagnosis), (''diagnosisDate'', diagnosisDate)
) c (col, value)
) t
pivot
(
max(value)
for col in (' + #cols + ')
) p '
execute(#query);
See SQL Fiddle with Demo. Both queries give the result:
| PERSON | CASE# | AGE | DIAGNOSIS_1 | DIAGNOSISDATE_1 | DIAGNOSIS_2 | DIAGNOSISDATE_2 | DIAGNOSIS_3 | DIAGNOSISDATE_3 | DIAGNOSIS_4 | DIAGNOSISDATE_4 |
------------------------------------------------------------------------------------------------------------------------------------------------------------------------
| John | 13784 | 56 | Depression | 03/13/2012 | Brain Injury | 03/14/2012 | Spinal Cord Injury | 03/15/2012 | Hypertension | 03/16/2012 |
| Kate | 2643 | 37 | Bipolar | 03/11/2012 | Hypertension | 03/12/2012 | (null) | (null) | (null) | (null) |
| Kevin | 500934 | 25 | Down Syndrome | 03/18/2012 | Clinical Obesity | 03/19/2012 | (null) | (null) | (null) | (null) |
| Pete | 803342 | 34 | Schizophenia | 03/17/2012 | (null) | (null) | (null) | (null) | (null) | (null) |
For this type of pivoting, I think the aggregate/group method is feasible:
select d.case, d.person,
max(case when seqnum = 1 then diagnosis end) as d1,
max(case when seqnum = 1 then diagnosisdate end) as d1date,
max(case when seqnum = 2 then diagnosis end) as d2,
max(case when seqnum = 2 then diagnosisdate end) as d2date,
. . . -- and so on, for as many groups that you want
from (select d.*, row_number() over (partition by case order by diagnosisdate) as seqnum
from diagnoses d
) d
group by d.case, d.person
Since you are dealing with sensitive medical information, identifyiable information (name age etc) shouldn't be stored in the same table as the medical information. Also, if you extract out the person info into its own table and a Diagnosis table that has the personID foreign key you can establish the 1 to many relationship you want.
Unless you use Dynamic SQL, the PIVOT operator will not work here. I assume that patients can come in on any date. The PIVOT operator works with a finite and predefined number of columns. Your options are to use Dynamic SQL to create the PIVOT table, or to use Excel or a reporting tool like SSRS to do a Pivot report.
I think the Dynamic SQL option would not be practical here, since, you could end up having hundreds of columns for each of the patient visit dates.
If you want to explore the Dynamic SQL option anyway, have a look here:
https://www.simple-talk.com/blogs/2007/09/14/pivots-with-dynamic-columns-in-sql-server-2005/

Data Matching with SQL and assigning Identity ID's

How to write a query that will match data and produce and identity for it.
For Example:
RecordID | Name
1 | John
2 | John
3 | Smith
4 | Smith
5 | Smith
6 | Carl
I want a query which will assign an identity after matching exactly on Name.
Expected Output:
RecordID | Name | ID
1 | John | 1X
2 | John | 1X
3 | Smith | 1Y
4 | Smith | 1Y
5 | Smith | 1Y
6 | Carl | 1Z
Note: The ID should be unique for every match. Also, it can be numbers or varchar.
Can somebody help me with this? The main thing is to assign the ID's.
Thanks.
How about this:
with temp as
(
select 1 as id,'John' as name
union
select 2,'John'
union
select 3,'Smith'
union
select 4,'Smith'
union
select 5,'Smith'
union
select 6,'Carl'
)
SELECT *, DENSE_RANK() OVER
(ORDER BY Name) as NewId
FROM TEMP
Order by id
The first part is for testing purposes only.
Please try:
SELECT *,
Rank() over (order by Name ASC)
FROM table
This structure seems to work:
CREATE TABLE #Table
(
Department VARCHAR(100),
Name VARCHAR(100)
);
INSERT INTO #Table VALUES
('Sales','michaeljackson'),
('Sales','michaeljackson'),
('Sales','jim'),
('Sales','jim'),
('Sales','jill'),
('Sales','jill'),
('Sales','jill'),
('Sales','j');
WITH Cte_Rank AS
(
SELECT [Name],
rw = ROW_NUMBER() OVER (ORDER BY [Name])
FROM #Table
GROUP BY [Name]
)
SELECT a.Department,
a.Name,
b.rw
FROM #Table a
INNER JOIN Cte_Rank b
ON a.Name = b.Name;