With SQL partitions min and max values are easy to find, but how is Gain or Loss determined over a partition?
This brings in the time element to compare min and max. If max occurs later in time than min, that would be a "Gain". If min occurs later, that would be a "Loss".
How might the GainorLoss column be calculated?
CREATE TABLE Weights (id int, date date, person varchar(40), Weight int);
INSERT INTO Weights VALUES (1, '2022-09-01', 'Alice', 100);
INSERT INTO Weights VALUES (2, '2022-10-01', 'Alice', 105);
INSERT INTO Weights VALUES (3, '2022-11-01', 'Alice', 110);
INSERT INTO Weights VALUES (4, '2022-12-01', 'Alice', 115);
INSERT INTO Weights VALUES (5, '2022-09-01', 'Peter', 150);
INSERT INTO Weights VALUES (6, '2022-10-01', 'Peter', 145);
INSERT INTO Weights VALUES (7, '2022-11-01', 'Peter', 140);
INSERT INTO Weights VALUES (8, '2022-12-01', 'Peter', 135);
select
person
, date
, weight
, min(Weight) OVER (PARTITION BY person) as minWeight
, max(Weight) OVER (PARTITION BY person) as maxWeight
--if max weight occurs after min weight, then "Gain" ELSE "Loss" AS GainorLoss
from weights
Desired output:
person
date
weight
minWeight
maxWeight
GainorLoss
Alice
2022-09-01
100
100
120
Gain
Alice
2022-10-01
105
100
120
Gain
Alice
2022-11-01
110
100
120
Gain
Alice
2022-12-01
120
100
120
Gain
Peter
2022-09-01
150
135
150
Loss
Peter
2022-10-01
145
135
150
Loss
Peter
2022-11-01
140
135
150
Loss
Peter
2022-12-01
135
135
150
Loss
You can use FIRST_VALUE to get the first or the last value based on an order.
The example below calculates 2 GainOrLess results.
The 2nd is what you described. The 1st is what I think you want.
But with the current sample data they give same result.
select *
, CASE
WHEN LastWeight > FirstWeight THEN 'Gain'
WHEN LastWeight < FirstWeight THEN 'Loss'
ELSE 'Same'
END AS [GainOrLoss1]
, CASE
WHEN MaxWeightDate > MinWeightDate THEN 'Gain'
WHEN MaxWeightDate < MinWeightDate THEN 'Loss'
ELSE 'Same'
END AS [GainOrLoss2]
from
(
select
person
, [date]
, weight
, FIRST_VALUE(Weight) OVER (PARTITION BY person ORDER BY [date], id) as FirstWeight
, FIRST_VALUE(Weight) OVER (PARTITION BY person ORDER BY [date] DESC, id DESC) as LastWeight
, FIRST_VALUE([date]) OVER (PARTITION BY person ORDER BY Weight, id) as MinWeightDate
, FIRST_VALUE([date]) OVER (PARTITION BY person ORDER BY Weight DESC, id DESC) as MaxWeightDate
from weights
) q
ORDER BY person, [date]
person | date | weight | FirstWeight | LastWeight | MinWeightDate | MaxWeightDate | GainOrLoss1 | GainOrLoss2
:----- | :--------- | -----: | ----------: | ---------: | :------------ | :------------ | :---------- | :----------
Alice | 2022-09-01 | 100 | 100 | 115 | 2022-09-01 | 2022-12-01 | Gain | Gain
Alice | 2022-10-01 | 105 | 100 | 115 | 2022-09-01 | 2022-12-01 | Gain | Gain
Alice | 2022-11-01 | 110 | 100 | 115 | 2022-09-01 | 2022-12-01 | Gain | Gain
Alice | 2022-12-01 | 115 | 100 | 115 | 2022-09-01 | 2022-12-01 | Gain | Gain
Peter | 2022-09-01 | 150 | 150 | 135 | 2022-12-01 | 2022-09-01 | Loss | Loss
Peter | 2022-10-01 | 145 | 150 | 135 | 2022-12-01 | 2022-09-01 | Loss | Loss
Peter | 2022-11-01 | 140 | 150 | 135 | 2022-12-01 | 2022-09-01 | Loss | Loss
Peter | 2022-12-01 | 135 | 150 | 135 | 2022-12-01 | 2022-09-01 | Loss | Loss
db<>fiddle here
I think the result would be more accurate if you measure the original weight with the current weight.
Or you can measure the status from the prior weight and the current weight.
It's up to you to choose, or you could just use both methods, they can be combined off course.
And I also added a resulttype for when the weight did not changed, when there is no gain and no loss
This method uses the original weight and the current weight to determine the gain-or-loss status
declare #Weights TABLE (id int, [date] date, person varchar(40), Weight int)
INSERT INTO #Weights values
(1, '2022-09-01', 'Alice', 100), (2, '2022-10-01', 'Alice', 105),
(3, '2022-11-01', 'Alice', 110), (4, '2022-12-01', 'Alice', 115),
(5, '2022-09-01', 'Peter', 150), (6, '2022-10-01', 'Peter', 145),
(7, '2022-11-01', 'Peter', 140), (8, '2022-12-01', 'Peter', 135)
select t.person,
t.[date],
t.weight,
t.minWeight,
t.maxWeight,
t.Original,
case when t.Original > t.weight then 'Loss'
when t.Original < t.weight then 'Gain'
else 'Unchanged'
end as GainOrLoss
from ( select w.person,
w.[date],
w.weight,
min(w.Weight) OVER (PARTITION BY w.person) as minWeight,
max(w.Weight) OVER (PARTITION BY w.person) as maxWeight,
FIRST_VALUE(w.Weight) OVER (PARTITION BY person ORDER BY [date], id) as Original
from #weights w
) t
with this result
-------|----------|--------|-----------|-----------|--------|---------
person |date |weight |minWeight |maxWeight |Original GainOrLoss
-------|----------|--------|-----------|-----------|--------|---------
Alice |2022-09-01| 100 |100 |115 |100 |Unchanged
Alice |2022-10-01| 105 |100 |115 |100 |Gain
Alice |2022-11-01| 110 |100 |115 |100 |Gain
Alice |2022-12-01| 115 |100 |115 |100 |Gain
Peter |2022-09-01| 150 |135 |150 |150 |Unchanged
Peter |2022-10-01| 145 |135 |150 |150 |Loss
Peter |2022-11-01| 140 |135 |150 |150 |Loss
Peter |2022-12-01| 135 |135 |150 |150 |Loss
You can test and change is in this DBFiddle
EDIT
If you want to see the weight changes with regards to the prior weight, you can use the LAG funtion.
In my option this would be the best method to follow up someone's weight change.
This method uses the prior weight and the current weight to determine the gain-or-loss status
select t.person,
t.[date],
t.priorWeight,
t.weight,
case when t.priorWeight > t.weight then 'Loss'
when t.priorWeight < t.weight then 'Gain'
else 'nochange'
end as GainOrLoss,
t.minWeight,
t.maxWeight
from ( select w.person,
w.[date],
w.weight,
min(w.Weight) OVER (PARTITION BY w.person) as minWeight,
max(w.Weight) OVER (PARTITION BY w.person) as maxWeight,
lag(w.Weight) over (partition by person order by [date]) as priorWeight
from #weights w
) t
-------|----------|------------|-------|----------|---------|---------
person |date |priorWeight |weight |GainOrLoss|minWeight|maxWeight
-------|----------|------------|-------|----------|---------|---------
Alice |2022-09-01| |100 |nochange |100 |115
Alice |2022-10-01|100 |105 |Gain |100 |115
Alice |2022-11-01|105 |110 |Gain |100 |115
Alice |2022-12-01|110 |115 |Gain |100 |115
Peter |2022-09-01| |150 |nochange |135 |150
Peter |2022-10-01|150 |145 |Loss |135 |150
Peter |2022-11-01|145 |140 |Loss |135 |150
Peter |2022-12-01|140 |135 |Loss |135 |150
And again a DBFiddle you can use to play with this query
You can combine the 2 methods offcourse
Related
I have a table with the purchase registers. There are all the purchase registers of a Pet Shop, since 2010.
I need some help to bring only the last five purchase of each client.
I was trying, but it is not working. It brings me the last 5 registers of all the table, and not of each client.
SELECT TOP (5) [client_name],
[purchase_date],
[item]
FROM [Pet_Shop]
ORDER BY client_name
WHERE client_name in ('John', 'Mary', 'Austin')
I need this kind of return:
client_name | purchase_date | item
___________________________________
John | 2019-09-14 | food
John | 2019-09-13 | ball
John | 2019-09-12 | shampoo
John | 2019-09-11 | cookie
John | 2019-09-11 | food
Mary | 2019-09-14 | collar
Mary | 2019-07-14 | food
Mary | 2019-06-14 | toy
Mary | 2019-06-14 | hamster
Mary | 2019-05-14 | food
Austin | 2019-09-18 | food
Austin | 2019-09-11 | collar
Austin | 2019-09-10 | toy
Austin | 2019-09-09 | catnip
Austin | 2019-09-11 | food
Use ROW_NUMBER():
SELECT *
FROM (
SELECT
client_name,
purchase_date,
item,
ROW_NUMBER() OVER(PARTITION BY client_name ORDER BY purchase_date desc) rn
FROM Pet_Shop
WHERE client_name in ('John', 'Mary', 'Austin')
) x
WHERE rn <= 5
ORDER BY client_name
You can use CROSS APPLY like this:
DECLARE #Registry TABLE (client_name VARCHAR(100), purchase_date DATETIME, item INT)
INSERT INTO #Registry
(client_name, purchase_date, item)
VALUES
('Client1', '1/1/2019', 1),
('Client1', '2/1/2019', 2),
('Client1', '3/1/2019', 3),
('Client1', '4/1/2019', 4),
('Client1', '5/1/2019', 5),
('Client1', '6/1/2019', 6),
('Client1', '7/1/2019', 7),
('Client2', '1/1/2019', 1),
('Client2', '2/1/2019', 2),
('Client2', '3/1/2019', 3),
('Client2', '4/1/2019', 4),
('Client2', '5/1/2019', 5),
('Client2', '6/1/2019', 6),
('Client2', '7/1/2019', 7)
;WITH Clients AS (
SELECT client_name FROM #Registry GROUP BY client_name
)
SELECT C.*, P.purchase_date, P.item
FROM Clients AS C
CROSS APPLY
(
SELECT TOP 5 R.purchase_date, R.item
FROM #Registry R
WHERE R.client_name = C.client_name
ORDER BY R.purchase_date DESC
) P
ORDER BY C.client_name, P.purchase_date, P.item
Here is the result:
client_name purchase_date item
Client1 2019-03-01 00:00:00.000 3
Client1 2019-04-01 00:00:00.000 4
Client1 2019-05-01 00:00:00.000 5
Client1 2019-06-01 00:00:00.000 6
Client1 2019-07-01 00:00:00.000 7
Client2 2019-03-01 00:00:00.000 3
Client2 2019-04-01 00:00:00.000 4
Client2 2019-05-01 00:00:00.000 5
Client2 2019-06-01 00:00:00.000 6
Client2 2019-07-01 00:00:00.000 7
Table-1:
kode | 101 | 102 | 103 | 104
=================================
1234 | 100 | 200 | 300 | 400
4555 | 1200 | 130 | 14500 | 1550
5012 | 100 | 150 | 350 | 440
Table-2:
kode | field1 | field2
=======================
1234 | 101 | 100
1234 | 102 | 200
1234 | 103 | 300
1234 | 104 | 400
4555 | 101 | 1200
4555 | 102 | 130
4555 | 103 | 14500
4555 | 104 | 1550
5012 | 101 | 100
5012 | 102 | 150
5012 | 103 | 350
5012 | 104 | 440
I have data in table-1, how to insert data from table-1 totable-2
with use sql query like unpivot to pivot which dynamic by using set #cols
You can use UNPIVOT to create the dataset you want. Let's say your tables looked like this:
create table table1 (
kode int,
[101] int,
[102] int,
[103] int,
[104] int
);
insert into table1 values
(1234 , 100 , 200 , 300 , 400),
(4555 , 1200 , 130 , 14500 , 1550),
(5012 , 100 , 150 , 350 , 440);
Your query will look like this
SELECT kode, field1, field2
FROM table1
UNPIVOT
(
field2 FOR field1 IN ([101], [102], [103], [104])
) AS up;
That will give you the desired result.
Let's way you have a new table like this
create table table2 (
kode int,
field1 int,
field2 int
);
Populate UNPIVOT'ed data into table2
insert into table2
SELECT kode, field1, field2
FROM table1
UNPIVOT
(
field2 FOR field1 IN ([101], [102], [103], [104])
) AS up;
select * from table2;
Example: https://rextester.com/YPWG93602
I'm looking to get the days between result dates for each patient: only looking at result dates where the result value is <90.00
;WITH patient_results AS (
SELECT * FROM (VALUES
(1, 'EA11AEE3-1D90-4602-9A37-0000007E2293', '85.10' ,'2015-12-11'),
(1, '27BCD3E4-2381-4139-B420-0000025B4113', '91.50' ,'2016-01-05'),
(1, 'D8969360-45D6-487B-AF94-0000035F78B0', '81.00' ,'2016-07-21'),
(5, '446E6413-442A-452A-BCF4-000006AA9896', '58.00' ,'2014-07-01'),
(5, '00305129-BC14-4A12-8368-00000AC04A9B', '53.00' ,'2014-12-13'),
(5, '96A67E53-2D6C-430B-A01F-00000AE4C37B', '42.80' ,'2015-02-01'),
(5, '7C330511-3E99-488C-AF5E-00000BDFA3FF', '54.00' ,'2015-07-01'),
(8, '62A2806A-4969-417A-B4DF-D547621CC594', '89.00' ,'2016-03-10'),
(8, '3B9F4E5B-3433-4F21-850A-FC2127A24B72', '92.60' ,'2016-06-30'),
(8, '1A2D780D-8C11-451C-8A64-6D49140B6232', '88.00' ,'2016-08-05')
) as t (pat_id, visit_id, result_value, result_date))
Based on the above looking to get something like this:
PAT_ID | VISIT_ID | RESULT_VALUE | RESULT_DATE| DAYSBETWEENRESULTDATES
1 | EA11AEE3-1D90-4602-9A37-0000007E2293 | 85.10 | 2015-12-11 | 0
1 | D8969360-45D6-487B-AF94-0000035F78B0 | 81.00 | 2016-07-21 | 223
5 | 446E6413-442A-452A-BCF4-000006AA9896 | 58.00 | 2014-07-01 | 0
5 | 00305129-BC14-4A12-8368-00000AC04A9B | 53.00 | 2014-12-13 | 165
5 | 96A67E53-2D6C-430B-A01F-00000AE4C37B | 42.80 | 2015-02-01 | 50
5 | 7C330511-3E99-488C-AF5E-00000BDFA3FF | 54.00 | 2015-07-01 | 150
8 | 62A2806A-4969-417A-B4DF-D547621CC594 | 89.00 | 2016-03-10 | 0
8 | 1A2D780D-8C11-451C-8A64-6D49140B6232 | 84.00 | 2016-08-05 | 148
I am using Sql Server 2012, Sql server management studio version 11.0.5058.0
Thank you.
Try this.
;WITH patient_results
AS
(
SELECT * FROM
(VALUES (1, 'EA11AEE3-1D90-4602-9A37-0000007E2293', '85.10' ,'2015-12-11'),
(1, '27BCD3E4-2381-4139-B420-0000025B4113', '91.50' ,'2016-01-05'),
(1, 'D8969360-45D6-487B-AF94-0000035F78B0', '81.00' ,'2016-07-21'),
(5, '446E6413-442A-452A-BCF4-000006AA9896', '58.00' ,'2014-07-01'),
(5, '00305129-BC14-4A12-8368-00000AC04A9B', '53.00' ,'2014-12-13'),
(5, '96A67E53-2D6C-430B-A01F-00000AE4C37B', '42.80' ,'2015-02-01'),
(5, '7C330511-3E99-488C-AF5E-00000BDFA3FF', '54.00' ,'2015-07-01'),
(8, '62A2806A-4969-417A-B4DF-D547621CC594', '89.00' ,'2016-03-10'),
(8, '3B9F4E5B-3433-4F21-850A-FC2127A24B72', '92.60' ,'2016-06-30'),
(8, '1A2D780D-8C11-451C-8A64-6D49140B6232', '88.00' ,'2016-08-05') )
as t (pat_id, visit_id, result_value, result_date))
SELECT *, ISNULL(DATEDIFF(DAY, LAG(result_date) OVER(PARTITION BY pat_id ORDER BY result_date), result_date), 0) as daysBetweenResultDates
FROM patient_results
WHERE result_value < 90.00
Result
pat_id visit_id result_value result_date DaysBetweenResultDates
1 EA11AEE3-1D90-4602-9A37-0000007E2293 85.10 2015-12-11 0
1 D8969360-45D6-487B-AF94-0000035F78B0 81.00 2016-07-21 223
5 446E6413-442A-452A-BCF4-000006AA9896 58.00 2014-07-01 0
5 00305129-BC14-4A12-8368-00000AC04A9B 53.00 2014-12-13 165
5 96A67E53-2D6C-430B-A01F-00000AE4C37B 42.80 2015-02-01 50
5 7C330511-3E99-488C-AF5E-00000BDFA3FF 54.00 2015-07-01 150
8 62A2806A-4969-417A-B4DF-D547621CC594 89.00 2016-03-10 0
8 1A2D780D-8C11-451C-8A64-6D49140B6232 88.00 2016-08-05 148
You can use OUTER APPLY to get previous values:
SELECT p.*,
ISNULL(DATEDIFF(DAY,t.result_date,p.result_date),0) AS DaysBetweenResultDates
FROM patient_results p
OUTER APPLY (
SELECT TOP 1 result_date
FROM patient_results
WHERE pat_id = p.pat_id and
result_date < p.result_date and
result_value < 90
ORDER BY result_date DESC) as t
WHERE p.result_value <90
I have a question in sql server
table name : Emp
Id |Pid |Firstname| LastName | Level
1 |101 | Ram |Kumar | 3
1 |100 | Ravi |Kumar | 2
2 |101 | Jaid |Balu | 10
1 |100 | Hari | Babu | 5
1 |103 | nani | Jai |44
1 |103 | Nani | Balu |10
3 |103 |bani |lalu |20
Here need to retrieve unique records based on id and Pid columns and records which have duplicate records need to skip.
Finally I want output like below
Id |Pid |Firstname| LastName | Level
1 |101 | Ram |Kumar | 3
2 |101 | Jaid |Balu | 10
3 |103 |bani |lalu |20
I found duplicate records based on below query
select id,pid,count(*) from emp group by id,pid having count(*) >=2
this query get duplicated records 2 that records need to skip to retrieve output
please tell me how to write query to achieve this task in sql server.
Since your output is based on unique ID and PID which do not have any duplicate value, You can use COUNT with partition to achieve your desired result.
SQL Fiddle
Sample Data
CREATE TABLE Emp
([Id] int, [Pid] int, [Firstname] varchar(4), [LastName] varchar(5), [Level] int);
INSERT INTO Emp
([Id], [Pid], [Firstname], [LastName], [Level])
VALUES
(1, 101, 'Ram', 'Kumar', 3),
(1, 100, 'Ravi', 'Kumar', 2),
(2, 101, 'Jaid', 'Balu', 10),
(1, 100, 'Hari', 'Babu', 5),
(1, 103, 'nani', 'Jai', 44),
(1, 103, 'Nani', 'Balu', 10),
(3, 103, 'bani', 'lalu', 20);
Query
SELECT *
FROM
(
SELECT *,rn = COUNT(*) OVER(PARTITION BY ID,PID)
FROM Emp
) Emp
WHERE rn = 1
Output
| Id | Pid | Firstname | LastName | Level |
|----|-----|-----------|----------|-------|
| 1 | 101 | Ram | Kumar | 3 |
| 2 | 101 | Jaid | Balu | 10 |
| 3 | 103 | bani | lalu | 20 |
I am trying to clean up a not so useful history table by changing it's format.
For the usage of the history table it is relevant between which time a row was valid.
The current situation:
Unit | Value | HistoryOn |
----------------------------------------
1 | 123 | 2013-01-05 14:16:00
1 | 234 | 2013-01-07 12:12:00
2 | 325 | 2013-01-04 14:12:00
1 | 657 | 2013-02-04 17:11:00
3 | 132 | 2013-04-02 13:00:00
The problem that arises here is that as this table grows it will become increasingly resource hungry when I want to know what status all of my containers had during a certain period. (say I want to know the value for all units on a specific date)
My solution is to create a table in this format:
Unit | value | HistoryStart | HistoryEnd |
---------------------------------------------------------------------
1 | 123 | 2013-01-05 14:16:00 | 2013-01-07 12:11:59
1 | 234 | 2013-01-07 12:12:00 | 2013-02-04 17:10:59
1 | 657 | 2013-02-04 17:11:00 | NULL
2 | 325 | 2013-01-04 14:12:00 | NULL
3 | 132 | 2013-04-02 13:00:00 | NULL
Note that the NULL value in HistoryEnd here indicates that the row is still representative of the current status.
I have tried to make use of a left join on the table itself using the HistoryOn field. This had the unfortunate side effect of cascading in an undesired manner.
SQL Query used:
SELECT *
FROM webhistory.Units u1 LEFT JOIN webhistory.Units u2 on u1.Unit = u2.Unit
AND u1.HistoryOn < u2.HistoryOn
WHERE u1.Units = 1
The result of the query is as follows:
Unit | Value | HistoryOn | Unit | Value | HistoryOn |
-------------------------------------------------------------------------------------
1 | 657 | 2013-02-04 17:11:00 | NULL | NULL | NULL
1 | 234 | 2013-01-07 12:12:00 | 1 | 657 | 2013-02-04 17:11:00
1 | 123 | 2013-01-05 14:16:00 | 1 | 657 | 2013-02-04 17:11:00
1 | 123 | 2013-01-05 14:16:00 | 1 | 234 | 2013-01-07 12:12:00
This effect is incremental because each entry will join on all the entries that are newer than itself instead of only the first entry that comes after it.
Sadly right as of yet I am unable to come up with a good query to solve this and would like insights or suggestions that could help me solve this migration problem.
Maybe I'm missing something, but this seems to work:
CREATE TABLE #webhist(
Unit int,
Value int,
HistoryOn datetime
)
INSERT INTO #webhist VALUES
(1, 123, '2013-01-05 14:16:00'),
(1, 234, '2013-01-07 12:12:00'),
(2, 325, '2013-01-04 14:12:00'),
(1, 657, '2013-02-04 17:11:00'),
(3, 132, '2013-04-02 13:00:00')
SELECT
u1.Unit
,u1.Value
,u1.HistoryOn AS HistoryStart
,u2.HistoryOn AS HistoryEnd
FROM #webhist u1
OUTER APPLY (
SELECT TOP 1 *
FROM #webhist u2
WHERE u1.Unit = u2.Unit AND u1.HistoryOn < u2.HistoryOn
ORDER BY HistoryOn
) u2
DROP TABLE #webhist
First data sample
create table Data(
Unit int,
Value int,
HistoryOn datetime)
insert into Data
select 1,123,'2013-01-05 14:16:00'
union select 1 , 234 , '2013-01-07 12:12:00'
union select 2 , 325 , '2013-01-04 14:12:00'
union select 1 , 657 , '2013-02-04 17:11:00'
union select 3 , 132 , '2013-04-02 13:00:00'
I created a function to calculate HistoryEnd
Noticed I named Data to table
CREATE FUNCTION dbo.fnHistoryEnd
(
#Unit as int,
#HistoryOn as datetime
)
RETURNS datetime
AS
BEGIN
-- Declare the return variable here
DECLARE #HistoryEnd as datetime
select top 1 #HistoryEnd=dateadd(s,-1,d.HistoryOn )
from Data d
where d.HistoryOn>#HistoryOn and d.Unit=#Unit
order by d.HistoryOn asc
RETURN #HistoryEnd
END
GO
Then, the query is trivial
select *,dbo.fnHistoryEnd(a.Unit,a.HistoryOn) from Data a
order by Unit, HistoryOn
EDIT
Don't forget order by clause in sub query. Look what could happen if not
CREATE TABLE #webhist(
Unit int,
Value int,
HistoryOn datetime
)
INSERT INTO #webhist VALUES
(1, 234, '2013-01-07 12:12:00'),
(2, 325, '2013-01-04 14:12:00'),
(1, 657, '2013-02-04 17:11:00'),
(3, 132, '2013-04-02 13:00:00'),
(1, 123, '2013-01-05 14:16:00')
select *, (select top 1 historyon from #webhist u2 where u2.historyon > u1.historyon and u1.unit = u2.unit) from #webhist u1;
select *, (select top 1 historyon from #webhist u2 where u2.historyon > u1.historyon and u1.unit = u2.unit order by u2.HistoryOn) from #webhist u1;
drop table #webhist