Flattening out a normalized SQL Server 2008 R2 database - sql

I am working with SQL Server 2008 R2.
I have 3 tables the data is normalized and I am looking to grab the 'Home' and 'Cell' phone for Bob Dole. However I need to only get the highest sequence phone number of each type. (below is an example of Bob Dole having 2 cell phones and the sequence number for each is 2 and 3 respectively)
Table PersonPhoneNumber
PersonPhoneNumberId Person PhoneNumberId PhoneNumberTypeId Sequence
Guid - vvv Bob Dole Guid - A 1 1
Guid - www Bob Dole Guid - B 2 2
Guid - xxx Bob Dole Guid - C 2 3
Table PhoneNumber
PhoneNumberId Number
Guid - A 111-111-1111
Guid - B 222-222-2222
Guid - C 333-333-3333
Table PhoneNumberType
PhoneNumberTypeId PhoneNumberType
1 Home
2 Cell
My desired output would be this (notice that I only returned the first Cell number.):
Person Home Cell
Bob Dole 111-111-1111 222-222-2222
I have been having issues flattening out the data
Any help with the query would be great!

You can use a row_number() and an aggregate function with CASE expression to convert the data from rows to columns:
select person,
max(case when rn = 1 and PhoneNumberType = 'Home' then number end) home,
max(case when rn = 1 and PhoneNumberType = 'Cell' then number end) cell
from
(
select ppn.person, pn.number,
pt.PhoneNumberType,
row_number() over(partition by ppn.person, ppn.PhoneNumberTypeId
order by ppn.sequence) rn
from PersonPhoneNumber ppn
inner join PhoneNumber pn
on ppn.PhoneNumberId = pn.PhoneNumberId
inner join PhoneNumberType pt
on ppn.PhoneNumberTypeId = pt.PhoneNumberTypeId
) d
group by person;
See SQL Fiddle with Demo
This could also be done using the PIVOT function:
select person,
home,
cell
from
(
select ppn.person, pn.number,
pt.PhoneNumberType,
row_number() over(partition by ppn.person, ppn.PhoneNumberTypeId
order by ppn.sequence) rn
from PersonPhoneNumber ppn
inner join PhoneNumber pn
on ppn.PhoneNumberId = pn.PhoneNumberId
inner join PhoneNumberType pt
on ppn.PhoneNumberTypeId = pt.PhoneNumberTypeId
) d
pivot
(
max(number)
for PhoneNumberType in (Home, Cell)
) piv
where rn = 1;
See SQL Fiddle with Demo

Here's an example with a sub-query to get the first sequence number for each Type. The outer query then uses a CASE statement to create the Home and Cell Columns.
SELECT P.Person
,MAX(CASE WHEN P.PhoneNumberTypeId = 1 THEN N.Number ELSE NULL END) AS Home
,MAX(CASE WHEN P.PhoneNumberTypeId = 2 THEN N.Number ELSE NULL END) AS Cell
FROM PersonPhoneNumber P
INNER JOIN
PhoneNumber N
ON P.PhoneNumberId = N.PhoneNumberId
INNER JOIN
(
SELECT Person
,PhoneNumberTypeId
,MIN(Sequence) AS FIRST_NUM
FROM PersonPhoneNumber
GROUP BY
Person
,PhoneNumberTypeId
) SQ1
ON P.Person = SQ1.Person
AND P.PhoneNumberTypeId = SQ1.PhoneNumberTypeId
AND P.Sequence = SQ1.FIRST_NUM
GROUP BY
P.PERSON

Related

How can I query data from one table by using multiple conditions and by looking for a value in another table

I have three ORACLE SQL tables where I have to query DATA_TABLE and pull dataid's whose attr is 2000 and value is cat and also the value for attr = 2001 has to be available in NAME_TABLE oldname column and I have to display Name from DOC_TABLE(using dataid's we got from above) and Code from Name_Table
From the above table my output has to be
Name,Code
FirstName, DG
Because dataid 1 in DATA_TABLE for attr 2000 its value is Cat and for attr 2001 its value is dog which is available in NAME_TABLE oldname column, As Name where dataid=1 in DOC_TABLE is FirstName and code for dog in NAME_TABLE is DG
I would phrase your query as follows:
select do.name, na.code
-- start the join here
from doc_table do
-- pull dataid's from DATA_TABLE whose attr is 2000 and value is cat
inner join data_table da on da.dataid = do.dataid and da.attr = 2000 and da.value = 'cat'
-- pull code from NAME_TABLE
inner join name_table na on na.oldname = da.value
-- the value for attr = 2001 has to be available in NAME_TABLE
where exists(
select 1
from data_table da1
inner join name_table na1 on na1.oldname = da1.value
where da1.dataid = do.dataid and da1.attr = 2001
)
You can use analytical function as following:
Select distinct name, code from
(select do.name, na.code,
Count(case when da.value='cat' and da.attr=2000 then 1 end)
Over (Partition by do.dataid) as cat_cnt,
Count(case when na.code is not null and da.attr=2001 then 1 end)
Over (Partition by do.dataid) as code_cnt
from doc_table do
Left join data_table da on da.dataid = do.dataid
Left join name_table na on na.oldname = da.value)
Where cat_cnt > 0 and code_count > 0;
Cheers!!

If record exist in A then return record else if record exist in B return record else null

I am trying to express this idea in SQL
If record exist in A then return record else if record exist in B return record ELSE NULL
Example: Select the team captain if they exists else select the oldest player on the team
WITH Captains_CTE (TeamName, PlayerName)
AS (
SELECT TeamName, PlayerName FROM TeamPlayer WHERE IsCaptain = 1
),
OldestPlayer_CTE (TeamName, PlayerName)
AS (
SELECT TeamName, PlayerName FROM TeamPlayer AS tp
INNER JOIN (
SELECT TeamName, MAX(PlayerAge) AS MaxAge
FROM TeamPlayer
GROUP BY TeamName
) AS old ON old.TeamName = tp.TeamName AND old.MaxAge = tp.PlayerAge
)
SELECT CASE
WHEN Captains_CTE.TeamName IS NULL THEN OldestPlayer_CTE.TeamName
ELSE Captains_CTE.TeamName
END AS TeamName,
CASE
WHEN Captains_CTE.PlayerName IS NULL THEN OldestPlayer_CTE.PlayerName
ELSE Captains_CTE.PlayerName
END AS PlayerName
FROM Captains_CTE
FULL OUTER JOIN OldestPlayer_CTE ON Captains_CTE.TeamName = OldestPlayer_CTE.TeamName
I can do this by doing a outer join on A and B and then building a CASE statement for each column I wish to return but there has to be a better way. (yes my example query makes some terrible assumptions about age being unique)
Use not exists:
select a.*
from a
union all
select b.*
from b
where not exists (select 1 from a where a.? = b.?); -- "?" is for the column that specifies whether the record exists in A
You can use APPLY to do this:
SELECT t.TeamName, coalsesce(captain.PlayerName,'') As Captain
FROM Teams t
OUTER APPLY (
SELECT TOP 1 PlayerName
FROM TeamPlayers tp
WHERE tp.TeamID = t.TeamID
ORDER BY CASE WHEN tp.IsCaptain = 1 THEN 1 ELSE 2 END,
PlayerAge DESC
) captain
If there isn't a Teams table (there ought to be if there's any sense to the schema at all, but you never know), you can derive one:
SELECT t.TeamName, coalsesce(captain.PlayerName,'') As Captain
FROM (SELECT DISTINCT TeamName FROM TeamPlayers) t
OUTER APPLY (
SELECT TOP 1 PlayerName
FROM TeamPlayers tp
WHERE tp.TeamName = t.TeamName
ORDER BY CASE WHEN tp.IsCaptain = 1 THEN 1 ELSE 2 END,
PlayerAge DESC
) captain

how can i get the latest record published by each singer

I have 3 tables
table 1 : songs
-songname varchar
-singerlabel varchar
-date date
-category varchar
table 2 : singer
-singerlabel varchar
-singer# varchar
table 3 : singerNote
-singer# varchar
-firstname varchar
-lastname varchar
table 1 is connected to table 2 using singerlabel.
table 2 is connected to table 3 using singer#.
With this query:
select singerlabel, max(date) maxdate
from songs
group by singerlabel
you get the max date of each singerlabel, and then join to the other 3 tables:
select sn.firstname, sn.lastname, songs.songname
from (
select singerlabel, max(date) maxdate
from songs
group by singerlabel
) s inner join singer
on singer.singerlabel = s.singerlabel
inner join singernote sn
on sn.singer = singer.singer
inner join songs
on songs.singerlabel = s.singerlabel and songs.date = s.maxdate
If your RDBMS supports window functions, this can be achieved with ROW_NUMBER() :
SELECT x.*
FROM (
SELECT
si.*, sn.first_name, sn.last_name, so.songname, so.date, so.category
ROW_NUMBER() OVER(PARTITION BY so.singerlabel ORDER BY so.date DESC) rn
FROM singer si
INNER JOIN singerNote sn ON sn.singer# = si.singer#
INNER JOIN songs so ON so.singerlabel = si.singerlabel
) x WHERE x.rn = 1
Without window function, you can use a correlated subquery with a NOT EXISTS condition to ensure that you are joining with the most recent song :
SELECT si.*, sn.first_name, sn.last_name, so.songname, so.date, so.category
FROM
singer si
INNER JOIN singerNote sn
ON sn.singer# = si.singer#
INNER JOIN songs so
ON so.singerlabel = si.singerlabel
AND NOT EXISTS (
SELECT 1
FROM songs so1
WHERE so1.singerlabel = si.singerlabel AND so1.date > so.date
)

Get Incremental index for specific rows

I want to get the incremental index when note exists for the row. I am trying to achieve the same with ROW_Number() but it seems there is a problem with the method being used to generate it.
SELECT * RowNo,
(SELECT CASE
WHEN LEN(Value) > 0 THEN ROW_NUMBER()
OVER (
ORDER BY ID)
ELSE ''
END
FROM Dictionary
WHERE ID = ABC.ID) Note
FROM ABCD AS ABC WITH(NOLOCK)
INNER JOIN XYZ AS XYZ WITH(NOLOCK)
ON ABC.Id = XYZ.ID
WHERE ABC.Id = 10
output expected:
ID Name Note
1 A 1
2 B
3 C 2
4 D
5 E
6 F 3
The subquery isn't needed here, and you want to use the partition by argument to separate values having len(value)>0 from those having no value:
SELECT
ID,
Name,
CASE WHEN LEN(Value)>0 THEN ROW_NUMBER() OVER (
PARTITION BY CASE WHEN LEN(Value)>0 THEN 1 ELSE 0 END
ORDER BY ID) ELSE '' END as Note
FROM ABCD AS ABC WITH(NOLOCK)
INNER JOIN XYZ AS XYZ WITH(NOLOCK)
ON ABC.Id = XYZ.ID
Where ABC.Id = 10
I think maybe you need to change the approach to make the Dictionary query the "main" query. It's hard to say without knowing exactly what your tables look like. Which Table does the "Id" in your expected output come from?
Try like this:
WITH cte AS (
SELECT ID, ROW_NUMBER() OVER (ORDER BY ID) AS Note
FROM Dictionary WHERE ID=10
AND LEN(Value)>0
)
SELECT ABC.ID, [Name], cte.Note
FROM ABCD AS ABC WITH(NOLOCK)
INNER JOIN XYZ AS XYZ WITH(NOLOCK) ON ABC.Id = XYZ.ID
LEFT OUTER JOIN cte ON ABC.Id=cte.ID

Join 2 tables: one data table and one table of statut and get statut with no entrie

I have this query:
SELECT c.Show_Code, req.Statut_ID, COUNT(req.Statut_ID) 'Count'
FROM [Case] c
JOIN Request req ON req.Case_Number = c.Number
GROUP BY c.Show_Code, req.Statut_ID
The result is:
Show_Code Statut_ID Count
564900 2 1
568127 2 1
And I have this statut table (Ref_RequestStatut)
ID Name
1 Test
2 Test2
How can I get this result:
Show_Code Statut_ID Count
564900 1 0
564900 2 1
568127 1 0
568127 2 1
I want all the statut, even those which have no value?
Thank
If you are using SQL Server 2005 or later:
WITH counted AS (
SELECT c.Show_Code, req.Statut_ID, COUNT(req.Statut_ID) 'Count'
FROM [Case] c
JOIN Request req ON req.Case_Number = c.Number
GROUP BY c.Show_Code, req.Statut_ID
),
showcodes AS (
SELECT DISTINCT Show_Code
FROM counted
)
SELECT
s.Show_Code,
r.ID AS Statut_ID,
Count = ISNULL(c.Count, 0)
FROM showcodes s
CROSS JOIN Ref_RequestStatut r
LEFT JOIN counted c ON s.Show_Code = c.Show_Code AND r.ID = c.Statut_ID
ORDER BY
s.Show_Code,
r.ID