SQL Server Median Function - sql-server

CREATE FUNCTION [dbo].[f_Get_Average_Order_Size_Median]
(
#ITEM char(15)
)
RETURNS decimal(21,6)
AS
BEGIN
SELECT #Median = AVG(1.0 * QTYSHP)
FROM
(
SELECT o.QTYSHP, rn = ROW_NUMBER() OVER (ORDER BY o.QTYSHP), c.c
FROM dbo.tbl AS o
WHERE RQDATE >=DATEADD (mm,-6, GETDATE())
AND PRICE != '0'
AND SALESMN != 'WB'
AND item = #ITEM )
+
SELECT o.QTYSHP, rn = ROW_NUMBER() OVER (ORDER BY o.QTYSHP), c.c
FROM tbl
WHERE RQDATE >=DATEADD (mm,-6, GETDATE())
AND PRICE != '0'
AND SALESMN != 'WB'
AND item = #ITEM
CROSS JOIN (SELECT c = COUNT(*)
FROM dbo.tblS) AS c
WHERE RQDATE >=DATEADD (mm,-6, GETDATE())
AND PRICE != '0'
AND SALESMN != 'WB'
AND item = #ITEM
+
(SELECT c = COUNT(*)
FROM dbo.tblS) AS c
WHERE RQDATE >=DATEADD (mm,-6, GETDATE())
AND PRICE != '0'
AND SALESMN != 'WB'
AND item = #ITEM
) AS x
WHERE rn IN ((c + 1)/2, (c + 2)/2);
#Return = #Median
BEGIN
END
RETURN #Return
END TRANSACTION...
Is this the correct median function? Please correct me ..I'm learning

The median is the value that accumulates 50% of the values (the 50% percentile). So I think the simplest way to do it is:
Count the number of records (let's say this count is 'n')
Select the top n / 2 records (if n is even, round it to the next integer value), sorted by the column that holds the value for which you want to calculate the median. Read the biggest (last) value of this column.
I'm not quite familiar with SQL server, but in MySQL I would do it like this:
set #n = (select count(*) from yourTable);
set #med = ceil(#n / 2);
select yourColumn
from (
select yourColumn
from yourTable
order by yourColumn
limit #med
) as a
order by yourColumn desc
limit 1;

For SQL Server 2005+ you could try this solution:
DECLARE #MyTable TABLE
(
ID INT PRIMARY KEY,
Value NUMERIC(9,2)
);
INSERT #MyTable (ID, Value) VALUES (1, 10);
INSERT #MyTable (ID, Value) VALUES (2, 20);
INSERT #MyTable (ID, Value) VALUES (3, 30);
INSERT #MyTable (ID, Value) VALUES (4, 40);
-- Test #1: 4 rows => AVG(20,30)
SELECT AVG(y.Value) AS Median#1
FROM
(
SELECT *,
ROW_NUMBER() OVER(ORDER BY x.ID ASC) AS RowNumASC,
ROW_NUMBER() OVER(ORDER BY x.ID DESC) AS RowNumDESC
FROM #MyTable x
) y
WHERE y.RowNumASC = y.RowNumDESC
OR y.RowNumASC + 1 = y.RowNumDESC
OR y.RowNumASC - 1 = y.RowNumDESC;
-- End of Test #1
-- Test #2: 5 rows => AVG(30)
INSERT #MyTable (ID, Value) VALUES (5, 50);
SELECT AVG(y.Value) AS Median#2
FROM
(
SELECT *,
ROW_NUMBER() OVER(ORDER BY x.ID ASC) AS RowNumASC,
ROW_NUMBER() OVER(ORDER BY x.ID DESC) AS RowNumDESC
FROM #MyTable x
) y
WHERE y.RowNumASC = y.RowNumDESC
OR y.RowNumASC + 1 = y.RowNumDESC
OR y.RowNumASC - 1 = y.RowNumDESC;
-- End of Test #2
Results:
Median#1
---------
25.000000
Median#2
---------
30.000000

Related

Recursive CTE with partition by column

Below are the table structure
drop table if exists #Transactions
create table #Transactions (TID int, amt int)
insert into #Transactions values(1, 100)
insert into #Transactions values(1, -50)
insert into #Transactions values(1, 100)
insert into #Transactions values(1, -100)
insert into #Transactions values(1, 200)
;WITH y AS
(
SELECT TID, amt, rn = ROW_NUMBER() OVER (ORDER BY TID)
FROM #Transactions
), x AS
(
SELECT TID, rn, amt, rt = amt
FROM y
WHERE rn = 1
UNION ALL
SELECT y.TID, y.rn, y.amt, x.rt + y.amt
FROM x INNER JOIN y
ON y.rn = x.rn + 1
)
SELECT TID, amt, RunningTotal = rt
FROM x
ORDER BY x.rn
OPTION (MAXRECURSION 10000);
This is similar to question recursive cte with running balance
But I need to running balance for each TIds..suppose if I insert to following transaction of TId=2
insert into #Transactions values(2, 100)
insert into #Transactions values(2, -50)
insert into #Transactions values(2, 100)
insert into #Transactions values(2, -100)
insert into #Transactions values(2, 200)
I need to achieve same only in recursive CTE method without lots of modification.. Please suggest a solution
You need to handle TID in your ROW_NUMBER() window function and also CTE JOIN
;WITH y AS
(
SELECT TID, amt, rn = ROW_NUMBER() OVER (PARTITION BY TID -- <= added here
ORDER BY TID)
FROM #Transactions
), x AS
(
SELECT TID, rn, amt, rt = amt
FROM y
WHERE rn = 1
UNION ALL
SELECT y.TID, y.rn, y.amt, x.rt + y.amt
FROM x INNER JOIN y
ON y.rn = x.rn + 1
AND y.TID = x.TID -- <= added here
)
SELECT TID, amt, RunningTotal = rt
FROM x
ORDER BY x.rn
OPTION (MAXRECURSION 10000);
Any compelling reason that you must use CTE instead of a simple SUM() with window function ?
Add Partition with TID
FIDDLE DEMO
;WITH y AS
(
SELECT TID, amt, rn = ROW_NUMBER() OVER (PARTITION BY TID ORDER BY TID)
FROM #Transactions
), x AS
(
SELECT TID, rn, amt, rt = amt
FROM y
WHERE rn = 1
UNION ALL
SELECT y.TID, y.rn, y.amt,x.rt + y.amt
FROM x INNER JOIN y
ON y.rn = x.rn + 1 AND x.TID = y.TID
)
SELECT TID, amt, RunningTotal = rt
FROM x
ORDER BY x.TID, x.rn
OPTION (MAXRECURSION 10000);
You dont need recursive CTE. You can simply for a PARTITION BY based approach.
SELECT tid
, AMT
, SUM(amt) OVER(PARTITION BY tid ORDER BY tid
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
) AS RunningTotal
FROM #Transactions
update
Sorry. Just now, went through comments.
If you have to use CTE, go with answer by #Squirrel. If you are fine with window functions, you can use the above approach.

Updating Incremental value to a column in a SQL table for a group of rows

I have a situation where I have to update a incremental value to a column based on the value on the same column from the previous row for the same group of records.
Rule for updating 'COUNT' column is:
For the very 1st row of a particular REFNO,
If Amount 1 = Amount 2 then
COUNT = 1
Else
COUNT = 0
For all other rows (excluding the 1st row) of a particular REFNO:
If Amount 1 = Amount 2 then
COUNT = COUNT from previous row for the same REFNO + 1
Else
COUNT = COUNT from previous row for the same REFNO
So the result should look like below:
Though the sample data which I have shown has only 14 records the actual table I am updating is going to have few million rows in them. So I am searching for a solution which will do a set based update rather than row by row processing !!
You can update from a CTE that uses window functions to calculate the number.
The SQL below first calculates a row_number for the equal amounts.
Then for the others that don't have an equal amount, the maximum of the previous row_number is taken.
WITH CTE AS
(
SELECT *,
(CASE
WHEN [Amount 1] = [Amount 2]
THEN rn
ELSE MAX(rn) OVER (PARTITION BY [REFNO] ORDER BY [ROW ID] ASC ROWS UNBOUNDED PRECEDING)
END) as rnk
FROM (
SELECT
[ROW ID], [REFNO], [Amount 1], [Amount 2], [COUNT],
(CASE
WHEN [Amount 1] = [Amount 2]
THEN ROW_NUMBER() OVER (PARTITION BY [REFNO], IIF([Amount 1] = [Amount 2],0,1) ORDER BY [ROW ID] ASC)
ELSE 0
END) AS rn
FROM PAYMENT
) q
)
UPDATE CTE
SET [COUNT] = rnk;
A test on db<>fiddle here
Try it's
declare #t table (
rowid int identity,
refno int,
amount1 int,
amount2 int
)
insert into #t(refno,amount1,amount2) values (1000000,100,200)
insert into #t(refno,amount1,amount2) values (1000000,250,250)
insert into #t(refno,amount1,amount2) values (1000000,300,300)
insert into #t(refno,amount1,amount2) values (1000000,400,400)
insert into #t(refno,amount1,amount2) values (1000010,400,100)
insert into #t(refno,amount1,amount2) values (1000010,200,100)
insert into #t(refno,amount1,amount2) values (1000010,100,300)
insert into #t(refno,amount1,amount2) values (1000021,400,400)
insert into #t(refno,amount1,amount2) values (1000021,200,100)
insert into #t(refno,amount1,amount2) values (1000032,200,200)
insert into #t(refno,amount1,amount2) values (1000032,300,300)
insert into #t(refno,amount1,amount2) values (1000033,200,100)
insert into #t(refno,amount1,amount2) values (1000033,200,100)
select rowid,refno,amount1,amount2,rw-1 as count
from (
select
row_number() over(partition by amount1,amount2 order by rowid) rw,*
from #t) as src
This code works for that particular set, but no guarantee that there will be a situation when it won't work:
CREATE TABLE #tmp(
RowID INT IDENTITY(1,1),
RefNo INT,
Amount1 INT,
Amount2 INT
)
INSERT INTO #tmp(RefNo,Amount1,Amount2)
SELECT * FROM (VALUES
(100000,100,200),
(100000,250,250),
(100000,300,300),
(100000,400,400),
(100000,400,100),
(100010,200,100),
(100010,100,300),
(100010,400,400),
(100021,200,100),
(100021,200,200),
(100032,300,300),
(100032,200,100),
(100033,200,100),
(100033,200,100)) AS x(a,b,c)
;WITH Try1 AS (SELECT t1.*, [Count] =
CASE WHEN t1.Amount1 != t1.Amount2 AND
(t2.RowId IS NULL OR t2.Amount1 != t2.Amount2) THEN 0
WHEN t1.Amount1 != t1.Amount2 AND t2.Amount1 = t2.Amount2 THEN t2.RowId
WHEN t1.Amount1 = t1.Amount2 AND t2.RowId IS NULL THEN t1.RowId
WHEN t1.Amount1 = t1.Amount2 AND t2.RowId IS NOT NULL THEN t1.RowId
END
, NextRefNo = CASE WHEN t2.RowId IS NULL THEN 1 ELSE 0 END
FROM #tmp AS t1
OUTER APPLY ( SELECT * FROM #tmp AS t2
WHERE t2.RowId = t1.RowID - 1 AND t2.RefNo = t1.RefNo) AS t2)
, Try2 AS (SELECT RowID, RefNo, Amount1, Amount2, [Count]
, NextRefNo = ISNULL(t2.NextRefNo,0)
FROM Try1 AS t1
OUTER APPLY ( SELECT NextRefNo FROM Try1 AS t2
WHERE t2.[Count] > 0 AND t2.NextRefNo = 1
AND t2.RefNo = t1.RefNo ) AS t2)
SELECT RowID, RefNo, Amount1, Amount2
, [Count] = DENSE_RANK() OVER(PARTITION BY RefNo ORDER BY [Count]) - 1 + NextRefNo
FROM Try2
ORDER BY RowID;

Adding columns in a query executed from a stored procedure

I have a query in a stored procedure like below
select x,y from table
and the results will look like below
x y
1 a
1 b
2 a
2 b
3 a
3 b
i need to add a blank column or zeros when the value of x changes like below
x y
1 a
1 b
0 0
2 a
2 b
0 0
3 a
3 b
Can this be done by sql or since i'm using the data for birt reports can this be done with birt?
You need UNION ALL to add the extra rows, you also need to ORDER them, the DENSE_RANK is to get rid of the extra row.
here is how it could be done:
DECLARE #t table(x int, y char(1))
INSERT #t values
(1,'a'),(1,'b'),(2,'a'),
(2,'b'),(3,'a'),(3,'b')
;WITH CTE AS
(
SELECT
2 rn, x,y, x sort from #t
UNION ALL
SELECT
distinct dense_rank() over (order by x desc) rn, 0, '0', x+.1 sort
FROM #t
)
SELECT x,y
FROM CTE
WHERE rn > 1
ORDER BY sort, x
Result:
x y
1 a
1 b
0 0
2 a
2 b
0 0
3 a
3 b
This is working example:
DECLARE #DataSource TABLE
(
[x] TINYINT
,[y] CHAR(1)
);
INSERT INTO #DataSource ([x], [y])
VALUES (1, 'a')
,(1, 'b')
,(2, 'a')
,(2, 'b')
,(3, 'a')
,(3, 'b');
WITH DataSource AS
(
SELECT *
FROM #DataSource
UNION ALL
-- the NULL will be always displayed on the first position
SELECT DISTINCT [x]
,NULL
FROM #DataSource
)
SELECT IIF([Rank] = 1, 0, [x])
,IIF([Rank] = 1, 0, [x])
FROM
(
SELECT ROW_NUMBER() OVER(PARTITION BY [x] ORDER BY [y]) AS [Rank]
,[x]
,[y]
FROM DataSource
) DS
ORDER BY [x]
,[Rank]
Few important notes:
the NULL values for each x will be with rank 1 always
the final result set is sorted by x and rank both
declare #t table (X varchar(1),Y varchar(1))
insert into #t(X,y) values (1,'A'),
(1,'B'),
(2,'A'),
(2,'B'),
(3,'A'),
(3,'B')
;with CTE As(
select X,Y,ROW_NUMBER()OVER(PARTITION BY X,Y ORDER BY X)RN
from #t
CROSS APPLY
(
values
('',NULL),
('',NULL)
) C(R, N)),
CTE2 AS(
Select CASE WHEN RN > 1 THEN 0 ELSE X END X ,
CASE WHEN RN > 1 THEN CAST(0 AS VARCHAR) ELSE Y END ID
,ROW_NUMBER()OVER(PARTITION BY X ORDER BY (SELECT NULL)) R
FROM CTE
)
select X,ID from cte2 where R <> 2

how to get nulls when number incremented

i have a piece of code looks like this
declare #t table (record int,string varchar(MAX))
insert into #t (record,string)values (1,'ABC')
insert into #t (record,string)values (2,'DEF/123')
insert into #t (record,string)values (3,'GHI/456/XYZ')
i got a query where i can result like this
SELECT record,
RIGHT(LEFT(T.string,Number-1),CHARINDEX('/',REVERSE(LEFT('/' + T.string,number-1))))
FROM
master..spt_values,
#t T
WHERE
Type = 'P' AND Number BETWEEN 1 AND LEN(T.string)+1
AND
(SUBSTRING(T.string,Number,1) = '/' OR SUBSTRING(T.string,Number,1) = '')
getting output
record values
1 ABC
2 DEF
2 123
3 GHI
3 456
3 XYZ
how can i get output like this
record values
1 ABC
1 NULL
1 NULL
2 DEF
2 123
2 NULL
3 GHI
3 456
3 XYZ
it has been asked by some user .i excelled upto here and from there how can i achieve desire output
The idea is to generate a rows of record cross joined to 1,2,3 to produce combination of record with another column numbered 1,2,3, then use that combination to join to your splitted values. You must add a ROW_NUMBER for your splitted values first to be able to join it with the generated combinations.
;WITH CteThree(record, N) AS(
SELECT
t.record,
x.N
FROM (
SELECT DISTINCT record FROM #t
)t
CROSS JOIN(
SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3
)x(N)
),
CteSplitted AS(
SELECT
record,
ROW_NUMBER() OVER(PARTITION BY record ORDER BY Number) AS N,
RIGHT(LEFT(T.string,Number-1),CHARINDEX('/',REVERSE(LEFT('/' + T.string,number-1)))) AS str
FROM master..spt_values v
CROSS JOIN #t T
WHERE
Type = 'P'
AND Number BETWEEN 1 AND LEN(T.string)+1
AND (SUBSTRING(T.string,Number,1) = '/' OR SUBSTRING(T.string,Number,1) = '')
)
SELECT
t.record,
s.str
FROM CteThree t
LEFT JOIN CteSplitted s
ON s.record = t.record
AND s.N = t.N
how about this:
declare #t table (record int,string varchar(MAX));
declare #s char(1) = '/';
WITH counter as (
SELECT MAX(LEN(string)-LEN(REPLACE(string, #s, ''))) lines
) ,
splitter as (
SELECT record, string
, line = 1
, pos = h.pos
, value = CASE WHEN h.pos>0 THEN SUBSTRING(string,1,h.pos) ELSE string END
FROM #t
CROSS APPLY (SELECT CHARINDEX(#s, string) pos ) h
UNION ALL
SELECT record, string
, line = s.line + 1
, pos = CASE WHEN s.pos = 0 THEN 0 ELSE h.pos END
, value = CASE WHEN s.pos = 0 THEN null
WHEN h.pos > 0 THEN SUBSTRING(string,s.pos+1,h.pos-s.pos-1)
ELSE SUBSTRING(string,s.pos+1,99)
END
FROM splitter s
CROSS APPLY (SELECT CHARINDEX(#s, string, s.pos+1) pos ) h
WHERE s.line<=(SELECT lines FROM counter)
)
SELECT *
FROM splitter
ORDER BY record,line
try this
DECLARE #t TABLE
(
record INT ,
string VARCHAR(MAX)
)
INSERT INTO #t
( record, string )
VALUES ( 1, 'ABC' ),
( 2, 'DEF/123' ),
( 3, 'GHI/456/XYZ' );
WITH cte
AS ( SELECT Number = 1
UNION ALL
SELECT Number + 1
FROM cte
WHERE Number <= 100
),
NotNull
AS ( SELECT record ,
RIGHT(LEFT(T.string, Number - 1),
CHARINDEX('/',
REVERSE(LEFT('/' + T.string,
number - 1)))) string ,
ROW_NUMBER() OVER ( PARTITION BY T.record ORDER BY T.record ) AS RN
FROM cte
JOIN #t T ON Number <= ( LEN(T.string) + 1 )
AND SUBSTRING(T.string + '/', Number, 1) = '/'
)
SELECT template.record ,
NotNull.string
FROM ( SELECT *
FROM ( SELECT DISTINCT
RN
FROM NotNull
) AS A
CROSS JOIN ( SELECT Record
FROM NotNull
) AS B
) AS template
LEFT JOIN NotNull ON template.RN = NotNull.RN
AND template.Record = NotNull.Record
try this
declare #t table (record int,string varchar(MAX))
insert into #t (record,string)values (1,'ABC')
insert into #t (record,string)values (2,'DEF/123')
insert into #t (record,string)values (3,'GHI/456/XYZ')
declare #mx int
select #mx= len(string)-len(replace(string,'/','')) from #t
select record,t.c.value('.','varchar(max)') as col2 from
(select record,x=cast('<t>'+replace(left(string+'////////////////////',(len(string)+(#mx-(len(string)-len(replace(string,'/','')))))),'/','</t><t>') +'</t>' as xml) from #t)
a cross apply x.nodes('/t') t(c)

SQL Server get next previous rows from a table based on a date

I have the following SQL Server query and I am trying to get the next and previous row from a given Id. All works great unless there are two/more dates that are the same, then my logic breaks down.
If you set #currentId = 4 then I get back id 7 (it should be id 6) for the prev
If you set #currentId = 6 then I get back id 2 (it should be id 4) for the next
declare #t table (i int, d datetime)
insert into #t (i, d)
select 1, '17-Nov-2009 07:22:13' union
select 2, '18-Nov-2009 07:22:14' union
select 3, '17-Nov-2009 07:23:15' union
select 4, '20-Nov-2009 07:22:18' union
select 5, '17-Nov-2009 07:22:17' union
select 6, '20-Nov-2009 07:22:18' union
select 7, '21-Nov-2009 07:22:19'
--order that I want
select * from #t order by d desc, i
declare #currentId int; set #currentId = 4
--Get Prev
select TOP 1 * from #t where d > (select TOP 1 d from #t where i = #currentId order by d, i desc) order by d, i desc
--Get Next
select TOP 1 * from #t where d < (select TOP 1 d from #t where i = #currentId order by d desc) order by d desc
Can anyone help me work out how to guarantee get the next/prev row based on a given id, Note that it is important that I keep the this order, Date Desc, id ASC
Many thanks
EDIT: It should be noted that this is going to be used for SQL Server 2005.
You were nearly there ... try this instead. Effectively changed the date comparison to be "...-or-equals-to" and telling it not to match the current ID so it doesn't return the same row...
declare #currID int
set #currID = 4
select top 1 *
from (
select *
from #t
where d = (select d from #t where i = #currID)
and i > #currID
union ALL
select *
from #t
where d < (select d from #t where i = #currID)
) as t
order by d desc, i
select top 1 *
from (
select *
from #t
where d = (select d from #t where i = #currID)
and i < #currID
union ALL
select *
from #t
where d > (select d from #t where i = #currID)
) as t
order by d, i desc
Toy can try something like this
declare #t table (i int, d datetime)
insert into #t (i, d)
select 1, '17-Nov-2009 07:22:13' union
select 2, '18-Nov-2009 07:22:14' union
select 3, '17-Nov-2009 07:23:15' union
select 4, '20-Nov-2009 07:22:18' union
select 5, '17-Nov-2009 07:22:17' union
select 6, '20-Nov-2009 07:22:18' union
select 7, '21-Nov-2009 07:22:19'
--order that I want
select * from #t order by d desc, i
declare #currentId int;
set #currentId = 4
SELECT TOP 1 t.*
FROM #t t INNER JOIN
(
SELECT d CurrentDateVal
FROM #t
WHERE i = #currentId
) currentDate ON t.d <= currentDate.CurrentDateVal AND t.i != #currentId
ORDER BY t.d DESC
SELECT t.*
FROM #t t INNER JOIN
(
SELECT d CurrentDateVal
FROM #t
WHERE i = #currentId
) currentDate ON t.d >= currentDate.CurrentDateVal AND t.i != #currentId
ORDER BY t.d
You must be carefull, it can seem that 6 should be both prev and next.

Resources