SQL - selecting only certain rows if they exist - sql

I have a table that contains Home addresses and Mailing addresses. It looks like this:
ID Name StNum StName City State Zip Type
-- ---- ----- ------ ---- ----- --- ----
1 Joe 1234 Main St Waco TX 76767 HOM
1 Joe 2345 High St Waco TX 76763 MLG
2 Amy 3456 Broad St Athens GA 34622 HOM
3 Mel 987 Front St Cary NC 65331 HOM
3 Mel 1111 Main Ave Hilo HI 99779 MLG
I need to write an SQL statement that will only return the Mailing address (MLG record) if it exists, and if not, will return the Home address (HOM record).
The expected results from this table would be:
ID Name StNum StName City State Zip Type
-- ---- ----- ------ ---- ----- --- ----
1 Joe 2345 High St Waco TX 76763 MLG
2 Amy 3456 Broad St Athens GA 34622 HOM
3 Mel 1111 Main Ave Hilo HI 99779 MLG
Any help that you can provide would be much appreciated! Thanks!

use correlated subquery
select * from
(
select *,case when Type='MLG' then 1 else 0 end as typeval
from tablename
)A where typeval in (select max(case when Type='MLG' then 1 else 0 end) from tablename b
where a.name=b.name)
OR if your DB supports row_number() then u can try below -
select * from
(
select *, row_number() over(partition by name order by case when Type='MLG' then 1 else 0 end desc)
from tablename
)A where rn=1

In case you are using SQL Server, i would solve it with the ROW_NUMBER function.
SELECT ID, Name, StNum, StName, City, State, Zip, Type
FROM (
SELECT *
,ROW_NUMBER() OVER (PARTITION BY ID ORDER BY Type DESC) AS Rn
FROM yourtable
)
WHERE Rn = 1

This can be done using a WHERE clause that exclude the ids of the users who have MLG
Schema (MySQL v5.7)
CREATE TABLE test (
`ID` INTEGER,
`Name` VARCHAR(3),
`StNum` INTEGER,
`StName` VARCHAR(8),
`City` VARCHAR(6),
`State` VARCHAR(2),
`Zip` INTEGER,
`Type` VARCHAR(3)
);
INSERT INTO test
(`ID`, `Name`, `StNum`, `StName`, `City`, `State`, `Zip`, `Type`)
VALUES
('1', 'Joe', '1234', 'Main St', 'Waco', 'TX', '76767', 'HOM'),
('1', 'Joe', '2345', 'High St', 'Waco', 'TX', '76763', 'MLG'),
('2', 'Amy', '3456', 'Broad St', 'Athens', 'GA', '34622', 'HOM'),
('3', 'Mel', '987', 'Front St', 'Cary', 'NC', '65331', 'HOM'),
('3', 'Mel', '1111', 'Main Ave', 'Hilo', 'HI', '99779', 'MLG');
Query #1
SELECT id,
name,
StNum,
StName,
City,
State,
Zip,
Type
FROM test t1
WHERE t1.`Type` = 'MLG'
OR t1.id NOT IN
(
SELECT id
FROM test t2
WHERE t2.`Type` = 'MLG'
);
Output :
| id | name | StNum | StName | City | State | Zip | Type |
| --- | ---- | ----- | -------- | ------ | ----- | ----- | ---- |
| 1 | Joe | 2345 | High St | Waco | TX | 76763 | MLG |
| 2 | Amy | 3456 | Broad St | Athens | GA | 34622 | HOM |
| 3 | Mel | 1111 | Main Ave | Hilo | HI | 99779 | MLG |
View on DB Fiddle
Or, my first dumb version :
This can be done using UNION
Schema (MySQL v5.7)
CREATE TABLE test (
`ID` INTEGER,
`Name` VARCHAR(3),
`StNum` INTEGER,
`StName` VARCHAR(8),
`City` VARCHAR(6),
`State` VARCHAR(2),
`Zip` INTEGER,
`Type` VARCHAR(3)
);
INSERT INTO test
(`ID`, `Name`, `StNum`, `StName`, `City`, `State`, `Zip`, `Type`)
VALUES
('1', 'Joe', '1234', 'Main St', 'Waco', 'TX', '76767', 'HOM'),
('1', 'Joe', '2345', 'High St', 'Waco', 'TX', '76763', 'MLG'),
('2', 'Amy', '3456', 'Broad St', 'Athens', 'GA', '34622', 'HOM'),
('3', 'Mel', '987', 'Front St', 'Cary', 'NC', '65331', 'HOM'),
('3', 'Mel', '1111', 'Main Ave', 'Hilo', 'HI', '99779', 'MLG');
Query #1
SELECT id,
name,
StNum,
StName,
City,
State,
Zip,
Type
FROM test t1
WHERE t1.`Type` = 'MLG'
UNION ALL
SELECT id,
name,
StNum,
StName,
City,
State,
Zip,
Type
FROM test t2
WHERE t2.id NOT IN (SELECT id FROM test t3 WHERE t3.`Type` = 'MLG')
ORDER BY id;
Output
| id | name | StNum | StName | City | State | Zip | Type |
| --- | ---- | ----- | -------- | ------ | ----- | ----- | ---- |
| 1 | Joe | 2345 | High St | Waco | TX | 76763 | MLG |
| 2 | Amy | 3456 | Broad St | Athens | GA | 34622 | HOM |
| 3 | Mel | 1111 | Main Ave | Hilo | HI | 99779 | MLG |
View on DB Fiddle

This is a prioritization query. With two values, often the simplest method is union all with not exists (or not in).
That does not generalize well For more values, using row_number() with case is convenient:
select t.*
from (select t.*,
row_number() over (partition by id
order by (case when type = 'MLG' then 1 else 2 end)
) as seqnum
from t
) t
where seqnum = 1;
In your particular case, you could use order by type desc, because the two types happen to be prioritized in reverse alphabetical ordering. However, I recommend using case because the intention is more explicit.

Related

Combine First and Last Names of people based on criteria

So I have an interesting scenario. There are 2 events, for which one of the Married EventType I'd like to join associated married couples of the house, grouped by an identifier (HouseID; 2 people in the same house), the EventType, and the EventDate. For the case such as an EventType of Birthday, the 2 residents of the house would not be combined in the same row. In the case of an EventType of Wedding, combine the name result of 2 rows (2 rules below based on LastName) into 1. It's possible to have only 1 person for a HouseID for the EventType of Wedding or Birthday, so therefore they would list as an individual row. The combining rules for EventType of Wedding would be as follows:
If the last names are the same, the FinalName column result would be Will and Mary Stanton
If the last names are different, the FinalName column result would be Stephen Jacobs and Janetsy Lilly.
The combining of the names would be contingent on the HouseID, EventType, and EventDate being the same, specific only to Wedding. This is because it's possible for 2 people to live in a house that are married, but not to each other which we base off the EventDate; we assume the EventDate is the indicator that they are married to each other. The example table input is as follows:
DECLARE #t TABLE (
HouseID INT,
FirstName NVARCHAR(64),
LastName NVARCHAR(64),
EventType NVARCHAR(64),
EventDate DATE
);
INSERT INTO #t (HouseID, FirstName, LastName, EventType, EventDate)
VALUES
(1, 'Will', 'Stanton', 'Birthday', '1974-01-05'),
(1, 'Mary', 'Stanton', 'Birthday', '1980-05-22'),
(2, 'Jason', 'Stockmore', 'Birthday', '1987-12-07'),
(3, 'Mark', 'Mellony', 'Wedding', '2021-04-04'),
(3, 'Stacy', 'Mellony', 'Wedding', '2021-04-04'),
(4, 'Stephen', 'Johnson', 'Wedding', '2012-01-30'),
(4, 'Janetsy', 'Johnson', 'Wedding', '2012-01-30'),
(5, 'George', 'Jackson', 'Wedding', '2009-11-15'),
(5, 'Sally', 'Mistmoore', 'Wedding', '2009-11-15'),
(6, 'Sandy', 'Katz', 'Wedding', '2010-03-19'),
(6, 'Jeff', 'Trilov', 'Wedding', '2016-09-09'),
(7, 'Sandra', 'Kirchbaum', 'Wedding', '2011-05-22'),
(8, 'Jessica', 'Bower', 'Birthday', '1996-02-26'),
(8, 'Frank', 'Fjorn', 'Birthday', '1969-07-19');
The ideal result based on the input table would resemble:
| HouseID | FinalName | EventType | EventDate |
| ------- | ---------------------------------- | --------- | ---------- |
| 1 | Mary Stanton | Birthday | 1974-01-05 |
| 1 | Will Stanton | Birthday | 1980-05-22 |
| 2 | Jason Stockmore | Birthday | 1987-12-07 |
| 3 | Mark and Stacy Mellony | Wedding | 2021-04-04 |
| 4 | Stephen and Janetsy Johnson | Wedding | 2012-01-30 |
| 5 | George Jackson and Sally Mistmoore | Wedding | 2009-11-15 |
| 6 | Sandy Katz | Wedding | 2010-03-19 |
| 6 | Jeff Trilov | Wedding | 2016-09-09 |
| 7 | Sandra Kirchbaum | Wedding | 2011-05-22 |
| 8 | Jessica Bower | Birthday | 1996-02-26 |
| 8 | Frank Fjorn | Birthday | 1969-07-19 |
I have tried a couple of approaches; one of which is using an update statement to update the First Name and update a subsequent FirstName based on Row number using a previously set #Values variable, unioning the result of the types that do not combine (in this case, Birthday). Here is where I built the names then used the MAX() aggregation to select the larger result:
SELECT HouseID,
FirstName
, LastName
, EventType
, EventDate
, RowNum = ROW_NUMBER() OVER (PARTITION BY LastName, EventType ORDER BY 1/0)
, Values1 = CAST(NULL AS VARCHAR(MAX))
INTO #EntityValues1
FROM #t
WHERE EventType = 'Wedding'
UPDATE #EntityValues1
SET #Values1 = Values1 =
CASE WHEN RowNum = 1
THEN FirstName
ELSE #Values1 + ' and ' + FirstName
END
However this example only works with combining FirstName1 + FirstName2 + LastName (in a subsequent query with MAX(Values1) + ' ' + LastName. I had to do a subsequent query to take the approach where I am combining names that do not have the same last name. I know this particular query is a bit tricky, but I'm wondering if there's any magic I'm missing out on. I've seen some suggestions that use a FOR XML approach with STUFF involved, and some other suggestions, but this one appears to be a tough one.
Here is an answer, with a working demo
;WITH
[DoubleWeddings] AS (
SELECT
[HouseID]
FROM
#t
WHERE
[EventType] = 'Wedding'
GROUP BY
[HouseID],
[EventDate]
HAVING
COUNT(*) = 2
),
[DoubleWeddingsSameLastName] AS (
SELECT
T.[HouseID]
FROM
[DoubleWeddings] DW
JOIN
#t T
ON T.[HouseID] = DW.[HouseID]
GROUP BY
T.[HouseID],
T.[LastName]
HAVING
COUNT(*) = 2
),
[DoubleWeddingsDifferentLastName] AS (
SELECT [HouseID] FROM [DoubleWeddings]
EXCEPT
SELECT [HouseID] FROM [DoubleWeddingsSameLastName]
),
[Couples] AS (
SELECT
T.[HouseID],
ROW_NUMBER() OVER (PARTITION BY T.[HouseID] ORDER BY 1/0) [RN],
T.[FirstName],
T.[LastName],
T.[EventType],
T.[EventDate]
FROM
[DoubleWeddings] DW
JOIN
#t T
ON T.[HouseID] = DW.[HouseID]
)
SELECT
DWSL.[HouseID],
FORMATMESSAGE(
'%s and %s %s',
F.[FirstName],
S.[FirstName],
S.[LastName]) [FinalName],
F.[EventType],
F.[EventDate]
FROM
[DoubleWeddingsSameLastName] DWSL
JOIN
[Couples] F
ON F.[HouseID] = DWSL.[HouseID] AND F.[RN] = 1
JOIN
[Couples] S
ON S.[HouseID] = DWSL.[HouseID] AND S.[RN] = 2
UNION ALL
SELECT
DWDL.[HouseID],
FORMATMESSAGE(
'%s %s and %s %s',
F.[FirstName],
F.[LastName],
S.[FirstName],
S.[LastName]) [FinalName],
F.[EventType],
F.[EventDate]
FROM
[DoubleWeddingsDifferentLastName] DWDL
JOIN
[Couples] F
ON F.[HouseID] = DWDL.[HouseID] AND F.[RN] = 1
JOIN
[Couples] S
ON S.[HouseID] = DWDL.[HouseID] AND S.[RN] = 2
UNION ALL
SELECT
T.[HouseID],
FORMATMESSAGE(
'%s %s',
T.[FirstName],
T.[LastName]) [FinalName],
T.[EventType],
T.[EventDate]
FROM
#t T
LEFT JOIN
[DoubleWeddings] DW
ON DW.[HouseID] = T.[HouseID]
WHERE
DW.[HouseID] IS NULL
ORDER BY
[HouseID],
[EventDate],
[FinalName]
There is one issue, I've used the ORDER BY 1/0 technique to skip providing an order for the ROW_NUMBER() which tends to assign the row numbers in the order of the underlying data. However, this is not guaranteed, and could very depending on the parallelization of the query.
It would be better if the order of the combination was provided by a column in the data, however, none is present in the example.
To get this event calendar:
select
t.HouseID,
CONCAT(STRING_AGG(CONCAT(FirstName,
' ',
CASE WHEN ln.LastName<>t.LastName
then t.LastName END),' and '),
' ',
MIN(t.LastName)) as Name,
t.EventType,
t.EventDate
from t
left join (select t1.HouseID, min(t1.LastName) as LastName from t as t1 GROUP BY t1.HouseID having count(t1.LastName)=1) ln on ln.HouseID = t.HouseID
GROUP BY t.EventDate, t.EventType, t.HouseID
order by month(t.EventDate), day(t.EventDate);
output:
HouseID
Name
EventType
EventDate
1
Will Stanton
Birthday
1974-01-05
4
Stephen and Janetsy Johnson
Wedding
2012-01-30
8
Jessica Bower
Birthday
1996-02-26
6
Sandy Katz
Wedding
2010-03-19
3
Mark and Stacy Mellony
Wedding
2021-04-04
7
Sandra Kirchbaum
Wedding
2011-05-22
1
Mary Stanton
Birthday
1980-05-22
8
Frank Fjorn
Birthday
1969-07-19
6
Jeff Trilov
Wedding
2016-09-09
5
George and Sally Jackson
Wedding
2009-11-15
2
Jason Stockmore
Birthday
1987-12-07
see: DBFIDDLE
EDIT:
Corrected the columnname (Name to FinalName), and the ordering of the results.
Fixed the value for HouseId=5
select
t.HouseID,
CONCAT(STRING_AGG(CONCAT(FirstName,
' ',
ISNULL(ln.LastName, t.LastName)
),' and '),
' ',
MIN(ln.LastName)) as FinalName,
t.EventType,
t.EventDate
from t
left join (select t1.HouseID, min(t1.LastName) as LastName from t as t1 GROUP BY t1.HouseID having count(t1.LastName)=1) ln on ln.HouseID = t.HouseID
GROUP BY t.EventDate, t.EventType, t.HouseID
order by HouseID, EventDate
;
see: DBFIDDLE
output:
HouseID
FinalName
EventType
EventDate
1
Will Stanton
Birthday
1974-01-05
1
Mary Stanton
Birthday
1980-05-22
2
Jason Stockmore Stockmore
Birthday
1987-12-07
3
Mark Mellony and Stacy Mellony
Wedding
2021-04-04
4
Stephen Johnson and Janetsy Johnson
Wedding
2012-01-30
5
George Jackson and Sally Mistmoore
Wedding
2009-11-15
6
Sandy Katz
Wedding
2010-03-19
6
Jeff Trilov
Wedding
2016-09-09
7
Sandra Kirchbaum Kirchbaum
Wedding
2011-05-22
8
Frank Fjorn
Birthday
1969-07-19
8
Jessica Bower
Birthday
1996-02-26

SQL - Multiple records into one line with conditions

I have two tables that look like this
Account Table
| Account Number | Account Name |
| -------------- | -------------- |
| 12345 | Bob Jones |
| 12346 | Jenny Smith |
| 12347 | Mary Frederick |
Email Table
| Account Number | Email Type |Email | IsPrimary |
| ---------------------- | --------------|-----------------------|-----------------|
| 12345 | WORK | BOBSMITH#STACK.COM | 0 |
| 12345 | HOME | BOBSMITH#GMAIL.COM | 0 |
| 12345 | WORK | BOBSMITH#APPLE.COM | 1 |
| 12345 | HOME | BOBSMITH#HOTMAIL.COM | 0 |
| 12346 | HOME | JMSMITH#GMAIL.COM | 1 |
| 12346 | WORK | JMSMITH#DISNEY.COM | 0 |
| 12346 | HOME | JMSMITH#HOTMAIL.COM | 0 |
| 12347 | HOME | MFRED#HOTMAIL.COM | 0 |
I need to end up with results like this:
| Account Number | Account Name | Home Email | Work Email | Alternate Email |
| ------------------------ | ----------------- |--------------------|--------------------|----------------------|
| 12345 | Bob Jones | BOBSMITH#GMAIL.COM | BOBSMITH#APPLE.COM | BOBSMITH#HOTMAIL.COM |
| 12346 | Jenny Smith | JMSMITH#GMAIL.COM | JMSMITH#DISNEY.COM | JMSMITH#HOTMAIL.COM |
| 12347 | Mary Frederick | MFRED#HOTMAIL.COM | | |
I have all possible combinations of emails such as multiple work and no home, no primaries at all, multiple primaries... etc.
I need the logic to be:
Home Email: When type is Home. If there is a primary, choose the first one. If there is no primary, choose the first non-primary home email. Otherwise make it blank
Work Email: Same as above but for work
Alternate Email: Any third email on the list. If there are more than 3, choose home over work.
I have tried a ton of things and nothing has worked well. Here is my top attempt -two things don't work about it. The 'top 1' returns a lot of nulls and the code for alternates isn't always mutually exclusive to the home and work email, especially for people that have no primary checked:
select distinct
A01.AccountNumber,
A01.FirstName,
A01.LastName,
(
select top 1
case
when (A07.Active = 1 and A07.EmailType = 'HOME' and A07.UseAsPrimary = 1)
then A07.EmailAddress
when (A07.Active = 1 and A07.EmailType = 'HOME')
then A07.EmailAddress
end
from A07_AccountEmails AS A07
where A07.AccountNumber = A01.AccountNumber
) as HomeEmail
,(
select top 1
case
when (A07.Active = 1 and A07.EmailType = 'WORK' and A07.EmailType is not null)--and A07.UseAsPrimary = 1)
then A07.EmailAddress
when (A07.Active = 1 and A07.EmailType = 'WORK')
then A07.EmailAddress
end
from A07_AccountEmails AS A07
where A07.AccountNumber = A01.AccountNumber
) as WorkEmail
,(
select top 1 (A07.EmailAddress)
from A07_AccountEmails A07
where A07.AccountNumber = A01.AccountNumber
and A07.Active = 1 and A07.UseAsPrimary = 0
) as AlternateEmailMulti
from A01_AccountMaster A01
left outer join A07_AccountEmails A07 on A07.AccountNumber = A01.AccountNumber
where
A01.[Status] = 'A'
It seems a simple sub-query per email address should do the trick? I have added them in an OUTER APPLY to allow the values to be used twice, once in the select, and once to be excluded from the alternate email column.
declare #Account table (Number int, [Name] varchar(64));
declare #Email table (AccountNumber int, [Type] varchar(4), Email varchar(256), IsPrimary bit);
insert into #Account (Number, [Name])
values
(12345, 'Bob Jones'),
(12346, 'Jenny Smith'),
(12347, 'Mary Frederick');
insert into #Email (AccountNumber, [Type], Email, IsPrimary)
values
(12345, 'WORK', 'BOBSMITH#STACK.COM', 0),
(12345, 'HOME', 'BOBSMITH#GMAIL.COM', 0),
(12345, 'WORK', 'BOBSMITH#APPLE.COM', 1),
(12345, 'HOME', 'BOBSMITH#HOTMAIL.COM', 0),
(12346, 'HOME', 'JMSMITH#GMAIL.COM', 1),
(12346, 'WORK', 'JMSMITH#DISNEY.COM', 0),
(12346, 'HOME', 'JMSMITH#HOTMAIL.COM', 0),
(12347, 'HOME', 'MFRED#HOTMAIL.COM', 0);
select A.Number, A.[Name]
, E.Home [Home Email]
, E.Work [Work Email]
, (
select top 1 Email
from #Email
where AccountNumber = A.Number
and Email not in (E.Home, E.Work)
order by case when [Type] = 'HOME' then 1 else 0 end desc
)
from #Account A
outer apply (
select
(select top 1 Email from #Email where AccountNumber = A.Number and [Type] = 'HOME' order by IsPrimary desc),
(select top 1 Email from #Email where AccountNumber = A.Number and [Type] = 'WORK' order by IsPrimary desc)
) E (Home, Work);
Returns your desired results:
Account Number
Account Name
Home Email
Work Email
Alternate Email
12345
Bob Jones
BOBSMITH#HOTMAIL.COM
BOBSMITH#APPLE.COM
BOBSMITH#GMAIL.COM
12346
Jenny Smith
JMSMITH#GMAIL.COM
JMSMITH#DISNEY.COM
JMSMITH#HOTMAIL.COM
12347
Mary Frederick
MFRED#HOTMAIL.COM
NULL
NULL
Note: If you provide your sample data as DDL+DML (as I have here) it makes it much easier for people to assist.
Here is an alternative approach which list all e-mails, primaries first.
We use group_,concat to show all matching entries, order by primary prim inversed by desc and finally a newline separator \n to make a vertical list.
NB : It doesn't print correctly in the terminal because of the newlines.
USE test;
CREATE TABLE user (
acc INT,
nam CHAR(10)
);
INSERT INTO user VALUES ( 1, 'Andrew');
INSERT INTO user VALUES ( 2, 'Bernard');
INSERT INTO user VALUES ( 3, 'Charles');
CREATE TABLE telMail (
acc INT,
email CHAR(25),
type CHAR(5),
prim INT
);
INSERT INTO telMail VALUES(1,'a#w0','Work',0);
INSERT INTO telMail VALUES(1,'a#w1','Work',0);
INSERT INTO telMail VALUES(1,'ap#w2','Work',1);
INSERT INTO telMail VALUES(1,'a#w3','Work',0);
INSERT INTO telMail VALUES(1,'a#h1','Home',0);
INSERT INTO telMail VALUES(1,'a#h2','Home',0);
INSERT INTO telMail VALUES(2,'b#h1','Home',0);
INSERT INTO telMail VALUES(2,'b#w1','Work',0);
INSERT INTO telMail VALUES(2,'b#w2','Work',0);
INSERT INTO telMail VALUES(3,'c#h1','Home',0);
INSERT INTO telMail VALUES(3,'cp#h1','Home',1);
INSERT INTO telMail VALUES(3,'c#w1','Work',0);
SELECT
u.acc 'Account',
u.nam 'Name',
GROUP_CONCAT(DISTINCT w.email ORDER BY w.prim DESC SEPARATOR '\n' ) work_mail,
GROUP_CONCAT(DISTINCT h.email ORDER BY h.prim DESC SEPARATOR '\n' ) home_mail
FROM
(SELECT acc,email,prim FROM telMail WHERE type ='Home') h
JOIN
user u on u.acc=h.acc
JOIN
(SELECT acc,email,prim FROM telMail WHERE type ='Work') w ON u.acc = w.acc
GROUP BY u.acc, u.nam
ORDER BY u.acc;
DROP TABLE user;
DROP TABLE telMail;

PARTITION BY multiple column while inserting data from another table

How can I skip unique constraint error in SQL Server?
This is my source table:
CREATE TABLE source
(
RollNo INTEGER,
Nam VARCHAR(6),
Gender VARCHAR(1),
Score INTEGER
);
INSERT INTO source (RollNo, Nam, Gender, Score)
VALUES ('101', 'John', 'M', '85'),
('102', 'Tracy', 'F', '79'),
('103', 'Jake', 'M', '92'),
('104', 'Edgar', 'M', NULL),
('105', 'Monica', 'F', '25'),
('106', 'Monica', 'F', '50'),
('1070', 'Yash', 'M', '68'),
('107', 'Yash', 'M', '70'),
('108', 'SFS', 'M', '68'),
('18', 'SFS77', 'F', '65');
I want populate in the dest table from source table where name & Gender is a Unique key and SeqNo should increment automatically:
dest table description:
CREATE TABLE dest
(
SeqNo BIGINT IDENTITY(1000,1) PRIMARY KEY,
RollNo INTEGER,
Nam VARCHAR(6),
Gender VARCHAR(1),
Score INTEGER
);
Here's what I tried:
Attempt #1:
INSERT INTO dest (RollNo, Nam, Gender, Score)
SELECT
FIRST_VALUE(RollNo) OVER (PARTITION BY Nam, Gender ORDER BY Score DESC),
FIRST_VALUE(Nam) OVER (PARTITION BY Nam, Gender ORDER BY Score DESC),
FIRST_VALUE(Gender) OVER (PARTITION BY Nam, Gender ORDER BY Score DESC),
FIRST_VALUE(Score) OVER (PARTITION BY Nam, Gender ORDER BY Score DESC)
FROM
source
WHERE
Nam IS NOT NULL AND Gender IS NOT NULL ;
ERROR: Violation of UNIQUE KEY constraint
Attempt #2:
INSERT INTO dest (RollNo, Nam, Gender, Score)
SELECT MAX(RollNo),Nam, Gender, MAX(Score)
FROM source
GROUP BY Nam, Gender
ORDER BY MAX(Score) DESC;
Output:
| SeqNo | RollNo | Nam | Gender | Score |
|-------|--------|--------|--------|--------|
| 1000 | 103 | Jake | M | 92 |
| 1001 | 101 | John | M | 85 |
| 1002 | 102 | Tracy | F | 79 |
| 1003 | 1070 | Yash | M | 70 |
| 1004 | 108 | SFS | M | 68 |
| 1005 | 18 | SFS77 | F | 65 |
| 1006 | 106 | Monica | F | 50 |
| 1007 | 104 | Edgar | M | (null) |
If you see the row of yash it is taking max of RollNo. and max of Score which is wrong, I want it to take first value but I don't know how to do it.
Is there any other way to solve exclude this above two methods?
You can identify the row that has the highest score for each Nam/Gender tuple with ROW_NUMBER(), and use that information to filter the source data:
INSERT INTO dest (RollNo, Nam, Gender, Score)
SELECT RollNo, Nam, Gender, Score
FROM (
SELECT s.*,
ROW_NUMBER() OVER(PARTITION BY Nam, Gender ORDER BY Score DESC) rn
FROM source s
) s
WHERE rn = 1
Side note: I would recommend putting a unique constraint on Nam/Gender tuples in the target table, so potential duplicates are always rejected at insert time:
CREATE TABLE dest (
SeqNo BIGINT IDENTITY(1000,1) PRIMARY KEY,
RollNo INTEGER,
Name VARCHAR(6),
Gender VARCHAR(1),
Score INTEGER,
UNIQUE (Name, Gender)
);
Side note #2: don't put single quotes around column names; they stand for literal strings in standard SQL.

Convert multiple rows into a single row in SQL Server

I want to change multiple rows into a single row based on Number and Type:
+------+---------+----------+-------+---------+-------+
| Name | Address | City | State | Number | Type |
+------+---------+----------+-------+---------+-------+
| XYZ | 123 Rd | New York | NY | 123D | Code1 |
| XYZ | 123 Rd | New York | NY | 56A45 | Code2 |
| XYZ | 123 Rd | New York | NY | D45256 | Code3 |
| XYZ | 123 Rd | New York | NY | 345TT | Code2 |
| XYZ | 123 Rd | New York | NY | 34561NN | Code2 |
| XYZ | 123 Rd | New York | NY | 84567YY | Code2 |
+------+---------+----------+-------+---------+-------+
Result
+------+---------+----------+-------+-------+-------+----------+-----------+----------+--------+
| Name | Address | City | State | code1 | Code2 | code2_II | Code2_III | Code2_IV | Code3 |
+------+---------+----------+-------+-------+-------+----------+-----------+----------+--------+
| XYZ | 123 Rd | New York | NY | 123D | 56A45 | 345TT | 34561NN | 84567YY | D45256 |
+------+---------+----------+-------+-------+-------+----------+-----------+----------+--------+
Query I used only work for Code2 and Code2_II but I want to bring Code2_III and Code2_IV
select
Name, Addresss, City, State,
min(Number) as Code1,
(case when min(Number) <> max(Number) then max(Number) end) as Code2
from
t
group by
Name, Addresss, City, State;
As Tim mentions in the comments to your question, this will be very difficult to do without using dynamic SQL.
This code is not exactly what you are looking for but should get you most of the way there.
CREATE TABLE #Addresses (
[Name] varchar(255),
[Address] varchar(255),
[City] varchar(255),
[State] varchar(255),
[Number] varchar(255),
[Type] varchar(255)
)
INSERT INTO #Addresses
VALUES
('XYZ','123 Rd','New York','NY','123D','Code1'),
('XYZ','123 Rd','New York','NY','56A45','Code2'),
('XYZ','123 Rd','New York','NY','D45256','Code3'),
('XYZ','123 Rd','New York','NY','345TT','Code2'),
('XYZ','123 Rd','New York','NY','34561NN','Code2'),
('XYZ','123 Rd','New York','NY','84567YY','Code2');
SELECT *
FROM (
SELECT
[Name], [Address], [City], [State], [Number]
, [Type] + '_' + LTRIM(STR(rn)) AS NewType
FROM (
SELECT *
,ROW_NUMBER() OVER(PARTITION BY [Name], [Type] ORDER BY [Number]) rn
FROM #Addresses
) a
) p
PIVOT (
MAX([Number])
FOR [NewType] IN ([Code1_1], [Code2_1], [Code2_2], [Code2_3], [Code2_4], [Code3_1])
) AS pvt
In short it is using ROW_NUMBER to figure out how many Numbers exist for a particular Type. It then uses a select statement to build a new name field called NewType.
From there it uses a PIVOT statement to break out the data into separate columns.
A few things to note.
As mentioned it is somewhat dynamic but you will have to use Dynamic SQL for the PIVOT part if you are going to run this in a larger query or have varying amount of values in the Type and Number fields. However if you know you will never have more than 4 of any code you can build the statement like this
SELECT *
FROM (
SELECT
[Name], [Address], [City], [State], [Number]
, [Type] + '_' + LTRIM(STR(rn)) AS NewType
FROM (
SELECT *
,ROW_NUMBER() OVER(PARTITION BY [Name], [Type] ORDER BY [Number]) rn
FROM #Addresses
) a
) p
PIVOT (
MAX([Number])
FOR [NewType] IN ([Code1_1], [Code1_2], [Code1_3], [Code1_4], [Code2_1], [Code2_2], [Code2_3], [Code2_4], [Code3_1], [Code3_2], [Code3_3], [Code3_4])
) AS pvt
Doing it this way will produce null fields for any code that doesn't have a value.
This will likely be slow with larger datasets.
Hope this gets you point in the right direction.
You can use conditional aggregation:
select Name, Addresss, City, State,
min(case when type = 'Code1' then number) as code1,
min(case when type = 'Code2' and seqnum = 1 then number) as code2,
min(case when type = 'Code2' and seqnum = 2 then number) as code2_II,
min(case when type = 'Code2' and seqnum = 3 then number) as code2_III,
min(case when type = 'Code2' and seqnum = 4 then number) as code2_IV,
min(case when type = 'Code3' and seqnum = 1 then number) as code3
from (select t.*,
row_number() over (partition by Name, Addresss, City, State, type order by type) as seqnum
from t
) t
group by Name, Addresss, City, State;
Note: This returns exactly six code columns. That seems to be what you expect. If you want a dynamic number of columns, you need to use dynamic SQL.
SELECT DISTINCT Name,
Address,
City,
STATE,
STUFF((
SELECT ',' + [tCode1].Number
FROM #TestTable [tCode1]
WHERE [tCode1].Type = 'Code1'
FOR XML PATH('')
), 1, 1, N'') [Code1],
STUFF((
SELECT ',' + [tCode2].Number
FROM #TestTable [tCode2]
WHERE [tCode2].Type = 'Code2'
FOR XML PATH('')
), 1, 1, N'') [Code2],
STUFF((
SELECT ',' + [tCode3].Number
FROM #TestTable [tCode3]
WHERE [tCode3].Type = 'Code3'
FOR XML PATH('')
), 1, 1, N'') [Code3]
FROM #TestTable;
I referenced this https://www.mssqltips.com/sqlservertip/2914/rolling-up-multiple-rows-into-a-single-row-and-column-for-sql-server-data/
To Rolling multiple rows into one
Result:
+------+---------+----------+-------+-------+-----------------------------+---------+
| Name | Address | City | State | Code1 | Code2 | Code3 |
+------+---------+----------+-------+-------+-----------------------------+---------+
| XYZ | 123 Rd | New York | NY | 123D | 56A45,345TT,34561NN,84567YY | D45256 |
+------+---------+----------+-------+-------+-----------------------------+---------+

Select rows into columns and show a flag in the column

Trying to get an output like the below:
| UserFullName | JAVA | DOTNET | C | HTML5 |
|--------------|--------|--------|--------|--------|
| Anne San | | | | |
| John Khruf | 1 | 1 | | 1 |
| Mary Jane | 1 | | | 1 |
| George Mich | | | | |
This shows the roles of a person. A person could have 0 or N roles. When a person has a role, I am showing a flag, like '1'.
Actually I have 2 blocks of code:
Block #1: The tables and a simple output which generates more than 1 rows per person.
SQL Fiddle
MS SQL Server 2008 Schema Setup:
CREATE TABLE AvailableRoles
(
id int identity primary key,
CodeID varchar(5),
Description varchar(500),
);
INSERT INTO AvailableRoles
(CodeID, Description)
VALUES
('1', 'JAVA'),
('2', 'DOTNET'),
('3', 'C'),
('4', 'HTML5');
CREATE TABLE PersonalRoles
(
id int identity primary key,
UserID varchar(100),
RoleID varchar(5),
);
INSERT INTO PersonalRoles
(UserID, RoleID)
VALUES
('John.Khruf', '1'),
('John.Khruf', '2'),
('Mary.Jane', '1'),
('Mary.Jane', '4'),
('John.Khruf', '4');
CREATE TABLE Users
(
UserID varchar(20),
EmployeeType varchar(1),
EmployeeStatus varchar(1),
UserFullName varchar(500),
);
INSERT INTO Users
(UserID, EmployeeType, EmployeeStatus, UserFullName)
VALUES
('John.Khruf', 'E', 'A', 'John Khruf'),
('Mary.Jane', 'E', 'A', 'Mary Jane'),
('Anne.San', 'E', 'A', 'Anne San'),
('George.Mich', 'T', 'A', 'George Mich');
Query 1:
SELECT
A.UserFullName,
B.RoleID
FROM
Users A
LEFT JOIN PersonalRoles B ON B.UserID = A.UserID
WHERE
A.EmployeeStatus = 'A'
ORDER BY
A.EmployeeType ASC,
A.UserFullName ASC
Results:
| UserFullName | RoleID |
|--------------|--------|
| Anne San | (null) |
| John Khruf | 1 |
| John Khruf | 2 |
| John Khruf | 4 |
| Mary Jane | 1 |
| Mary Jane | 4 |
| George Mich | (null) |
Block #2: An attempt to convert the rows into columns to be used in the final result
SQL Fiddle
MS SQL Server 2008 Schema Setup:
CREATE TABLE AvailableRoles
(
id int identity primary key,
CodeID varchar(5),
Description varchar(500),
);
INSERT INTO AvailableRoles
(CodeID, Description)
VALUES
('1', 'JAVA'),
('2', 'DOTNET'),
('3', 'C'),
('4', 'HTML5');
Query 1:
SELECT
*
FROM
(
SELECT CodeID, Description
FROM AvailableRoles
) d
PIVOT
(
MAX(CodeID)
FOR Description IN (Java, DOTNET, C, HTML5)
) piv
Results:
| Java | DOTNET | C | HTML5 |
|--------|--------|-------|--------|
| 1 | 2 | 3 | 4 |
Any help in mixing both blocks to show the top output will be welcome. Thanks.
Another option without PIVOT operator is:
select u.UserFullName,
max(case when a.CodeID='1' then '1' else '' end) JAVA,
max(case when a.CodeID='2' then '1' else '' end) DOTNET,
max(case when a.CodeID='3' then '1' else '' end) C,
max(case when a.CodeID='4' then '1' else '' end) HTML5
from
Users u
LEFT JOIN PersonalRoles p on (u.UserID = p.UserID)
LEFT JOIN AvailableRoles a on (p.RoleID = a.CodeID)
group by u.UserFullName
order by u.UserFullName
SQLFiddle: http://sqlfiddle.com/#!3/630c3/19
You can try this.
SELECT *
FROM
(
select u.userfullname,
case when p.roleid is not null then 1 end as roleid,
a.description
from users u
left join personalroles p
on p.userid = u.userid
left join availableroles a
on a.codeid = p.roleid
) d
PIVOT
(
MAX(roleID)
FOR Description IN (Java, DOTNET, C, HTML5)
) piv
Fiddle