SQL Combining multiple rows into one - sql

I want to merge multiple rows into one, and only keep the values where the value is not NULL
Here is what i want to achieve:
I want from this
+----+-----------------+-----------------+-----------------+--------------------+
| ID | 1stNofification | 2ndNotification | 3rdNotification | NotificationNumber |
+----+-----------------+-----------------+-----------------+--------------------+
| 1 | 01.01.2019 | NULL | NULL | 1 |
+----+-----------------+-----------------+-----------------+--------------------+
| 1 | NULL | 02.02.2019 | NULL | 2 |
+----+-----------------+-----------------+-----------------+--------------------+
| 1 | NULL | NULL | 03.03.2019 | 3 |
+----+-----------------+-----------------+-----------------+--------------------+
| 2 | 06.01.2019 | NULL | NULL | 1 |
+----+-----------------+-----------------+-----------------+--------------------+
| 2 | NULL | 09.02.2019 | NULL | 2 |
+----+-----------------+-----------------+-----------------+--------------------+
| 2 | NULL | NULL | 11.03.2019 | 3 |
+----+-----------------+-----------------+-----------------+--------------------+
to this:
+----+-----------------+-----------------+-----------------+
| ID | 1stNofification | 2ndNotification | 3rdNotification |
+----+-----------------+-----------------+-----------------+
| 1 | 01.01.2019 | 02.02.2019 | 03.03.2019 |
+----+-----------------+-----------------+-----------------+
| 2 | 06.01.2019 | 09.02.2019 | 11.03.2019 |
+----+-----------------+-----------------+-----------------+
I tried something like:
SELECT
ID,
MAX(CASE WHEN a.NotificationNumber = 1 THEN 1stNotification END)1stNotification,
MAX(CASE WHEN a.NotificationNumber = 2 THEN 2ndNotification END)2ndNotification,
MAX(CASE WHEN a.NotificationNumber = 3 THEN 3rdNotification END)3rdNotification
FROM Notifications
GROUP BY ID
But that did not give me my expected results unfortunately.
Would really appreciate if someone could help me out :)

You just need to use max without any case
SELECT
ID,
MAX(1stNotification) AS 1stNotification,
MAX(2ndNotification) AS 2ndNotification,
MAX(3rdNotification) AS 3rdNotification
FROM Notifications
GROUP BY ID

I think you need something like this...
; with cte as (
select 1 as id, 'dd' as not1, null as not2, null as not3 , 1 as notifications
union all
select 1, null, 'df', null , 2
union all
select 1, null, null, 'vc', 3
union all
select 2, 'ws', null, null, 1
union all
select 2, null, 'xs', null, 2
union all
select 2, null, null, 'nm', 3
)
, ct as (
select id, coalesce(not1, not2, not3) as ol, notifications ,
'notification' + cast(notifications as varchar(5)) as Col
from cte
)
select * from (
select id, ol, col from ct )
as d
pivot (
max(ol) for col in ( [notification1], [notification2], [notification3] )
) as P
Here as per my understanding your notification columns in result are actually notification number mention in rows.

Related

Table structure issue - Query Pivot/Unpivot

I am trying to query a table for data conversion but I am running into issues with the structure. I am guessing I need to Pivot then Unpivot but I am not sure where to begin.
This is my current table
+----+-----------+-------+---------+
| ID | field | value | date |
+----+-----------+-------+---------+
| 1 | Draw1 | 1500 | NULL |
| 1 | Draw1Date | NULL | 4/15/16 |
| 1 | Draw1Fee | 100 | NULL |
| 1 | Draw2 | 2000 | NULL |
| 1 | Draw2Date | NULL | 3/14/17 |
| 1 | Draw2Fee | 100 | NULL |
| 2 | Draw1 | 800 | NULL |
| 2 | Draw1Date | NULL | 4/16/18 |
| 2 | Draw1Fee | 150 | NULL |
| 2 | Draw2 | 760 | NULL |
| 2 | Draw2Date | NULL | 5/6/18 |
| 2 | Draw2Fee | 150 | NULL |
+----+-----------+-------+---------+
Result needed
+----+-------+---------+---------+------+
| ID | Draws | Amount | Date | Fee |
+----+-------+---------+---------+------+
| 1 | Draw1 | 1500 | 4/15/16 | 100 |
| 1 | Draw2 | 2000 | 3/14/17 | 100 |
| 2 | Draw1 | 800 | 4/16/18 | 150 |
| 2 | Draw2 | 760 | 5/6/18 | 150 |
+----+-------+---------+---------+------+
My answer works for provided data. if you are looking for more general solution, for more variety of data, you may try to find another answer. I've not used PIVOT/UNPIVOT.
Test Data:
create table #t (ID int, field varchar(20), [value] int, [date] date)
insert into #t values
(1 ,'Draw1' , 1500 , NULL ),
(1 ,'Draw1Date' , NULL , '4/15/16'),
(1 ,'Draw1Fee' , 100 , NULL ),
(1 ,'Draw2' , 2000 , NULL ),
(1 ,'Draw2Date' , NULL , '3/14/17'),
(1 ,'Draw2Fee' , 100 , NULL ),
(2 ,'Draw1' , 800 , NULL ),
(2 ,'Draw1Date' , NULL , '4/16/18'),
(2 ,'Draw1Fee' , 150 , NULL ),
(2 ,'Draw2' , 760 , NULL ),
(2 ,'Draw2Date' , NULL , '5/6/18' ),
(2 ,'Draw2Fee' , 150 , NULL )
Query:
;with ct as (
select ID, field
from #t
where field in ('Draw1', 'Draw2')
group by ID, field
)
select ct.ID, ct.field
, t1.[value] as Amount, t2.[date] as [Date], t3.[value] as Fee
from ct
inner join #t t1 on t1.ID = ct.ID and t1.field = ct.field
inner join #t t2 on t2.ID = ct.ID and t2.field = ct.field + 'Date'
inner join #t t3 on t3.ID = ct.ID and t3.field = ct.field + 'Fee'
Result:
ID field Amount Date Fee
1 Draw1 1500 2016-04-15 100
1 Draw2 2000 2017-03-14 100
2 Draw1 800 2018-04-16 150
2 Draw2 760 2018-05-06 150
Try this...
SELECT tblAmount.id AS ID,
tblAmount.field AS Draws,
Max(tblAmount.value) AS Amount,
Max(tblDate.[date]) AS [Date],
Max(tblFee.value) AS Fee
FROM tablename tblAmount
INNER JOIN (SELECT id, field, [date]
FROM tablename
WHERE [date] IS NOT NULL AND field LIKE '%Date') tblDate
ON tblAmount.id = tblDate.id
AND tblDate.field LIKE tblAmount.field + '%'
INNER JOIN (SELECT id, field, value
FROM tablename
WHERE value IS NOT NULL AND field LIKE '%Fee') tblFee
ON tblAmount.id = tblFee.id
WHERE tblAmount.value IS NOT NULL
AND tblAmount.field NOT LIKE '%Fee'
AND tblAmount.field NOT LIKE '%Date'
GROUP BY tblAmount.id, tblAmount.field
ORDER BY tblAmount.id, tblAmount.field
Output
+----+-------+--------+------------+-----+
| ID | Draws | Amount | Date | Fee |
+----+-------+--------+------------+-----+
| 1 | Draw1 | 1500 | 2016-04-15 | 100 |
| 1 | Draw2 | 2000 | 2017-03-14 | 100 |
| 2 | Draw1 | 800 | 2018-04-16 | 150 |
| 2 | Draw2 | 760 | 2018-05-06 | 150 |
+----+-------+--------+------------+-----+
Demo: http://www.sqlfiddle.com/#!18/97688/101/0
I would simply do:
select id, left(field, 5),
max(case when len(field) = 5 then value end) as value,
max(case when field like '%date' then value end) as date,
sum(case when field like '%fee' then value end) as fee
from t
group by id, left(field, 5);
If your field is really more complex, do you are looking for date and fee at the end, by anything before, then use cross apply:
select t.id, v.draws,
max(case when t.field = v.draws then value end) as value,
max(case when t.field like '%date' then value end) as date,
sum(case when t.field like '%fee' then value end) as fee
from t cross apply
(values (replace(replace(field, 'date', ''), 'fee', '')) v(draws)
group by id, v.draws;

joining more than two tables without repeating values

I want to join three tables,
I have three tables user, profession and education where "uid" is primary key for user table and foreign key for other two tables. I want to join these tables to produce result in one single table
user profession education
+------+-------+ +-----+----------+ +-----+---------+
| uid | uName | | uid | profName | | uid | eduName |
+------+-------+ +-----+----------+ +-----+---------+
| 1 | aaa | | 1 | prof1 | | 1 | edu1 |
| 2 | bbb | | 1 | prof2 | | 1 | edu2 |
| 3 | ccc | | 2 | prof1 | | 1 | edu3 |
| | | | 3 | prof3 | | 3 | edu4 |
| | | | 3 | prof2 | | | |
+------+-------+ +-----+----------+ +-----+---------+
Expected output
+------+-------+-----+----------+-----+---------+
| uid | uName | uid | profName | uid | eduName |
+------+-------+-----+----------+-----+---------+
| 1 | aaa | 1 | prof1 | 1 | edu1 |
| null | null | 1 | prof2 | 1 | edu2 |
| null | null |null | null | 1 | edu3 |
| 2 | bbb | 2 | prof1 | null| null |
| 3 | ccc | 3 | prof3 | 3 | edu4 |
| null | null | 3 | prof2 | null| null |
+------+-------+-----+----------+-----+---------+
I tried following query
select u.uid ,u.uName,p.uid , p.profName,e.uid,e.eduName
from user u inner join profession p on u.uid=p.pid
inner join education e on u.uid = e.uid
where u.uid=p.uid
and u.uid=e.uid
and i.uid=1
Which gives me duplicate values
+------+-------+-----+----------+-----+---------+
| uid | uName | uid | profName | uid | eduName |
+------+-------+-----+----------+-----+---------+
| 1 | aaa | 1 | prof1 | 1 | edu1 |
| 1 | aaa | 1 | prof2 | 1 | edu1 |
| 1 | aaa | 1 | prof1 | 1 | edu2 |
| 1 | aaa | 1 | prof2 | 1 | edu2 |
| 1 | aaa | 1 | prof1 | 1 | edu3 |
| 1 | aaa | 1 | prof2 | 1 | edu3 |
+------+-------+-----+----------+-----+---------+
Is there a way to get the output with not repeating the values.
Thanks
Bit of a swine this one.
I agree with #GordonLinoff that ideally this presentation would be done on the client side.
However, if we wish to do it in SQL, then the basic approach is that you have to get the maximum number of rows that will be consumed by each user (based on a count of how many entries they have in each of the professions and educations tables, and then of these counts, the max count).
Once we have the number of rows required for each user, we expand the rows out for each user as necessary using a numbers table (I've included a number generator for the purpose).
Then we join each table on, according to the uid and the row number of the entry in the joined table relative to the row number of the "expanded" rows for each user. Then we select the relevant columns, and that's us done. Pay the nurse on the way out!
WITH
number_table(number) AS
(
SELECT
(ones.n) + (10 * tens.n) + (100 * hundreds.n) AS number
FROM --available range 0 to 999
(VALUES (0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) AS ones(n)
,(VALUES (0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) AS tens(n)
,(VALUES (0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) AS hundreds(n)
)
,users(u_uid, userName) AS
(
SELECT 1, 'aaa'
UNION ALL
SELECT 2, 'bbb'
UNION ALL
SELECT 3, 'ccc'
)
,professions(p_u_uid, profName) AS
(
SELECT 1, 'prof1'
UNION ALL
SELECT 1, 'prof2'
UNION ALL
SELECT 2, 'prof1'
UNION ALL
SELECT 3, 'prof3'
UNION ALL
SELECT 3, 'prof2'
)
,educations(e_u_uid, eduName) AS
(
SELECT 1, 'edu1'
UNION ALL
SELECT 1, 'edu2'
UNION ALL
SELECT 1, 'edu3'
UNION ALL
SELECT 3, 'edu4'
)
,row_counts(uid, row_count) AS
(
SELECT u_uid, COUNT(u_uid) FROM users GROUP BY u_uid
UNION ALL
SELECT p_u_uid, COUNT(p_u_uid) FROM professions GROUP BY p_u_uid
UNION ALL
SELECT e_u_uid, COUNT(e_u_uid) FROM educations GROUP BY e_u_uid
)
,max_counts(uid, max_count) AS
(
SELECT uid, MAX(row_count) FROM row_counts GROUP BY uid
)
SELECT
u_uid
,userName
,p_u_uid
,profName
,e_u_uid
,eduName
FROM
max_counts
INNER JOIN
number_table ON number BETWEEN 1 AND max_count
LEFT JOIN
(
SELECT u_uid, userName, ROW_NUMBER() OVER (PARTITION BY u_uid ORDER BY userName) AS user_match
FROM users
) AS users
ON u_uid = uid
AND number = user_match
LEFT JOIN
(
SELECT p_u_uid, profName, ROW_NUMBER() OVER (PARTITION BY p_u_uid ORDER BY profName) AS prof_match
FROM professions
) AS professions
ON p_u_uid = uid
AND number = prof_match
LEFT JOIN
(
SELECT e_u_uid, eduName, ROW_NUMBER() OVER (PARTITION BY e_u_uid ORDER BY eduName) AS edu_match
FROM educations
) AS educations
ON e_u_uid = uid
AND number = edu_match
ORDER BY
IIF(COALESCE(u_uid, p_u_uid, e_u_uid) IS NULL, 1, 0) ASC --nulls last
,COALESCE(u_uid, p_u_uid, e_u_uid) ASC
,IIF(COALESCE(p_u_uid, e_u_uid) IS NULL, 1, 0) ASC --nulls last
,COALESCE(p_u_uid, e_u_uid) ASC
,IIF(e_u_uid IS NULL, 1, 0) ASC --nulls last
,e_u_uid ASC
And the results:
u_uid userName p_u_uid profName e_u_uid eduName
----------- -------- ----------- -------- ----------- -------
1 aaa 1 prof1 1 edu1
NULL NULL 1 prof2 1 edu2
NULL NULL NULL NULL 1 edu3
2 bbb 2 prof1 NULL NULL
3 ccc 3 prof2 3 edu4
NULL NULL 3 prof3 NULL NULL
Did you try the distinct keyword?
select DISTINCT u.uid ,u.uName,p.uid , p.profName,e.uid,e.eduName
from user u inner join profession p on u.uid=p.pid
inner join education e on u.uid = e.uid
where u.uid=p.uid
and u.uid=e.uid
and i.uid=1

Select distinct one field other first non empty or null

I have table
| Id | val |
| --- | ---- |
| 1 | null |
| 1 | qwe1 |
| 1 | qwe2 |
| 2 | null |
| 2 | qwe4 |
| 3 | qwe5 |
| 4 | qew6 |
| 4 | qwe7 |
| 5 | null |
| 5 | null |
is there any easy way to select distinct 'id' values with first non null 'val' values. if not exist then null. for example
result should be
| Id | val |
| --- | ---- |
| 1 | qwe1 |
| 2 | qwe4 |
| 3 | qwe5 |
| 4 | qew6 |
| 5 | null |
In your case a simple GROUP BY should be the solution:
SELECT Id
,MIN(val)
FROM dbo.mytable
GROUP BY Id
Whenever using a GROUP BY, you have to use an aggregate function on all columns, which are not listed in the GROUP BY.
If an Id has a value (val) other than NULL, this value will be returned.
If there are just NULLs for the Id, NULL will be returned.
As far as i unterstood (regarding your comment), this is exactly what you're going to approach.
If you always want to have "the first" value <> NULL, you'll need another sort criteria (like a timestamp column) and might be able to solve it with a WINDOW-function.
If you want the first non-NULL value (where "first" is based on id), then MIN() doesn't quite do it. Window functions do:
select t.*
from (select t.*,
row_number() over (partition by id
order by (case when val is not null then 1 else 2 end),
id
) as seqnum
from t
) t
where seqnum = 1;
SQL Fiddle:
Create Table from SQL Fiddle:
CREATE TABLE tab1(pid integer, id integer, val varchar(25))
Insert dummy records :
insert into tab1
values (1, 1 , null),
(2, 1 , 'qwe1' ),
(3, 1 , 'qwe2'),
(4, 2 , null ),
(5, 2 , 'qwe4' ),
(6, 3 , 'qwe5' ),
(7, 4 , 'qew6' ),
(8, 4 , 'qwe7' ),
(9, 5 , null ),
(10, 5 , null );
fire below query:
SELECT Id ,MIN(val) as val FROM tab1 GROUP BY Id;

Select multiple rows for distinct column if column not null, otherwise select first row where column is null

I have an interesting query I need to execute. For Table A below, I want to select ALL non null phoneNumber for distinct userId, but if a non null phoneNumber value doesn't exist for distinct userId select only one null phoneNumber for distinct userId.
Table A
| id | userId | phoneNumber | emailAddress |
-------------------------------------------
| 1 | 1 | 0123456789 | null |
| 2 | 1 | 1234567890 | null |
| 3 | 1 | null | test#gmail |
| 4 | 2 | null | andy#yahoo |
| 5 | 2 | null | andy#gmail |
Expected Results
| id | userId | phoneNumber | emailAddress |
-------------------------------------------
| 1 | 1 | 0123456789 | null |
| 2 | 1 | 1234567890 | null |
| 5 | 2 | null | andy#gmail |
I wrote the query below and it returns the desired results, but I'm interested to see if there is a better, more optimal way to achieve this. Rather than writing multiple subqueries.
SELECT *
FROM A
WHERE phoneNumber IS NOT NULL
UNION
SELECT *
FROM A
WHERE id IN (SELECT MAX(id)
FROM A WHERE phoneNumber IS NULL
AND userId NOT IN (SELECT userId
FROM A
WHERE phoneNumber IS NOT NULL)
GROUP BY userId)
You can use the COUNT() and ROW_NUMBER() analytic functions:
SELECT *
FROM (
SELECT A.*,
COUNT( phoneNumber) OVER ( PARTITION BY userId ) AS ct,
ROW_NUMBER() OVER ( PARTITION BY userId ORDER BY id DESC ) AS rn
FROM A
)
WHERE phoneNumber IS NOT NULL
OR ( ct = 0 AND rn = 1 );

Convert tuple value to column names

Got something like:
+-------+------+-------+
| count | id | grade |
+-------+------+-------+
| 1 | 0 | A |
| 2 | 0 | B |
| 1 | 1 | F |
| 3 | 1 | D |
| 5 | 2 | B |
| 1 | 2 | C |
I need:
+-----+---+----+---+---+---+
| id | A | B | C | D | F |
+-----+---+----+---+---+---+
| 0 | 1 | 2 | 0 | 0 | 0 |
| 1 | 0 | 0 | 0 | 1 | 1 |
| 2 | 0 | 5 | 1 | 0 | 0 |
I don't know if I can even do this. I can group by id but how would you read the count value for each grade column?
CREATE TABLE #MyTable(_count INT,id INT , grade VARCHAR(10))
INSERT INTO #MyTable( _count ,id , grade )
SELECT 1,0,'A' UNION ALL
SELECT 2,0,'B' UNION ALL
SELECT 1,1,'F' UNION ALL
SELECT 3,1,'D' UNION ALL
SELECT 5,2,'B' UNION ALL
SELECT 1,2,'C'
SELECT *
FROM
(
SELECT _count ,id ,grade
FROM #MyTable
)A
PIVOT
(
MAX(_count) FOR grade IN ([A],[B],[C],[D],[F])
)P
You need a "pivot" table or "cross-tabulation". You can use a combination of aggregation and CASE statements, or, more elegantly the crosstab() function provided by the additional module tablefunc. All basics here:
PostgreSQL Crosstab Query
Since not all keys in grade have values, you need the 2-parameter form. Like this:
SELECT * FROM crosstab(
'SELECT id, grade, count FROM table ORDER BY 1,2'
, $$SELECT unnest('{A,B,C,D,F}'::text[])$$
) ct(id text, "A" int, "B" int, "C" int, "D" int, "F" int);