Given an instance of SQL Server 2008, imagine there's a table named #Configuration, which has three columns: ID, Code, and SubCode. There should be no duplicate rows for Code and SubCode.
Now imagine another detail level table #ConfigurationDetails which have duplicate rows of Code and Sub code may be available of SubCode as Null.
If SubCode is available then pick Amt and Data direct from the detail table and if SubCode is not available in the details table then pick Amt and Data in NULL record
(NOTE: SubCode=NULL entry is always available for every Configuration row )
Any ideas on where to start?
e.g.
a simple example...
table
declare #Configuration TABLE (
ID INTEGER IDENTITY PRIMARY KEY,
Code VARCHAR(50),
SubCode VARCHAR(50)
);
declare #ConfigurationDetails TABLE
(
Code VARCHAR(50),
SubCode VARCHAR(50),
Amt MONEY,
Data VARCHAR(123)
);
INSERT INTO #Configuration VALUES
('BR1','Sub1'),
('BR1','Sub2'),
('BR1','Sub3'),
('BR1','Sub4'),
('BR2','Sub1'),
('BR2','Sub2')
INSERT INTO #ConfigurationDetails VALUES
('BR1','Sub1',500,'BR1 Sub1 Data'),
('BR1','Sub2',600,'BR1 Sub2 Data'),
('BR1',NULL,700,'BR1 Data'),
('BR2','Sub1',500,'BR2 Sub1 Data'),
('BR2',NULL,700,'BR2 Data')
INPUT:
#SubCode = 'Sub1', #Code = 'BR1'
OUTPUT:
Code SubCode Amt Data
==== ======= === ====
BR1 Sub1 500 BR1 Sub1 Data
INPUT:
#SubCode = 'Sub4', #Code = 'BR1'
OUTPUT:
Code SubCode Amt Data
==== ======= === ====
BR1 NULL 700 BR1 Data
You should be able to use something like
SELECT *
FROM #Configuration c
CROSS APPLY (SELECT TOP 1 *
FROM #ConfigurationDetails cd
WHERE c.Code = cd.Code
AND ( c.SubCode = cd.SubCode
OR c.SubCode IS NULL )
ORDER BY cd.SubCode DESC --Order the not null match first if it exists
) CA
WITH cte
AS (
SELECT a.ID,
a.Code,
a.SubCode,
b.SubCode as bSubCode,
b.Amt,
b.Data,
ROW_NUMBER() OVER(PARTITION BY a.CODE, a.SubCode ORDER BY B.SUBCODE desc) as RN
FROM #Configuration as a
LEFT JOIN #ConfigurationDetails as b ON b.Code = a.Code
AND (a.SubCode = b.SubCode OR b.SubCode IS NULL)
)
SELECT * FROM cte
where rn=1
Related
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
How can I display each column in separate row and at the end add additional field.
For example I have this result:
ID ArticleName Brend1 Brend2 Brend3
== =========== ======== ======== ========
1 TestArticle 10001 20002 30003
I want to achieve this:
ID ArticleName BrandNo BrandName
== =========== ======= =========
1 TestArticle 10001 if column name = Brand1 Then Nike
1 TestArticle 20002 if column name = Brand2 Then Adidas
1 TestArticle 30003 if column name = Brand3 Then Mercedes
I can show each column in separate row, but how can I add additional column to the end of the result BrandName
Here is what I've done:
DECLARE #temTable TABLE
(
Id INT,
ArticleName VARCHAR(20),
Brand1 VARCHAR(20),
Brand2 VARCHAR(20),
Brand3 VARCHAR(20)
);
INSERT INTO #temTable
(
Id,
ArticleName,
Brand1,
Brand2,
Brand3
)
VALUES
(1, 'TestArticle', '10001', '20002', '30003');
SELECT Id,
ArticleName,
b.*
FROM #temTable a
CROSS APPLY
(
VALUES
(Brand1),
(Brand2),
(Brand3)
) b (Brand)
WHERE b.Brand IS NOT NULL;
You could use CROSS APPLY as
SELECT Id, ArticleName, Br BrandNo, Val BrandName
FROM #TemTable TT
CROSS APPLY(
VALUES
(Brand1, 'Nike'),
(Brand2, 'Adidas'),
(Brand3, 'Mercedes')
) T(Br, Val)
db-fiddle
I assume the brand is stored in another table, so you just need to add another column in your VALUES operator, and then join to the Brand Table:
SELECT Id,
ArticleName,
V.Brand
FROM #temTable a
CROSS APPLY (VALUES (1,Brand1),
(2,Brand2),
(3,Brand3)) V (BrandID,Brand)
JOIN dbo.Brand B ON V.BrandID = B.BrandID
WHERE V.Brand IS NOT NULL;
You can use UNPIVOT to achieve this. You can use either a case statement or another table variable to switch column names with brand names, I would prefer a table variable with a join it would make adding new column a bit easier.
DECLARE #d TABLE (ColNames VARCHAR(128) , BrandName VARCHAR(100))
INSERT INTO #d VALUES ('Brand1', 'Nike'),('Brand2', 'Adidas'),('Brand3', 'Mercedes')
SELECT up.Id
, up.ArticleName
, up.BrandNo
, d.BrandName
FROM #temTable
UNPIVOT (BrandNo FOR ColNames IN (Brand1,Brand2,Brand3)) up
INNER JOIN #d d ON d.ColNames = up.ColNames
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
);
A sample table us like
STATUS INVOICE
=======================
processed 100
reconciled 100
reconciled 200
paid 300
paid 100
paid 200
Output should be
STATUS INVOICE
=======================
processed 100
reconciled 200
paid 300
Logic : If there are multiple statuses against an invoice number , then we should follow the below order to fetch .
Processed > reconciled > paid
Please help me with the SQL query statement for this requirement .
This is a prioritization query. You can handle it using row_number():
select t.*
from (select t.*,
row_number() over (partition by invoice
order by case status when 'Processed' then 1 when 'reconciled' then 2 when 'paid' then 3 else 4 end
) as seqnum
from t
) t
where seqnum = 1;
You need conditional ordering with row_number() :
select top (1) with ties t.*
from table t
order by row_number() over (partition by invoice
order by (case status
when 'Processed'
then 1
when 'reconciled'
then 2
when 'paid'
then 3
else 4
end)
);
Others answers are ok but I'm posting it for the cases there's hundres (or more) of categories because in this case populating a table variable is better than writing lots and lots of CASE statements.
The trick here is to populate a table variable or temp table with your ordering rules and just aggregate by it.
create table dbo.[SAMPLE]
(
[STATUS] varchar(30) not null
,[INVOICE] int not null
)
GO
insert into dbo.[SAMPLE]
values
('processed', 100)
,('reconciled', 100)
,('reconciled', 200)
,('paid', 300)
,('paid', 100)
,('paid', 200)
GO
The below is a partial result showing how it get grouped by your ordering rules
--Processed > reconciled > paid
declare #Ordering as table
(
[STATUS] varchar(30) not null
,[Order] smallint not null
)
insert into #Ordering
values
('processed', 3)
,('reconciled',2)
,('paid',1)
select sp.[INVOICE], max(ord.[Order]) as Precedence
from dbo.[SAMPLE] sp
join #Ordering ord on ord.[STATUS] = sp.[STATUS]
group by sp.[INVOICE]
and below the final query with the expected results
--Processed > reconciled > paid
declare #Ordering as table
(
[STATUS] varchar(30) not null
,[Order] smallint not null
)
insert into #Ordering
values
('processed', 3)
,('reconciled',2)
,('paid',1)
select ord.[STATUS], grouped.INVOICE
from
(
select sp.[INVOICE], max(ord.[Order]) as Precedence
from dbo.[SAMPLE] sp
join #Ordering ord on ord.[STATUS] = sp.[STATUS]
group by sp.[INVOICE]
) as grouped
join #Ordering ord on ord.[Order] = grouped.Precedence
It can be also a interesting solution from performance perspective (acid test required of course).
if you have a status table and the order of status like this
id desc
1 processed
2 reconcilied
3 paid
the better way is joining with this tatble, group by invoice and select max(id)
select i.invoice, max(s.id)
from status s left outer join invoice i
on s.desc = i.status
group by i.invoice
if you havn't this table you can use with to create a virtual table and do this or you can use the case then
https://modern-sql.com/feature/with
https://learn.microsoft.com/it-it/sql/t-sql/language-elements/case-transact-sql?view=sql-server-2017
You can try this.
DECLARE #SampleDate TABLE (STATUS VARCHAR(20), INVOICE INT)
INSERT INTO #SampleDate VALUES
('processed', 100),
('reconciled', 100),
('reconciled', 200),
('paid', 300),
('paid', 100),
('paid', 200)
SELECT STATUS, INVOICE FROM (
SELECT T.*, ROW_NUMBER() OVER(PARTITION BY INVOICE ORDER BY St.ID) AS RN FROM #SampleDate T
INNER JOIN (VALUES (1,'processed'), (2,'reconciled'), (3,'paid')) St(Id, Name) ON T.STATUS = St.Name
) AS X WHERE RN= 1
Result:
STATUS INVOICE
-------------------- -----------
processed 100
reconciled 200
paid 300
WITH TempData As (SELECT MAX(INVOICE) AS INVOICE, [STATUS], CASE WHEN [STATUS] = 'processed' THEN 1 WHEN [STATUS] = 'reconciled' THEN 2 WHEN [STATUS] = 'paid' THEN 3 ELSE 4 END AS SEQ
FROM SAMPLETEST GROUP BY [STATUS])
SELECT [STATUS], INVOICE FROM TempData ORDER BY TempData.SEQ;
So lets say I have a Table "Stuff".
It has 3 columns.
So Job Code is supposed to be the same for both manager and the employee under that manager(like it is where jobcode= 000 ). That is the normal scenario.
However in some cases, The Manager will have "ABC" as jobcode.
In those cases, I need to replace "ABC" with the jobcode value of the most recent employee under that manager.
For example, for Manager A1, I need to replace his jobcode of ABC with 234,considering B1 is the most recent employee under him.
For manager A, his jobcode of ABC will be replaced with 121 since B is the only employee under him.
I wrote this query but it doesn't seem to work.
Update X
Set X.JobCode=Y.JobCode
FROM STUFF X
INNER JOIN STUFF Y
ON X.MGRCODE=Y.MGRCODE
AND X.JOBCODE = 'ABC"
AND Y.JOBCODE = ( SELECT TOP 1 JOBCODE FROM STUFF WHERE EMPCODE<>Y.MGRCODE AND
Y.MGRCODE IN (SELECT MGRCODE FROM STUFF WHERE EMPCODE=MGRCODE AND JOBCODE='ABC')
;WITH cteJobCodeRowNum AS (
SELECT
ManagerCode
,JobCode
,ROW_NUMBER() OVER (PARTITION BY ManagerCode ORDER BY UpdateDate DESC) as RowNumber
FROM
#Table
)
UPDATE t
SET JobCode = r.JobCode
FROM
#Table t
INNER JOIN cteJobCodeRowNum r
ON t.EmpCode = r.ManagerCode
AND r.RowNumber = 1
AND t.JobCode <> r.JobCode
WHERE
t.JobCode = 'ABC'
Use a partitioned window function to generate a Row Number to choose the Job Code you want. Partition by ManagerCode and order by UpdateDate descending. Then join that common table expression (or derived table if you nest it) to your table based on EmpCode = ManagerCode To Update The Managers record. You can also then constrain it only to when the Manager has a Job Code of 'ABC' and the job code returned by the row number is different so you only update a specific set of rows.
Another similar method is to create your own row number by using a related cross apply such as:
UPDATE t1
SET JobCode = NewJobCode
FROM
#Table t1
CROSS APPLY (SELECT TOP 1 JobCode as NewJobCode FROM #Table t2 WHERE t1.EmpCode = t2.ManagerCode ORDER BY t2.UpdateDate DESC) n
WHERE
t1.JobCode = 'ABC'
AND t1.JobCode <> n.NewJobCode
Here is a full working example of the window function method:
DECLARE #Table AS TABLE (JobCode CHAR(3), EmpCode VARCHAR(2), ManagerCode VARCHAR(2), UpdateDate DATETIME)
INSERT INTO #Table (JobCode, EmpCode, ManagerCode, UpdateDate)
VALUES ('ABC','A','A',GETDATE()-1),('121','B','B',GETDATE()-1)
,('ABC','A1','A1',GETDATE()-1)
,('234','B1','A1',GETDATE()+1)
,('342','C1','A1',GETDATE()-1)
,('000','A2','A2',GETDATE()-1)
,('000','B2','B2',GETDATE()-1)
SELECT *
FROM
#Table
;WITH cteJobCodeRowNum AS (
SELECT
ManagerCode
,JobCode
,ROW_NUMBER() OVER (PARTITION BY ManagerCode ORDER BY UpdateDate DESC) as RowNumber
FROM
#Table
)
UPDATE t
SET JobCode = r.JobCode
FROM
#Table t
INNER JOIN cteJobCodeRowNum r
ON t.EmpCode = r.ManagerCode
AND r.RowNumber = 1
AND t.JobCode <> r.JobCode
WHERE
t.JobCode = 'ABC'
SELECT *
FROM
#Table