SQL Server 2008 - Retrieve the nearest non null data - sql

I have two tables: table 1 a package_id and a timestamp column for which I have no weight information available, table 2 a package_id, a timestamp and a weight column where I do have the information.
What I'm trying to do is fill in the table 1 weight information based on table 2 using the following restrictions:
use the closest package_id available ie. for package_id 1 use 2 if available, if not 3 etc
if there is only one weight available use it for all the missing package_id's
if two weights are available, use the higher one ie. for package_id 5, if 4 and 6 are available use 6
The code:
IF OBJECT_ID('tempdb..#TIMEGAPS') IS NOT NULL DROP TABLE #TIMEGAPS
CREATE TABLE #TIMEGAPS (PACK_ID INT, Local_Time DATETIME)
IF OBJECT_ID('tempdb..#REALVALUES') IS NOT NULL DROP TABLE #REALVALUES
CREATE TABLE #REALVALUES (PACK_ID INT, Local_Time DATETIME, WEIGHT INT)
INSERT INTO #TIMEGAPS VALUES
(1,'2018-01-20 18:40:00.000'),
(1,'2018-01-20 18:50:00.000'),
(1,'2018-01-20 19:00:00.000'),
-----------------------------
(7,'2018-01-20 18:40:00.000'),
(7,'2018-01-20 18:50:00.000'),
(7,'2018-01-20 19:00:00.000'),
------------------------------
(12,'2018-01-20 18:40:00.000'),
(12,'2018-01-20 18:50:00.000'),
(12,'2018-01-20 19:00:00.000'),
(12,'2018-01-20 20:00:00.000')
INSERT INTO #REALVALUES VALUES
(2,'2018-01-20 18:40:00.000',50),
(3,'2018-01-20 18:40:00.000',70),
(4,'2018-01-20 18:40:00.000',150),
(5,'2018-01-20 18:40:00.000',60),
(6,'2018-01-20 18:40:00.000',45),
(8,'2018-01-20 18:40:00.000',55),
(9,'2018-01-20 18:40:00.000',25),
---------------------------------
(2,'2018-01-20 18:50:00.000',75),
(3,'2018-01-20 18:50:00.000',80),
(4,'2018-01-20 18:50:00.000',120),
(5,'2018-01-20 18:50:00.000',110),
(11,'2018-01-20 18:50:00.000',30),
---------------------------------
(8,'2018-01-20 19:00:00.000',70)
EDIT:
I've adapted the solution from here which I believe is what I need.
SELECT tg.PACK_ID, tg.Local_Time, p.WEIGHT
FROM #TIMEGAPS tg
OUTER APPLY
(
SELECT TOP 1 *, ABS(tg.PACK_ID - rv.PACK_ID) AS diff
FROM #REALVALUES rv
WHERE (tg.Local_Time = rv.Local_time OR rv.Local_time is null)
ORDER BY CASE WHEN rv.Local_time IS NULL THEN 2 ELSE 1 END,
ABS(rv.PACK_ID- tg.PACK_ID) ASC
) p
EDIT 2:
3. if two weights are available, use the highest PACK_ID ie. for package_id 5, if PACK_ID 4 and PACK_ID 6 are available use 6

Something like this?
It uses a row_number by distance.
SELECT
PACK_ID, Local_Time, WEIGHT
FROM (
SELECT g.PACK_ID, g.Local_Time, v.WEIGHT,
ROW_NUMBER() OVER (PARTITION BY g.PACK_ID, g.Local_Time
ORDER BY ABS(v.PACK_ID - g.PACK_ID), v.PACK_ID DESC) AS RN
FROM #TIMEGAPS AS g
JOIN #REALVALUES AS v ON v.Local_Time = g.Local_Time
) AS q
WHERE RN = 1
ORDER BY PACK_ID, Local_Time

I suppose we can start here. Make sure to initialize your tables before you run this. I assumed that #TIMEGAPS has a weight column based on your output.
DECLARE
#Pack_id INT
, #Weight_id INT
, #mloop INT = 0
DECLARE #possible TABLE (
id INT IDENTITY (1,1)
, pack_id INT
, weight )
BEGIN_LABEL:
SELECT TOP 1 #pack_id = PACK_ID
FROM #TIMEGAPS AS g
WHERE g.WEIGHT IS NULL
ORDER BY PACK_ID ASC
IF #pack_id IS NULL
BEGIN
PRINT 'Done'
EXIT
END
INSERT INTO #possible (pack_id , weight )
SELECT PACK_ID , WEIGHT
FROM #REALVALUES as r
LEFT JOIN #TIMEGAPS as g
ON g.WEIGHT = r.PACK_ID
WHERE g.WEIGHT IS NULL
ORDER BY ABS(#pack_id - PACK_ID) ASC , WEIGHT DESC
SELECT TOP 1 #Weight_id = weight
FROM #possible
ORDER BY id ASC
IF (#Weight_id IS NULL)
BEGIN
RAISERROR('No Weights available' , 18 , 1)
EXIT
END
UPDATE #TIMEGAPS
SET WEIGHT = #Weight_id
WHERE PACK_ID = #Pack_id
SET #mloop = #mloop + 1
IF #mloop > 99
BEGIN
PRINT 'Hit Safety'
EXIT
END
SELECT #Pack_id = NULL , #Weight_id = NULL;
DELETE #possible;
GOTO BEGIN_LABEL
SELECT g.PACK_ID , g.Local_Time , r.WEIGHT
FROM #TIMEGAPS AS g
INNER JOIN #REALVALUES AS r
r.PACK_ID = g.WEIGHT;
Im pretty sure RAISERROR() works in SQL 2008, but you can just replace them with print statements if they don't

Related

Select an excess quantity(rows) based on a total quantity from another table

I'm trying to select an excess quantity (per row grouped by same Groupletter and Date) based on a total quantity of the another table. I am able to get the excess but I think it's a long code and kinda hard to understand. So I'm wondering if someone can make my code shorter and precise.
Here is my code:
IF (SELECT OBJECT_ID('tempdb..#TableA')) IS NOT NULL
DROP TABLE #TableA
GO
CREATE TABLE #TableA
(
GroupLetter CHAR(2),
Quantity INT,
ValueDate DATE
)
INSERT INTO #TableA VALUES
('A',1,'01-02-2000'),('A',1,'01-02-2000'),('A',1,'01-02-2000'),('A',2,'01-03-2000'),('A',2,'01-03-2000'),
('B',2,'01-04-2002'),('B',2,'01-05-2002'),
('C',1,'01-02-2003'),('C',1,'01-02-2003'),('C',1,'02-02-2003'),
('D',1,'02-02-2004'),('D',1,'02-02-2004'),
('E',30,'01-02-2005'),('E',3,'01-07-2005'),('E',1,'01-02-2005'),
('F',30,'01-06-2006'),('F',15,'01-06-2006'),('F',2,'01-08-2006'),
('G',1,'01-02-2007')
;
IF (SELECT OBJECT_ID('tempdb..#TableB')) IS NOT NULL
DROP TABLE #TableB
GO
CREATE TABLE #TableB
(
GroupLetter CHAR(2),
Limit INT
)
INSERT INTO #TableB VALUES
('A',3),
('B',3),
('c',1),
('D',2),
('E',29),
('F',32),
('G',3)
;
------------------------------------------------
WITH Step1 AS
(
SELECT a.GroupLetter,
a.Quantity,
a.ValueDate,
SUM(a.Quantity) OVER (PARTITION BY a.GroupLetter,
a.ValueDate
ORDER BY a.Quantity DESC
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS RunningQty,
b.Limit
FROM #TableA a
JOIN #TableB b ON a.GroupLetter = b.GroupLetter
)
,Step2 AS
(
SELECT
*,
CASE
WHEN RunningQty <= Limit THEN Limit
ELSE Limit - LAG(RunningQty,1,0) OVER (PARTITION BY GroupLetter,
ValueDate
ORDER BY Quantity DESC)
END AS Qty
FROM Step1
)
,Step3 AS
(
SELECT
*,
CASE
WHEN qty <= 0
THEN Quantity
WHEN Quantity - qty > 0
THEN Quantity - qty
END AS DeniedQty
FROM Step2
)
SELECT dg.GroupLetter,
dg.DeniedQty,
dg.ValueDate
FROM Step3 dg
WHERE (dg.DeniedQty > 0 AND dg.DeniedQty IS NOT NULL)
And the result is:
GroupLetter
DeniedQty
ValueDate
A
1
2000-01-03
C
1
2003-01-02
E
1
2005-01-02
E
1
2005-01-02
F
13
2006-01-06

Compare two rows (both with different ID) & check if their column values are exactly the same. All rows & columns are in the same table

I have a table named "ROSTER" and in this table I have 22 columns.
I want to query and compare any 2 rows of that particular table with the purpose to check if each column's values of that 2 rows are exactly the same. ID column always has different values in each row so I will not include ID column for the comparing. I will just use it to refer to what rows will be used for the comparison.
If all column values are the same: Either just display nothing (I prefer this one) or just return the 2 rows as it is.
If there are some column values not the same: Either display those column names only or display both the column name and its value (I prefer this one).
Example:
ROSTER Table:
ID
NAME
TIME
1
N1
0900
2
N1
0801
Output:
ID
TIME
1
0900
2
0801
OR
Display "TIME"
Note: Actually I'm okay with whatever result or way of output as long as I can know in any way that the 2 rows are not the same.
What are the possible ways to do this in SQL Server?
I am using Microsoft SQL Server Management Studio 18, Microsoft SQL Server 2019-15.0.2080.9
Please try the following solution based on the ideas of John Cappelletti. All credit goes to him.
SQL
-- DDL and sample data population, start
DECLARE #roster TABLE (ID INT PRIMARY KEY, NAME VARCHAR(10), TIME CHAR(4));
INSERT INTO #roster (ID, NAME, TIME) VALUES
(1,'N1','0900'),
(2,'N1','0801')
-- DDL and sample data population, end
DECLARE #source INT = 1
, #target INT = 2;
SELECT id AS source_id, #target AS target_id
,[key] AS [column]
,source_Value = MAX( CASE WHEN Src=1 THEN Value END)
,target_Value = MAX( CASE WHEN Src=2 THEN Value END)
FROM (
SELECT Src=1
,id
,B.*
FROM #roster AS A
CROSS APPLY ( SELECT [Key]
,Value
FROM OpenJson( (SELECT A.* For JSON Path,Without_Array_Wrapper,INCLUDE_NULL_VALUES))
) AS B
WHERE id=#source
UNION ALL
SELECT Src=2
,id = #source
,B.*
FROM #roster AS A
CROSS APPLY ( SELECT [Key]
,Value
FROM OpenJson( (SELECT A.* For JSON Path,Without_Array_Wrapper,INCLUDE_NULL_VALUES))
) AS B
WHERE id=#target
) AS A
GROUP BY id, [key]
HAVING MAX(CASE WHEN Src=1 THEN Value END)
<> MAX(CASE WHEN Src=2 THEN Value END)
AND [key] <> 'ID' -- exclude this PK column
ORDER BY id, [key];
Output
+-----------+-----------+--------+--------------+--------------+
| source_id | target_id | column | source_Value | target_Value |
+-----------+-----------+--------+--------------+--------------+
| 1 | 2 | TIME | 0900 | 0801 |
+-----------+-----------+--------+--------------+--------------+
A general approach here might be to just aggregate over the entire table and report the state of the counts:
SELECT
CASE WHEN COUNT(DISTINCT ID) = COUNT(*) THEN 'Yes' ELSE 'No' END AS [ID same],
CASE WHEN COUNT(DISTINCT NAME) = COUNT(*) THEN 'Yes' ELSE 'No' END AS [NAME same],
CASE WHEN COUNT(DISTINCT TIME) = COUNT(*) THEN 'Yes' ELSE 'No' END AS [TIME same]
FROM yourTable;

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

SSRS report column list sort order

Hi for my ssrs report i am using a matrix to display data row as a two column list and
I am using the following expression in order to group the row;
=ceiling(rownumber(nothing) / 2)
and
the following expression to group column;
=ceiling(rownumber(nothing) mod 2)
similar to https://www.experts-exchange.com/articles/12331/Simple-way-to-show-multi-column-data-in-SSRS-Horizontally-or-Vertically.html
it is working correctly however i would like results to be display alphabetical order going vertical instead of horizontal.
Like.
Record a Record d
Record b Record e
Record c Record f
Instead of
Record a Record b
Record c Record d
Record e Record f
i have order by in my sql query
any suggestions on how to achieve this?
You can modify your sql for this. The hack below could be used if you do not want to use multiple columns.
DECLARE #T TABLE
(
TableID INT,
Value1 NVARCHAR(20),
ShouldBe INT
)
INSERT INTO #T (TableID,Value1,ShouldBe)
VALUES
(1,'A',1),
(2,'B',3),
(3,'C',5),
(4,'D',2),
(5,'E',4),
(6,'F',6),
(7,'A',1),
(8,'B',3),
(9,'C',5),
(10,'D',2),
(11,'E',4),
(12,'F',6),
(13,'A',1)
DECLARE #NumberOfRowsPerPage INT = 3
DECLARE #NumberOfColumns INT = 2
SELECT PageNumber,Value1
FROM
(
SELECT ColumnOrder=ROW_NUMBER() OVER(PARTITION BY PageNumber,ColumnTile ORDER BY TableID),*
FROM
(
SELECT ColumnTile=NTILE(#NumberOfColumns) OVER(PARTITION BY PageNumber ORDER BY TableID),*
FROM
(
SELECT PageNumber=( (DataRowNumber -1) / (#NumberOfRowsPerPage * #NumberOfColumns )) + 1, *
FROM
(
SELECT DataRowNumber=ROW_NUMBER() OVER( ORDER BY TableID) ,*
FROM #T
)AS A
)AS B
)AS C
)AS D
ORDER BY
PageNumber,ColumnOrder,DataRowNumber
The query will produce the following output based on the RowsPerPage and NumberOfColumns.
Page Value
1 A
1 D
1 B
1 E
1 C
1 F
2 A
2 D
2 B
2 E
2 C
2 F
3 A

Best way to get single MAX value + another column value from SQL

I've got the following three tables defined:
table 'A':
-------------------
majorID | bigint (primary key)
-------------------
table 'B':
-------------------
majorID | bigint (foreign key to table 'A's majorID)
minorID | bigint (primary key)
totalSize | bigint
-------------------
table 'C':
-------------------
objectID | bigint (primary key)
majorID | bigint (foreign key to table 'A's majorID)
minorID | bigint (foreign key to table 'B's minorID)
startPoint | bigint
length | bigint
-------------------
What I'm looking to do is get a list of all rows in table 'B', but show how much space is left for each row.
The remaining space can be found by finding the highest "startPoint", adding the value o the "length" column for the row containing the highest "startPoint", then subtracting that combined value from the "totalSize" column in table 'B'
I am currently able to achieve this using the following code:
create table #results (MinorID bigint, MajorID bigint, RemainingSpace bigint)
DECLARE #MinorID bigint
DECLARE #TotalSpace bigint
DECLARE #MajorID bigint
DECLARE cur CURSOR FOR
SELECT MinorID, MajorID, TotalSize FROM B
OPEN cur
FETCH NEXT FROM cur INTO #MinorID,#MajorID, #TotalSpace
WHILE ##FETCH_STATUS = 0
BEGIN
DECLARE #UsedSize bigint
SELECT TOP 1 #UsedSize = StartPoint + [length] FROM C
WHERE MinorID = #MinorID AND MajorID = #MajorID
ORDER BY StartPoint DESC
INSERT INTO #results VALUES (#MinorID,#MajorID,#TotalSpace - #UsedSize)
FETCH NEXT FROM cur INTO #MinorID,#MajorID, #TotalSpace
END
CLOSE cur
DEALLOCATE cur
SELECT * FROM #results
drop table #results
The problem is that I expect these tables are going to get VERY large, and I realise that running a cursor over the tables probably isn't the fastest way to achieve what I want.
However, I'm struggling to find a better solution (Monday morning blues), and was hoping someone more awake / better at SQL than me can suggest a solution!
note: The table designs are not "set in stone", so if the only solution is to denormalize the data so that table 'B' keeps a record of it's "taken space", then I'm open to that...
EDIT:
I went with a modified version of the accepted answer, as follows:
SELECT B.*, coalesce(C.StartPoint + C.Length,0) AS UsedSize
FROM TableB B
LEFT JOIN
(
SELECT *, DENSE_RANK() OVER(PARTITION BY C.MajorID, C.MinorID ORDER BY C.StartPoint DESC) AS Rank
FROM TableC C
) C
ON C.MajorID = B.MajorID
AND C.MinorID = B.MinorID
AND C.Rank = 1
Maybe you can work with the DENSE_RANK.
In this query i am joining the table C with the extra column Rank. This column is given the value 1 if its the highest startpoint. In the (AND C.Rank = 1) we only extract that row.
SELECT B.*, (C.StartPoint + C.Length) AS UsedSize
FROM TableB B
INNER JOIN
(
SELECT *, DENSE_RANK() OVER(PARTITION BY C.MajorID, C.MinorID ORDER BY C.StartPoint DESC) AS Rank
FROM TableC C
) C
ON C.MajorID = B.MajorID
AND C.MinorID = B.MinorID
AND C.Rank = 1
Maybe you are making things more complicated than they are. You are looking for the maximum startPoint for each minorID in order to add length and get thus the used size. But is it possible at all to have a lesser startPoint where its length is so big that adding both would exceed the maximum startPoint plus its length?
Is this possible ( max (startPoint) being lower than some other startPoint+length ):
minorID startPoint length
1 1 10
1 9 3
If not, what I assume, you can simply subtract max(startPoint + length):
select
minorID,
totalSize,
totalSize - (select max(startPoint + length) from C where C.minorID = B.minorID) as space_left
from B;
EDIT: I just read your comment that sometimes no C for a B exists. To account for this you will have to use ISNULL or COALESCE:
select
minorID,
totalSize,
totalSize - coalesce((select max(startPoint + length) from C where C.minorID = B.minorID), 0) as space_left
from B;
WITH UsedSpace AS
(
SELECT minorID, MAX(startPoint + length) AS used
FROM C
GROUP BY minorID
)
SELECT B.minorID, totalSize - COALESCE(UsedSpace.used, 0)
FROM B LEFT JOIN UsedSpace ON B.minorID = UsedSpace.minorID
You can use OUTER APPLY to get the top record from C ordered by StartPoint
SELECT B.MajorID,
B.MinorID,
B.TotalSize,
C.StartPoint,
C.Length,
SpaceRemaining = B.TotalSize - ISNULL(C.StartPoint + C.Length, 0)
FROM B
OUTER APPLY
( SELECT TOP 1 C.StartPoint, C.Length
FROM C
WHERE B.MinorID = c.MinorID
ORDER BY C.StartPoint DESC
) C;
Or you can use ROW_NUMBER to achieve the same result, depending on indexes etc, one may perform better than the other:
SELECT B.MajorID,
B.MinorID,
B.TotalSize,
C.StartPoint,
C.Length,
SpaceRemaining = B.TotalSize - ISNULL(C.StartPoint + C.Length, 0)
FROM B
LEFT JOIN
( SELECT C.MinorID,
C.StartPoint,
C.Length,
RowNumber = ROW_NUMBER() OVER(PARTITION BY C.MinorID ORDER BY C.StartPoint DESC, Length DESC)
FROM C
) C
ON B.MinorID = c.MinorID
AND C.Rownumber = 1;
Examples on SQL Fiddle
SELECT B.MajorId, B.MinorId, B.totalSize-(C.length+C.startPoint) as Space
from TABLEB B
LEFT JOIN (SELECT MAX(startPoint) maxSP,majorid, minorid FROM TABLEC GROUP BY MajorId, MinorId)
mxT ON B.majorID = mxT.majorID AND B.minorId=mxt.minorId
LEFT JOIN TABLEC C on C.majorid=mxt.MajorId AND C.minorId=mxt>MinorId AND C.startPoint=mxT.maxSP