Converting multiple rows into one row by ID - sql

What I am trying to achieve is group them by id and create a column for the date as well as data.
The background of the dataset are it is lab result taken by participant and some test are not able to be taken on same day due to fasting restrictions n etc. The database I am using is SQL Server.
Below are my DataSet as well as the desired output.
Sample dataset:
create table Sample
(
Id int,
LAB_DATE date,
A_CRE_1 varchar(100),
B_GLUH_1 varchar(100),
C_LDL_1 varchar(100),
D_TG_1 varchar(100),
E_CHOL_1 varchar(100),
F_HDL_1 varchar(100),
G_CRPH_1 varchar(100),
H_HBA1C_1 varchar(100),
I_GLU120_1 varchar(100),
J_GLUF_1 varchar(100),
K_HCR_1 varchar(100)
)
insert into Sample(Id, LAB_DATE,A_CRE_1, B_GLUH_1,C_LDL_1,E_CHOL_1,F_HDL_1,H_HBA1C_1,K_HCR_1)
values (01, '2017-11-21', '74', '6.4', '2.04', '4.17', '1.64', '6.1', '2.54')
insert into sample (Id, LAB_DATE, I_GLU120_1)
values (01, '2017-11-22','8.8')
insert into sample (Id, LAB_DATE, D_TG_1)
values (01, '2017-11-23','0.56')
insert into sample (Id,LAB_DATE,A_CRE_1,B_GLUH_1,C_LDL_1,D_TG_1,E_CHOL_1,F_HDL_1,K_HCR_1)
values (2,'2018-10-02','57','8.91','2.43','1.28','3.99','1.25','3.19')
insert into sample (Id,LAB_DATE,H_HBA1C_1)
values (2,'2018-10-03','8.6')
insert into sample (Id,LAB_DATE,J_GLUF_1)
values (2,'2018-10-04','7.8')
insert into sample (Id,LAB_DATE,A_CRE_1,B_GLUH_1,C_LDL_1,D_TG_1,E_CHOL_1,F_HDL_1,G_CRPH_1,H_HBA1C_1,K_HCR_1)
values (3,'2016-10-01','100','6.13','3.28','0.94','5.07','1.19','0.27','5.8','4.26')
Desired output:
ID|LAB_DATE|A_CRE_1|B_GLUH_1|C_LDL_1|Date_TG_1|D_TG_1|E_CHOL_1|F_HDL_1|G_CRPH_1|H_HBA1C_1|Date_GLU120_1|I_GLU120_1|J_GLUF_1|K_HCR_1
1|2017-11-21|74|6.4|2.04|2017-11-23|0.56|4.17|1.64|||6.1|2017-11-22|8.8|||2.54
2|02/10/2018|57|8.91|2.43||1.28|3.99|1.25||03/10/2018|8.6|||04/10/2018|7.8|3.19
3|01/10/2016|100|6.13|3.28||0.94|5.07|1.19|0.27||5.8|||||4.26

Here's a solution (that cannot cope with multiple rows of the same id/sample type - you haven't said what to do with those)
select * from
(select Id, LAB_DATE,A_CRE_1, B_GLUH_1,C_LDL_1,E_CHOL_1,F_HDL_1,H_HBA1C_1,K_HCR_1 from sample) s1
INNER JOIN
(select Id, LAB_DATE as glu120date, I_GLU120_1 from sample) s2
ON s1.id = s2.id
(select Id, LAB_DATE as dtgdate, D_TG_1 from sample) s3
ON s1.id = s3.id
Hopefully you get the idea with this pattern; if you have other sample types with their own dates, break them out of s1 and into their own subquery in a similar way (eg make an s4 for e_chol_1, s5 for k_hcr_1 etc). Note that if any sample type is missing it will cause the whole row to disappear from the results. If this is not desired and you accept NULL for missing samples, use LEFT JOIN instead of INNER
If there will be multiple samples for patient 01 and you only want the latest, the pattern becomes:
select * from
(select Id, LAB_DATE,A_CRE_1, B_GLUH_1,C_LDL_1,E_CHOL_1,F_HDL_1,H_HBA1C_1,K_HCR_1,
row_number() over(partition by id order by lab_date desc) rn
from sample) s1
INNER JOIN
(select Id, LAB_DATE as glu120date, I_GLU120_1,
row_number() over(partition by id order by lab_date desc) rn
from sample) s2
ON s1.id = s2.id and s1.rn = s2.rn
WHERE
s1.rn = 1
Note the addition of row_number() over(partition by id order by lab_date desc) rn - this establishes an incrementing counter in descending date order(latest record = 1, older = 2 ...) that restarts from 1 for every different id. We join on it too then say where rn = 1 to pick only the latest records for each sample type

As #Ben suggested, you can use group by id and take min for all column like below one.
DECLARE #Sample as table (
Id int,
LAB_DATE date,
A_CRE_1 varchar(100),
B_GLUH_1 varchar(100),
C_LDL_1 varchar(100),
D_TG_1 varchar(100),
E_CHOL_1 varchar(100),
F_HDL_1 varchar(100),
G_CRPH_1 varchar(100),
H_HBA1C_1 varchar(100),
I_GLU120_1 varchar(100),
J_GLUF_1 varchar(100),
K_HCR_1 varchar(100))
insert into #Sample(Id, LAB_DATE,A_CRE_1,
B_GLUH_1,C_LDL_1,E_CHOL_1,F_HDL_1,H_HBA1C_1,K_HCR_1)
values (01,'2017-11-21','74','6.4','2.04','4.17','1.64','6.1','2.54')
insert into #Sample (Id, LAB_DATE, I_GLU120_1)
values (01, '2017-11-22','8.8')
insert into #Sample (Id, LAB_DATE, D_TG_1)
values (01, '2017-11-23','0.56')
SELECT s.Id
, MIN(s.LAB_DATE) AS LAB_DATE
, MIN(s.A_CRE_1) AS A_CRE_1
, MIN(s.B_GLUH_1) AS B_GLUH_1
, MIN(s.C_LDL_1) AS C_LDL_1
, MIN(s.D_TG_1) AS D_TG_1
, MIN(s.E_CHOL_1) AS E_CHOL_1
, MIN(s.F_HDL_1) AS F_HDL_1
, MIN(s.G_CRPH_1) AS G_CRPH_1
, MIN(s.H_HBA1C_1) AS H_HBA1C_1
, MIN(s.I_GLU120_1) AS I_GLU120_1
, MIN(s.J_GLUF_1) AS J_GLUF_1
, MIN(s.K_HCR_1) AS K_HCR_1
FROM #Sample AS s
GROUP BY s.Id
You can also check the SQL Server STUFF function. Can take help from the below link
https://www.mssqltips.com/sqlservertip/2914/rolling-up-multiple-rows-into-a-single-row-and-column-for-sql-server-data/

Following on from my comments about presenting the original data, here's what I think you should do (taking the query you commented)
SELECT
ID,
MAX(CASE WHEN TestID='1' THEN Results END) [Test_1],
MAX(CASE WHEN TestID='2' THEN Results END) [Test_2],
MAX(CASE WHEN TestID='1' THEN Result_Date_Time END) Test12Date,
MAX(CASE WHEN TestID='3' THEN Results END) [Test_3],
MAX(CASE WHEN TestID='3' THEN Result_Date_Time END) Test3Date
FROM [tbBloodSample]
GROUP BY ID
ORDER BY ID
Notes: If TestID is an int, don't use strings like '1' in your query, use ints. You don't need an ELSE NULL in a case- null is the default if the when didn't work out
Here is a query pattern. Test1 and 2 are always done on the same day, hence why I only pivot their date once. Test 3 might be done later, might be same, this means the dates in test12date and test3date might be same, might be different
Convert the strings to dates after you do the pivot, to reduce the number of conversions

Related

How to show only the latest record in SQL

I have this issue where I want to show only the latest record (Col 1). I deleted the date column thinking that it might not work if it has different values. but if that's the case, then the record itself has a different name (Col 1) because it has a different date in the name of it.
Is it possible to fetch one record in this case?
The code:
SELECT distinct p.ID,
max(at.Date) as date,
at.[RAPID3 Name] as COL1,
at.[DLQI Name] AS COL2,
at.[HAQ-DI Name] AS COL3,
phy.name as phyi,
at.State_ID
FROM dbo.[Assessment Tool] as at
Inner join dbo.patient as p on p.[ID] = at.[Owner (Patient)_Patient_ID]
Inner join dbo.[Physician] as phy on phy.ID = p.Physician_ID
where (at.State_ID in (162, 165,168) and p.ID = 5580)
group by
at.[RAPID3 Name],
at.[DLQI Name],
at.[HAQ-DI Name],
p.ID, phy.name,
at.State_ID
SS:
In this SS I want to show only the latest record (COL 1) of this ID "5580". Means the first row for this ID.
Thank you
The Most Accurate way to handle this.
Extract The Date.
Than use Top and Order.
create table #Temp(
ID int,
Col1 Varchar(50) null,
Col2 Varchar(50) null,
Col3 Varchar(50) null,
Phyi Varchar(50) null,
State_ID int)
Insert Into #Temp values(5580,'[9/29/2021]-[9.0]High Severity',null,null,'Eman Elshorpagy',168)
Insert Into #Temp values(5580,'[10/3/2021]-[9.3]High Severity',null,null,'Eman Elshorpagy',168)
select top 1 * from #Temp as t
order by cast((Select REPLACE((SELECT REPLACE((SELECT top 1 Value FROM STRING_SPLIT(t.Col1,'-')),'[','')),']','')) as date) desc
This is close to ANSI standard, and it also caters for the newest row per id.
The principle is to use ROW_NUMBER() using a descending order on the date/timestamp (using a DATE type instead of a DATETIME and avoiding the keyword DATE for a column name) in one query, then to select from that query using the result of row number for the filter.
-- your input, but 2 id-s to show how it works with many ..
indata(id,dt,col1,phyi,state_id) AS (
SELECT 5580,DATE '2021-10-03','[10/3/2021] - [9,3] High Severity','Eman Elshorpagy',168
UNION ALL SELECT 5580,DATE '2021-09-29','[9/29/2021] - [9,0] High Severity','Eman Elshorpagy',168
UNION ALL SELECT 5581,DATE '2021-10-03','[10/3/2021] - [9,3] High Severity','Eman Elshorpagy',168
UNION ALL SELECT 5581,DATE '2021-09-29','[9/29/2021] - [9,0] High Severity','Eman Elshorpagy',168
)
-- real query starts here, replace following comman with "WITH" ...
,
with_rank AS (
SELECT
*
, ROW_NUMBER() OVER(PARTITION BY id ORDER BY dt DESC) AS rank_id
FROM indata
)
SELECT
id
, dt
, col1
, phyi
, state_id
FROM with_rank
WHERE rank_id=1
;
id | dt | col1 | phyi | state_id
------+------------+-----------------------------------+-----------------+----------
5580 | 2021-10-03 | [10/3/2021] - [9,3] High Severity | Eman Elshorpagy | 168
5581 | 2021-10-03 | [10/3/2021] - [9,3] High Severity | Eman Elshorpagy | 168

Show the incremental count for repeats in SQL SERVER

Say you have a table where one column has repeat values. How can I add another column that shows how many times that value has shown up SO FAR (top-down).
Ex. You have a column say "ccode" and in ccode you have the value "R52" repeat twice. Rather than Join the final count (2), I want the first appearance of R52 to have a count=1, and the second to have a count=2, and so on...
CREATE TABLE Temp
(
ccode varchar(50),
name varchar(50),
Val1 varchar(50),
g_Name varchar(50),
ce_hybrid varchar(50)
)
INSERT INTO Temp VALUES
( 'R52' , 'adam#email.ca' , 1, 'WALT', '3P'),
( 'R52' , 'adam#email.ca' , 2 , 'KEN', '3P'),
( 'R00' , 'alison#email.ca' , 1 , 'QUIN', '3P')
SELECT ccode, name, [1_G_Name], [2_G_Name], [1_Hybrids], [2_Hybrids] FROM
(
SELECT ccode, name, col, val FROM(
SELECT *, Val1+'_G_Name' as Col, g_Name as Val FROM Temp
UNION
SELECT *, Val1+'_Hybrids' as Col, ce_hybrid as Val FROM Temp
) t
) tt
PIVOT ( max(val) for Col in ([1_G_Name], [2_G_Name], [1_Hybrids], [2_Hybrids]) ) AS pvt
For a better idea: http://sqlfiddle.com/#!18/6160d/2
I want to have a table like above, but add Val1 column afterwards (dynamically) based on the repeats SO FAR in the table (top-down).
This output (image below) is CORRECT. But say my table didn't have Val1 column:
INSERT INTO Temp VALUES
( 'R52', 'adam#email.ca', 'WALT', '3P'),
( 'R52', 'adam#email.ca', 'KEN', '3P'),
( 'R00', 'alison#email.ca', 'QUIN', '3P')
How would I add Val1 column with the (1 , 2 , 1) to based on repeat count as I mentioned
Required Output:
I got an answer thanks to an amazing senior developer at work. I would feel bad if I didn't share so:
SELECT *, rank() over (partition by ccode order by g_name) Val1 FROM Temp
Use Rank() and Partition over the table. I partitioned by ccode so any matching/duplicating will start from 1 and add 1 each time the same ccode appears in the table.
Example 1: http://sqlfiddle.com/#!18/95a0d5/6
Example 2: http://sqlfiddle.com/#!18/569d8/1
Example 3: http://sqlfiddle.com/#!18/41bf32/1
In example 3, notice how since we used order by g_name and there are 2 identical names KEN and KEN for ccode=R52, the Val1 is 2 and 2 for them and 4 next time (3 gets skipped)
I ignored the rest of the code regarding pivot since my question was more regarding this rank/partition. I'm not super familiar with it other than what was explained over a call, but hope it helps someone.
P.S. what would be a better name for this question?

Query to determine cumulative changes to records

Given the following table containing the example rows, I’m looking for a query to give me the aggregate results of changes made to the same record. All changes are made against a base record in another table (results table), so the contents of the results table are not cumulative.
Base Records (from which all changes are made)
Edited Columns highlighted
I’m looking for a query that would give me the cumulative changes (in order by date). This would be the resulting rows:
Any help appreciated!
UPDATE---------------
Let me offer some clarification. The records being edited exist in one table, let's call that [dbo].[Base]. When a person updates a record from [dbo].[Base], his updates go into [dbo].[Updates]. Therefore, a person is always editing from the base table.
At some point, let's say once a day, we need to calculate the sum of changes with the following rule:
For any given record, determine the latest change for each column and take the latest change. If no change was made to a column, take the value from [dbo].[Base]. So, one way of looking at the [dbo].[Updates] table would be to see only the changed columns.
Please let's not discuss the merits of this approach, I realize it's strange. I just need to figure out how to determine the final state of each record.
Thanks!
This is dirty, but you can give this a shot (test here: https://rextester.com/MKSBU15593)
I use a CTE to do an initial CROSS JOIN of the Base and Update tables and then a second to filter it to only the rows where the IDs match. From there I use FIRST_VALUE() for each column, partitioned by the ID value and ordered by a CASE expression (if the Base column value matches the Update column value then 1 else 0) and the Datemodified column to get the most recent version of the each column.
It spits out
CREATE TABLE Base
(
ID INT
,FNAME VARCHAR(100)
,LNAME VARCHAR(100)
,ADDRESS VARCHAR(100)
,RATING INT
,[TYPE] VARCHAR(5)
,SUBTYPE VARCHAR(5)
);
INSERT INTO dbo.Base
VALUES
( 100,'John','Doe','123 First',3,'Emp','W2'),
( 200,'Jane','Smith','Wacker Dr.',2,'Emp','W2');
CREATE TABLE Updates
(
ID INT
,DATEMODIFIED DATE
,FNAME VARCHAR(100)
,LNAME VARCHAR(100)
,ADDRESS VARCHAR(100)
,RATING INT
,[TYPE] VARCHAR(5)
,SUBTYPE VARCHAR(5)
);
INSERT INTO dbo.Updates
VALUES
( 100,'1/15/2019','John','Doe','123 First St.',3,'Emp','W2'),
( 200,'1/15/2019','Jane','Smyth','Wacker Dr.',2,'Emp','W2'),
( 100,'1/17/2019','Johnny','Doe','123 First',3,'Emp','W2'),
( 200,'1/19/2019','Jane','Smith','2 Wacker Dr.',2,'Emp','W2'),
( 100,'1/20/2019','Jon','Doe','123 First',3,'Cont','W2');
WITH merged AS
(
SELECT b.ID AS IDOrigin
,'1/1/1900' AS DATEMODIFIEDOrigin
,b.FNAME AS FNAMEOrigin
,b.LNAME AS LNAMEOrigin
,b.ADDRESS AS ADDRESSOrigin
,b.RATING AS RATINGOrigin
,b.[TYPE] AS TYPEOrigin
,b.SUBTYPE AS SUBTYPEOrigin
,u.*
FROM base b
CROSS JOIN
dbo.Updates u
), filtered AS
(
SELECT *
FROM merged
WHERE IDOrigin = ID
)
SELECT distinct
ID
,FNAME = FIRST_VALUE(FNAME) OVER (PARTITION BY ID ORDER BY CASE WHEN FNAME = FNAMEOrigin THEN 1 ELSE 0 end, datemodified desc)
,LNAME = FIRST_VALUE(LNAME) OVER (PARTITION BY ID ORDER BY CASE WHEN LNAME = LNAMEOrigin THEN 1 ELSE 0 end, datemodified desc)
,ADDRESS = FIRST_VALUE(ADDRESS) OVER (PARTITION BY ID ORDER BY CASE WHEN ADDRESS = ADDRESSOrigin THEN 1 ELSE 0 end, datemodified desc)
,RATING = FIRST_VALUE(RATING) OVER (PARTITION BY ID ORDER BY CASE WHEN RATING = RATINGOrigin THEN 1 ELSE 0 end, datemodified desc)
,[TYPE] = FIRST_VALUE([TYPE]) OVER (PARTITION BY ID ORDER BY CASE WHEN [TYPE] = TYPEOrigin THEN 1 ELSE 0 end, datemodified desc)
,SUBTYPE = FIRST_VALUE(SUBTYPE) OVER (PARTITION BY ID ORDER BY CASE WHEN SUBTYPE = SUBTYPEOrigin THEN 1 ELSE 0 end, datemodified desc)
FROM filtered
Don't you just want the last record?
select e.*
from edited e
where e.datemodified = (select max(e2.datemodified)
from edited e2
where e2.id = e.id
);

SQL Server advanced Pivot grouped rows to columns

I need to Pivot/rotate data in rows into columns - but a little different from most examples I've seen.
We have customers that will buy things in sets (think a pizza ingredient seller... people will always buy cheese, dough, and sauce; optionally some will buy toppings, but we don't care about that).
What I need to do is sort this row data, by order date into columns. Below are two scripts to fill temp input and temp output table to show what I'm trying to achieve.
SQL Server 2008
CREATE table #myInput
(CustomerID Varchar(10), OrderDate varchar(10), Item varchar(13), ItemColor varchar(20));
CREATE table #myOUTPUT
(
CustomerID Varchar(10),
OrderDate_1 varchar(10),
PartA_1 varchar(20),
PartB_1 varchar(20),
PartC_1 varchar(20),
OrderDate_2 varchar(10),
PartA_2 varchar(20),
PartB_2 varchar(20),
PartC_2 varchar(20),
OrderDate_3 varchar(10),
PartA_3 varchar(20),
PartB_3 varchar(20),
PartC_3 varchar(20)
)
INSERT INTO #myInput
(CustomerID, OrderDate, Item, ItemColor)
VALUES
('abc','5/1/2001','PartA','Silver'),
('abc','5/1/2001','PartB','Red'),
('abc','5/1/2001','PartC','Green'),
('abc','5/20/2002','PartA','Purple'),
('abc','5/20/2002','PartB','Yellow'),
('abc','5/20/2002','PartC','Black'),
('abc','10/1/2002','PartA','Red'),
('abc','10/1/2002','PartB','Silver'),
('abc','10/1/2002','PartC','Blue'),
('def','4/1/2000','PartA','Green'),
('def','4/1/2000','PartB','Red'),
('def','4/1/2000','PartC','White'),
('jkl','5/1/2001','PartA','Black'),
('jkl','5/1/2001','PartB','Yellow'),
('jkl','5/1/2001','PartC','Silver'),
('jkl','10/10/2001','PartA','Green'),
('jkl','10/10/2001','PartB','Black'),
('jkl','10/10/2001','PartC','Silver')
;
And the result:
insert into #myOUTPUT
(CustomerID,OrderDate_1,PartA_1,PartB_1,PartC_1,OrderDate_2,PartA_2,PartB_2,PartC_2,OrderDate_3,PartA_3,PartB_3,PartC_3)
VALUES
('abc','5/1/2001','Silver','Red','Green','5/20/2002','Purple','Yellow','Black','10/1/2002','Red','Silver','Blue'),
('def','4/1/2000','Green','Red','White','','','','','','','',''),
('jkl','5/1/2001','Black','Yellow','Silver','10/10/2001','Green','Black','Silver','','','','');
select * from #myInput
select * from #myOUTPUT
We're looking for 17 or less orders. At least at the current moment, we don't have more than 1 dozen orders for any one customer.
I've tried a couple of different things- pivot doesn't seem to produce the output i'm looking for. I was thinking perhaps dense_rank to determine how many columns we'll need at first, and then insert into a cursor handle via cte? But i'm unable to get exactly the output needed. Note that the source "date" field is stored in DB as varchar. Also, there's no order number - so uninqueness is only from customer id, and date.
I would approach this using conditional aggregation. If I understand correctly:
select customer,
max(case when seqnum_co = 1 then orderdate end) as orderdate_1,
max(case when seqnum_co = 1 and item = 'Part_A' then itemcolor end) as parta_1,
max(case when seqnum_co = 1 and item = 'Part_B' then itemcolor end) as partb_1,
max(case when seqnum_co = 1 and item = 'Part_C' then itemcolor end) as partc_1,
max(case when seqnum_co = 2 then orderdate end) as orderdate_2,
max(case when seqnum_co = 2 and item = 'Part_A' then itemcolor end) as parta_2,
max(case when seqnum_co = 2 and item = 'Part_B' then itemcolor end) as partb_2,
max(case when seqnum_co = 2 and item = 'Part_C' then itemcolor end) as partc_2,
. . .
from (select i.*,
dense_rank() over (partition by i.customerid order by orderdate) as seqnum_co
from #myinput
) i
group by customer;

how to show records both the tables side by side

I have App table and Apphistory table i need to show app records and app history record side by side columns
declare #app table (Appno varchar(10),Name varchar(10),Height INT,weight INT,Createddate datetime)
insert into #app (Appno,Name,Height,weight,Createddate)values
('app1035','tom',10,60,'2015-07-02 20:14:45.590'),
('app1036','john',8,40,'2015-07-02 20:14:45.590'),
('app1037','jim',9,36,'2015-07-02 20:14:45.590')
declare #apphistory table
(
Appno varchar(10),
Name varchar(10),
Height INT,
weight INT,
Createddate datetime)
insert into #apphistory (Appno,Name,Height,weight,Createddate)
values('app1035','tom',10,60,'2015-07-02 20:14:45.590')
,('app1035','tom',8,45,'2015-06-02 20:14:45.590'),
('app1035','tom',6,NULL,'2015-05-02 20:14:45.590'),
('app1036','john',8,40,'2015-07-02 20:14:45.590')
,('app1036','john',8,40,'2015-06-02 20:14:45.590'),
('app1036','john',NULL,NULL,'2015-05-02 20:14:45.590')
select A.Appno, COALESCE(H.Appno,A.Appno)HAppno,
A.Name,COALESCE(H.Name,A.Name)Hname,
A.Height,COALESCE(H.Height,A.Height)Hheight,
A.weight,COALESCE(H.weight,A.weight)Hweight,
A.Createddate,COALESCE(H.Createddate,A.Createddate)Hcreateddate
FROM #app A LEFT JOIN (select top 1 Appno,Name,Height,weight,Createddate from #apphistory ORDER BY Createddate )H
ON A.Appno = H.Appno
WHERE A.Appno = 'app1036'
but what my problem is when there is Appno in App table and not there in App history table i will show record from App table.
when there is record in both tables i need to show old record of same app no with the values from Apphistory table
out put should be like :
Appno HAppno Name Hname Height Hheight weight Hweight Createddate Hcreateddate
app1036 app1036 john john 8 NULL 40 NULL 2015-07-02 20:14:45.590 2015-07-02 20:14:45.590
Use ROW_NUMBER() function to extract oldest record by created date. Fiddle sample
;WITH CTE AS
(
SELECT *, ROW_NUMBER() OVER (PARTITION BY Appno ORDER BY CreatedDate) rn
FROM #apphistory
)
SELECT a.*, h.*
FROM #app a
LEFT JOIN CTE h ON a.Appno = h.Appno AND h.rn = 1
You can use Coalesce() function to fill null values and order the select column list for your preference.
Here is a simplified code-sample of what I am suggesting:
SELECT a.AppNo, COALESCE(ah.Height, a.Height) AS Height
FROM App a
LEFT OUTER JOIN (
SELECT AppNo, COALESCE(Height, 'NULL') AS Height
FROM AppHistory
) ah
ON a.AppNo=ah.AppNo
I realize this leaves out a lot of the logic in your original question, I am only trying to illustrate the layered Coalesce technique I mentioned in my comment.