Filling missing records with previous existing records - sql-server

I have an existing database where some logic is made by the front end application.
Now I have to make reports from that database and I'm facing to a proble of missing records which are covered on a record basis in the frontend but have issues in the report
Given the following tables:
create table #T (id int, id1 int, label varchar(50))
create table #T1 (id int, T_id1 int, A int, B int, C int)
With the values:
insert into #T values (10, 1, 'label1'), (10, 2, 'label2'), (10, 3, 'label3'), (10, 15, 'label15'), (10, 16, 'label16'), (20, 100, 'label100'), (20, 101, 'label101')
insert into #T1 values (10, 1, 100, 200, 300), (10, 15, 150, 250, 350), (20, 100, 151, 251, 351), (20, 101, 151, 251, 351)
if I make a report we can see some missing records:
select #T.id, #T.id1, #T1.A, #T1.B, #T1.C
from #T left join #T1 on #T.id1 = #T1.T_id1
result:
id id1 A B C
10 1 100 200 300
10 2 NULL NULL NULL
10 3 NULL NULL NULL
10 15 150 250 350
10 16 NULL NULL NULL
20 100 151 251 351
20 101 151 251 351
Expected result would be:
id id1 A B B
10 1 100 200 300
10 2 100 200 300
10 3 100 200 300
10 15 150 250 350
10 16 150 250 350
20 100 151 251 351
20 101 151 251 351
As you can see here the missing data is filled out of the the first (in id, id1 order) previous existing record for a given id. For a given id there can be any number of "missing" records and for the given id there can be any number of existing records after a not existing ones.
I can do this with a cursor but I'm looking for a solution without cursor

You can use subquery (to find groups with same values) + window function
WITH Grouped AS (
SELECT #T.id, #T.id1, #T1.A, #T1.B, #T1.C,
GroupN = SUM(CASE WHEN #T1.A IS NULL THEN 0 ELSE 1 END) OVER(/* PARTITION BY id ? */ ORDER BY id1 ROWS UNBOUNDED PRECEDING)
FROM #T
LEFT JOIN #T1 ON #T.id1 = #T1.T_id1
)
SELECT Grouped.id, Grouped.id1,
A = MAX(A) OVER(PARTITION BY GroupN),
B = MAX(B) OVER(PARTITION BY GroupN),
C = MAX(C) OVER(PARTITION BY GroupN)
FROM Grouped

You can use below sql for thedesired output:
with cte (id, id1, A, B, C)
as
(
select #T.id, #T.id1, #T1.A, #T1.B, #T1.C
from #T left join #T1 on #T.id1 = #T1.T_id1
)
select cte.id, cte.id1,
coalesce(cte.A,TT.A) A,
coalesce(cte.B,TT.B) B,
coalesce(cte.C,TT.C) C
from cte
left join
(
select p.id1,max(q.id1) id1_max
from cte p
inner join cte q on p.id1 > q.id1 and p.a is null and q.a is not null
group by p.id1
) T on cte.id1 = T.id1
left join cte TT on T.id1_max = TT.id1

Related

How to find only the top 3 values of a column and group the rest of the column values as zero?

Consider a table having data as shown. I want to find the top 3 marks and combine the rest of the values of column marks as a single value 0.
name age marks height
-----------------------------
anil 25 67 5
ashish 23 75 6
ritu 22 0 4
manish 25 0 6
kirti 23 97 5
Output
name age marks height
-----------------------------
kirti 23 97 5
ashish 23 75 6
anil 25 67 5
OTHERS 0 0 0
With TOP 3 and UNION ALL for the last row:
select t.* from (
select top 3 * from tablename
order by marks desc
) t
union all
select 'OTHERS', 0, 0, 0
See the demo.
Results:
> name | age | marks | height
> :----- | --: | ----: | -----:
> kirti | 23 | 97 | 5
> ashish | 23 | 75 | 6
> anil | 25 | 67 | 5
> OTHERS | 0 | 0 | 0
I would use a CTE (Common Table Expression) and the ROW_NUMBER() function:
;WITH cte AS (SELECT
[Name],
Age,
Marks,
Height,
ROW_NUMBER() OVER (ORDER BY Marks DESC) AS [Rank]
FROM
Test
)
SELECT
[Name],
Age,
Marks,
Height
FROM
cte
WHERE
[Rank] <= 3
UNION ALL SELECT 'OTHERS', 0, 0, 0
You can use select top 3 or row_number()
You can use row_number() as follows
declare #mytable as table(name varchar(50),age int,marks int,height int)
insert into #mytable values('anil', 25, 67, 5),('ashish', 23, 75, 6),('ritu', 22, 0, 4),('manish', 25, 0, 6),('kirti', 23, 97, 5),
('other',0,0,0);
with cte as(
select name,age,marks,height,row_number() over(partition by 1 order by marks desc) row# from #mytable )
select name,age,marks,height from cte where row#<4 or name='other'
order by row#
Another way, using union without inserting ('other',0,0,0) to the table, you can the same result
declare #mytable as table(name varchar(50),age int,marks int,height int)
insert into #mytable values('anil', 25, 67, 5),('ashish', 23, 75, 6),('ritu', 22, 0, 4),('manish', 25, 0, 6),('kirti', 23, 97, 5)
--,('other',0,0,0)
;
with cte as(
select name,age,marks,height,row_number() over(partition by 1 order by marks desc) row# from #mytable )
select name,age,marks,height,row# from cte where row#<4
union select 'others',0,0,0,4
order by row#

Remove duplication from sql query

Trying to select the total delivery from each store where the status is 100, some cases one repair number having 2 100(status delivery). how i can remove all the duplicated from selection even no need one means if its duplicated should cancel that repair from counting. kindly check my code below that's what i reach now.
SELECT UL.StoreName, COUNT(DISTINCT JT.REPAIRNO) AS TotalDelivery
FROM DataDetails AS UL LEFT OUTER JOIN
JOBTRACKING AS JT ON UL.storeID = JT.store_code
WHERE (CAST(JT.created_Date AS date)='2017-03-08')
AND JT.JOBSTATUS=100
GROUP BY UL.StoreName
for example
Name TotalDelivery
ABC 4
XYZ 4
this one come from
RepairNo Store Status CreatedDate
1000 ABC 100 3/8/2017
1001 ABC 100 3/8/2017
1001 ABC 100 3/8/2017
1008 ABC 100 3/8/2017
1009 ABC 100 3/8/2017
1011 XYZ 100 3/8/2017
1011 XYZ 100 3/8/2017
1013 XYZ 100 3/8/2017
1014 XYZ 100 3/8/2017
1015 XYZ 100 3/8/2017
1015 XYZ 100 3/8/2017
need the result as below
Name TotalDelivery
ABC 3
XYZ 2
it will return all the rows and removes duplication but it will return one from duplicate , i want to remove that one also. only a row those dont have any duplucates. thanks in advance.
If you want the non-duplicate results, you need to use SUB QUERY clause to filter them out. Try the below query.
Updated
SELECT UL.StoreName, COUNT(1) AS TotalDelivery
FROM DataDetails AS UL
LEFT OUTER JOIN JOBTRACKING AS JT
ON UL.storeID = JT.store_code
WHERE CAST(JT.created_Date AS date)='2017-03-08'
AND JT.JOBSTATUS=100
AND JT.REPAIRNO IN (SELECT REPAIRNO from JOBTRACKING j WHERE j.store_code = UL.storeID GROUP BY j.REPAIRNO HAVING COUNT(1) = 1)
GROUP BY UL.StoreName, UL.storeID
Test Script
CREATE TABLE #DataDetails
(
StoreName CHAR(3), storeID int
)
CREATE TABLE #JOBTRACKING
(
store_code int, REPAIRNO INT, JOBSTATUS INT, created_Date DATE
)
INSERT #DataDetails VALUES( 'ABC', 1), ('XYZ', 2)
INSERT #JOBTRACKING VALUES (1, 1000, 100, '2017-03-08'), (1, 1001, 100, '2017-03-08'), (1, 1001, 100, '2017-03-08'), (1, 1008, 100, '2017-03-08'), (1, 1009, 100, '2017-03-08')
,(2, 1011, 100, '2017-03-08'), (2, 1011, 100, '2017-03-08'), (2, 1013, 100, '2017-03-08'), (2, 1014, 100, '2017-03-08'), (2, 1015, 100, '2017-03-08'), (2, 1015, 100, '2017-03-08')
SELECT UL.StoreName, COUNT(1) AS TotalDelivery
FROM #DataDetails AS UL
LEFT OUTER JOIN #JOBTRACKING AS JT
ON UL.storeID = JT.store_code
WHERE CAST(JT.created_Date AS date)='2017-03-08'
AND JT.JOBSTATUS=100
AND JT.REPAIRNO IN (SELECT REPAIRNO from #JOBTRACKING j WHERE j.store_code = UL.storeID GROUP BY j.REPAIRNO HAVING COUNT(1) = 1)
GROUP BY UL.StoreName, UL.storeID
Results
+-----------+---------------+
| StoreName | TotalDelivery |
+-----------+---------------+
| ABC | 3 |
| XYZ | 2 |
+-----------+---------------+
I need more details but as I understand, Firstly you should prepare the total select data list result. For example in the inner select you can group the data and eleminate the all concurrent or redundant data or you can apply where criteria then use outer select and group by GROUP BY UL.StoreName and then you will get the true answer. Do not use distinct !

move multiple columns to rows and one row as column

I have the result of my query in a temp table which looks something like shown below :
CREATE TABLE #temptable
(
productid INT,
date DATE,
Indicator varchar(max),
VendorCode INT,
morning INT,
noon INT,
evening INT
)
insert into #temptable values (101,'8-5-2016', 'High', 202, 0,1,0)
insert into #temptable values (101,'8-6-2016', 'High', 202, 0,0,1)
insert into #temptable values (101,'8-5-2016', 'Low', 202, 0,0,1)
insert into #temptable values (101,'8-6-2016', 'Low', 202, 0,0,1)
insert into #temptable values (101,'8-5-2016', 'Avg', 202, 1,0,1)
insert into #temptable values (101,'8-6-2016', 'Avg', 202, 0,0,1)
select * from #temptable
I need the output to look something like this :
I looked at using pivots but looks like that works only with aggregates ? Is there an easy way to do this ?
You can get the result you want by first applying the unpivot operator, and then pivot the result:
select
productid, VendorCode, date, time, Low, High, Avg
from (
select productid, VendorCode, date, time, Indicator, val
from #temptable
unpivot (val for time in ([morning],[noon],[evening])) u
) t
pivot (max(val) for indicator in ([Low],[High],[Avg])) p
order by
productid, VendorCode, date,
case time
when 'Morning' then 1
when 'Noon' then 2
when 'Evening' then 3
end
The case expression at the end of the order by clause makes sure the result is ordered correctly (Morning, Noon, Evening).
First do UNPIVOT then PIVOT
SELECT
piv.productid ,
piv.date ,
piv.VendorCode ,
piv.Tmp AS [Time],
piv.Low ,
piv.High ,
piv.Avg
from
(
select *
from #temptable
unpivot
(
[Time]
for [Tmp] in (morning, noon, evening)
) u
) src
pivot
(
min([Time])
for Indicator in ([Low], [High], [Avg])
) piv
Result:
productid date VendorCode Time Low High Avg
101 2016-08-05 202 evening 1 0 1
101 2016-08-05 202 morning 0 0 1
101 2016-08-05 202 noon 0 1 0
101 2016-08-06 202 evening 1 1 1
101 2016-08-06 202 morning 0 0 0
101 2016-08-06 202 noon 0 0 0

Using SQL unpivot on two groups

I have a table as below:
Name ValueA1 ValueA2 ValueA3 ValueB1 ValueB2 ValueB3 QtyA1 QtyA2 QtyA3 QtyB1 QtyB2 QtyB3
John 1 2 3 4 5 6 100 200 300 150 250 350
Dave 11 12 13 14 15 16 100 200 300 150 250 350
I am able to use unpivot to get the values:
select [Name]
, Replace(u.[Period],'Value','') as [Period]
, u.[Value]
from Table1
unpivot
(
[Value]
for [Period] in ([ValueA1], [ValueA2], [ValueA3], [ValueB1], [ValueB2], [ValueB3])
) u;
SQL Fiddle
However I'm trying to get both the Value and Qty columns on a single row, what I want to end up with is:
Name Number Value Qty
John A1 1 100
John A2 2 200
John A3 3 300
John B1 4 150
John B2 5 250
John B3 6 350
Dave A1 11 100
Dave A2 12 200
Dave A3 13 300
Dave B1 14 150
Dave B2 15 250
Dave B3 16 350
What I have so far is (which doesn't work at all):
select [Name]
, Replace(u.[Period],'Value','') as [Period]
, u.[Value]
, u2.[Value]
from Table1
unpivot
(
[Value]
for [Period] in ([ValueA1], [ValueA2], [ValueA3], [ValueB1], [ValueB2], [ValueB3])
) u
unpivot
(
[Qty]
for [Period] in ([QtyA1], [QtyA2], [QtyA3], [QtyB1], [QtyB2], [QtyB3])
) u2;
Is what I am trying to do even possible with unpivot?
You can use a simple apply for this by specifying the pairs in a values clause:
declare #table table (Name varchar(10), ValueA1 int, ValueA2 int, QtyA1 int, QtyA2 int);
insert into #table
select 'John', 1, 2, 100, 200 union all
select 'Dave', 11, 12, 100, 200;
select Name, Number, Value, Qty
from #table
cross
apply ( values
('A1', ValueA1, QtyA1),
('A2', ValueA2, QtyA2)
) c (number, value, qty);
If you're using an older edition of MSSQL, you might need to use this
instead of the values clause above:
cross
apply ( select 'A1', ValueA1, QtyA1 union all
select 'A2', ValueA2, QtyA2
) c (number, value, qty);
Returns:
Name Number Value Qty
John A1 1 100
John A2 2 200
Dave A1 11 100
Dave A2 12 200

How to get this sql table from other table?

Is there a way to have something like:
id Name value
--------------------
1 sex m
2 age 12
3 weight 200
4 height 200
5 rx 34
from a known table:
sex age weight height rx
--------------------------
m 12 200 200 34
If I do:
Select
[id] = ORDINAL_POSITION,
[Name] = COLUMN_NAME
from INFORMATION_SCHEMA.COLUMNS
where TABLE_NAME = 'known'
I get:
id Name
-----------
1 sex
2 age
3 weight
4 height
5 rx
how to changethe query to get:
id Name value
--------------------
1 sex m
2 age 12
3 weight 200
4 height 200
5 rx 34
If they were 2 rows:
sex age weight height rx
--------------------------
m 12 200 200 34
f 34 245 111 67
id Name value
--------------------
1 sex m
2 age 12
3 weight 200
4 height 200
5 rx 34
6 sex f
7 age 34
8 weight 240
9 height 111
10 rx 67
-----------------EDIT--------------------
Thanks for your answers, but I am wondering if this can be possible intead of getting
id value
-------------------
1 m
2 12
3 200
4 200
5 34
from:
sex age weight height rx
--------------------------
m 12 200 200 34
using
Select
[id] = ORDINAL_POSITION,
[Value] ...
from INFORMATION_SCHEMA.COLUMNS
where TABLE_NAME = 'known'
The problem you're going to run into whatever method you choose is that all data in a particular column must be of the same type. In your case you have sex, which is a character, and a bunch of numbers (probably integers). This stops you being able to do this neatly as you cant use UNPIVOT. However there is always a way...
Given this setup code:
CREATE TABLE test(sex char(1), age int, weight int, height int, rx int)
INSERT INTO test
SELECT 'm', 12 , 200, 200, 34
union select 'f',34,245,111,67
You can do this, which is just a small addition to your query:
Select
[id] = ROW_NUMBER() OVER(ORDER BY isc.ORDINAL_POSITION),
[Name] = COLUMN_NAME,
[Value] = CASE LOWER(COLUMN_NAME)
WHEN 'sex' THEN CAST(d.sex AS VARCHAR(20))
WHEN 'age' then CAST(d.age AS VARCHAR(20))
WHEN 'weight' THEN CAST(d.weight AS VARCHAR(20))
WHEN 'height' THEN CAST(d.height AS VARCHAR(20))
WHEN 'rx' THEN CAST(d.rx AS VARCHAR(20))
END
from INFORMATION_SCHEMA.COLUMNS isc
CROSS JOIN dbo.test d
where TABLE_NAME = 'test'
Output:
1 sex m
2 sex f
3 age 12
4 age 34
5 weight 200
6 weight 245
7 height 200
8 height 111
9 rx 34
10 rx 67
You''ll notice this output is in a slightly different order to your own. This is because you have not described any key on your "known" table. If you did have a key on that table, you simply change this line:
[id] = ROW_NUMBER() OVER(ORDER BY isc.ORDINAL_POSITION),
to
[id] = ROW_NUMBER() OVER(ORDER BY d.yourKeyField, isc.ORDINAL_POSITION),
You can do it with PIVOT/UNPIVOT (see Books Online).
You can try this.
declare #T table
(
sex char(1),
age int,
weight int,
height int,
rx int
)
insert into #T values
('m', 12, 200, 200, 34),
('f', 34, 245, 111, 67)
select row_number() over(order by (select 1)) as ID,
T2.X.value('local-name(.)', 'varchar(128)') as Name,
T2.X.value('.', 'varchar(10)') as Value
from (select *
from #T
for xml path(''), type
) as T1(X)
cross apply T1.X.nodes('/*') as T2(X)
This part order by (select 1) makes the assignment of ID's somewhat unpredictable. If you had a primary key (ID int identity) or a datetime to use in order by you could change that to order by ID instead.

Resources