I have a table with float values. I want to use a subset (A) of those values to create ranks using PERCENT_RANK(). Then I want to assign ranks to a second (non-intersecting) subset (B) of values in the table based on the ranks derived from the first subset (A). Simply joining on values from (B) to values in (A) won't work since the values from subset (B) in general won't equal values in subset (A). In that case, I'm fine using either a "closest value" approach or a "linear interpolation" approach to get the ranks. My preference is for speed and simplicity since I'm dealing with hundreds of thousands of rows.
Here is a concrete example (assume subset A is where Flag = 0 and subset B is where Flag = 1):
DECLARE #Data TABLE
(
Value FLOAT,
Flag BIT
)
INSERT INTO #Data SELECT 0.081, 0
INSERT INTO #Data SELECT 0.831, 0
INSERT INTO #Data SELECT 0.798, 0
INSERT INTO #Data SELECT 0.722, 0
INSERT INTO #Data SELECT 0.322, 0
INSERT INTO #Data SELECT 0.186, 0
INSERT INTO #Data SELECT 0.494, 0
INSERT INTO #Data SELECT 0.757, 0
INSERT INTO #Data SELECT 0.996, 0
INSERT INTO #Data SELECT 0.146, 0
INSERT INTO #Data SELECT 0.514, 1
INSERT INTO #Data SELECT 0.787, 1
INSERT INTO #Data SELECT 0.125, 1
INSERT INTO #Data SELECT 0.324, 1
INSERT INTO #Data SELECT 0.86, 1
--Subset A
SELECT *,
Rnk = PERCENT_RANK() OVER (ORDER BY Value)
FROM #Data
WHERE Flag = 0
--Subset B
SELECT *,
Rnk = ?--Ranking based on ranks derived from subset A
FROM #Data
WHERE Flag = 1
Hmmm . . . This is one way:
with a as (
select d.*
PERCENT_RANK() OVER (ORDER BY Value) as rnk
from #Data d
where Flag = 0
)
select b.*, a.rnk
from #Data b outer join
(select top 1 *
from a
where a.value <= b.value
order by a.value desc
) a
where Flag = 1;
Related
I have following two tables. I need to merge 2 tables based also sorted on
few conditions.
DROP TABLE IF EXISTS #t1
CREATE TABLE #t1
(
id INT IDENTITY PRIMARY KEY,
value INT
)
DROP TABLE IF EXISTS #t2
CREATE TABLE #t2
(
id INT IDENTITY PRIMARY KEY,
value int
)
INSERT INTO #t1 (value)
VALUES (1), (30), (19), (10), (40);
INSERT INTO #t2 (value)
VALUES (100), (70), (20);
SELECT * FROM #t1
UNION
SELECT * FROM #t2
I need to union 2 tables and sorted value based on below condition
#t1 first row (fixed)
#t1 last row (fixed)
#t1 rest (other than first row) based on value
4 #t2 only sorted based on value
Expected output:
why are you using varchar to store numeric value ? you will need to perform a cast() or convert() before you will be to sort it.
Assumption : by first or last you are referring with row with minimum and maximum value in column id.
select value, seq as [order]
from
(
select id, value,
seq = case when row_number() over(order by id) = 1 then 1
when row_number() over(order by id desc) = 1 then 2
else 3
end
from #t1
union all
select id, value, seq = 4
from #t2
) d
order by seq, convert(int, value)
You can check this below logic of ordering the output using CASE statement in the ORDER clause-
SELECT *
FROM
(
select 'T1' T_Name,* from #t1
union
select 'T2',* from #t2
)A
ORDER BY
CASE
WHEN t_name = 'T1' AND id = (SELECT MIN(ID) FROM #t1) THEN 1
WHEN t_name = 'T2' AND id = (SELECT MAX(ID) FROM #t2) THEN 2
WHEN t_name = 'T1' THEN 3
ELSE 4
END,
Id -- Or you can order by Value. Just keep in mind that Value is VARCHAR as per your setup. So, Ordering will be also impact accordingly.
Let's take an example. These are the rows of the table I want get the data:
The column I'm talking about is the reference one. The user can set this value on the web form, but the system I'm developing must suggest the lowest reference value still not used.
As you can see, the smallest value of this column is 35. I could just take the smaller reference and sum 1, but, in that case, the value 36 is already used. So, the value I want is 37.
Is there a way to do this without a loop verification? This table will grow so much.
This is for 2012+
DECLARE #Tbl TABLE (id int, reference int)
INSERT INTO #Tbl
( id, reference )
VALUES
(1, 49),
(2, 125),
(3, 35),
(4, 1345),
(5, 36),
(6, 37)
SELECT
MIN(A.reference) + 1 Result
FROM
(
SELECT
*,
LEAD(reference) OVER (ORDER BY reference) Tmp
FROM
#Tbl
) A
WHERE
A.reference - A.Tmp != -1
Result: 37
Here is yet another place where the tally table is going to prove invaluable. In fact it is so useful I keep a view on my system that looks like this.
create View [dbo].[cteTally] as
WITH
E1(N) AS (select 1 from (values (1),(1),(1),(1),(1),(1),(1),(1),(1),(1))dt(n)),
E2(N) AS (SELECT 1 FROM E1 a cross join E1 b), --10E+2 or 100 rows
E4(N) AS (SELECT 1 FROM E2 a cross join E2 b), --10E+4 or 10,000 rows max
cteTally(N) AS
(
SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) FROM E4
)
select N from cteTally
Next of course we need some sample data and table to hold it.
create table #Something
(
id int identity
, reference int
, description varchar(10)
)
insert #Something (reference, description)
values (49, 'data1')
, (125, 'data2')
, (35, 'data3')
, (1345, 'data4')
, (36, 'data5')
, (7784, 'data6')
Now comes the magic of the tally table.
select top 1 t.N
from cteTally t
left join #Something s on t.N = s.reference
where t.N >= (select MIN(reference) from #Something)
and s.id is null
order by t.N
This is ugly, but should get the job done:
select
top 1 reference+1
from
[table]
where
reference+1 not in (select reference from [table])
order by reference
I used a table valued express to get the next value. I first left outer joined the table to itself (shifting the key in the join by +1). I then looked only at rows that had no corresponding match (b.ID is null). The minimum a.ReferenceID + 1 gives us the answer we are looking for.
create table MyTable
(
ID int identity,
Reference int,
Description varchar(20)
)
insert into MyTable values (10,'Data')
insert into MyTable values (11,'Data')
insert into MyTable values (12,'Data')
insert into MyTable values (15,'Data')
-- Find gap
;with Gaps as
(
select a.Reference+1 as 'GapID'
from MyTable a
left join MyTable b on a.Reference = b.Reference-1
where b.ID is null
)
select min(GapID) as 'NewReference'
from Gaps
NewReference
------------
13
I hope the code was clearer than my description.
CREATE TABLE #T(ID INT , REFERENCE INT, [DESCRIPTION] VARCHAR(50))
INSERT INTO #T
SELECT 1,49 , 'data1' UNION ALL
SELECT 2,125 , 'data2' UNION ALL
SELECT 3,35 , 'data3' UNION ALL
SELECT 4,1345, 'data4' UNION ALL
SELECT 5,36 , 'data5' UNION ALL
SELECT 6,7784, 'data6'
SELECT TOP 1 REFERENCE + 1
FROM #T T1
WHERE
NOT EXISTS
(
SELECT 1 FROM #T T2 WHERE T2.REFERENCE = T1.REFERENCE + 1
)
ORDER BY T1.REFERENCE
--- OR
SELECT MIN(REFERENCE) + 1
FROM #T T1
WHERE
NOT EXISTS
(
SELECT 1 FROM #T T2 WHERE T2.REFERENCE = T1.REFERENCE + 1
)
How about using a Tally table. The following illustrates the concept. It would be better to use a persisted numbers table as opposed to the cte however the code below illustrates the concept.
For further reading as to why you should use a persisted table, check out the following link: sql-auxiliary-table-of-numbers
DECLARE #START int = 1, #END int = 1000
CREATE TABLE #TEST(UsedValues INT)
INSERT INTO #TEST(UsedValues) VALUES
(1),(3),(5),(7),(9),(11),(13),(15),(17)
;With NumberSequence( Number ) as
(
Select #start as Number
union all
Select Number + 1
from NumberSequence
where Number < #end
)
SELECT MIN(Number)
FROM NumberSequence n
LEFT JOIN #TEST t
ON n.Number = t.UsedValues
WHERE UsedValues IS NULL
OPTION ( MAXRECURSION 1000 )
You could try using a descending order:
SELECT DISTINCT reference
FROM `Resultsados`
ORDER BY `reference` ASC;
As far as I know, there is no way to do this without a loop. To prevent multiple values from returning be sure to use DISTINCT.
Hi I need to write a query to back populate the CurrentQuantity field in the below table. I was originally thinking to use the while loop but I am wondering whether there is a way to write a recursive query to achieve this.
Bascially, the currentQuantity is next available currentQuantity + QuantityChanged of current record. So in this scenario, currentQuantity for ID -11 is CurrentQuantity of ID 20 which is -45 + QuantityChanged of current record which is (-5 * -1) and therefore it becomes -40.
ID column is always incremental so we can make an assumption on this.
I have been trying to write the recursive query using CTE but not sure where to start really... Any help will be highly appreciated !
Not so great because your data is not really related. Managed to do it though - example code:
create table #data (id int, currentQuantity int, quantityChanged int);
insert into #data values (-30, NULL, -10);
insert into #data values (-24, NULL, -10);
insert into #data values (-22, NULL, -10);
insert into #data values (-11, NULL, -5);
insert into #data values (20, -45, -5);
select * from #data;
;with rcte as (
select id, currentquantity, quantityChanged, 0 as Level
from #data where id = 20
union all
select d.id, r.currentquantity + (d.quantityChanged * -1),
d.quantityChanged, level + 1
from #data d inner join rcte r on d.id < r.id
)
update #data
set currentQuantity = r.currentQuantity
from #data d
inner join rcte r
on d.id = r.id
inner join (
select id, row_number() over (order by id desc) - 1 level
from #data
) idl
on r.id = idl.id
and r.level = idl.level;
select * from #data;
That should produce what you are looking for.
NB: If you are using SQL Server > 2008 you could use the lead/lag functions to do this as well.
I have a source table with id and count.
id count
a 5
b 2
c 31
I need to populate a destination table with each integer up to the count for each id.
id value
a 1
a 2
a 3
a 4
a 5
b 1
b 2
c 1
c 2
etc...
My current solution is like so:
INSERT INTO destination (id,value)
source.id
sequence.number
FROM
(VALUES (1),(2),(3),(4),(5),(6),(7),(8),(9)) AS sequence(number)
INNER JOIN
source ON sequence.number <= source.count
This solution has an upper limit and is plain lame. Is there anyway to replace the sequence with a set of all integers? Or another solution that does not use looping.
this should work:
WITH r AS (
SELECT id, count, 1 AS n FROM SourceTable
UNION ALL
SELECT id, count, n+1 FROM r WHERE n<count
)
SELECT id,n FROM r
order by id,n
OPTION (MAXRECURSION 0)
Unfortunately, there is not set of all integers in SQL Server. However, using a little trickery, you can easily generate such a set:
select N from (
select ROW_NUMBER() OVER (ORDER BY t1.object_id) AS N
from sys.all_objects t1, sys.all_objects t2
) AS numbers
where N between 1 and 1000000
will generate a set of all numbers from 1 through 1000000. If you need more than a few million numbers, add sys.all_objects to the cross join a third time.
You can find many examples in this page:
DECLARE #table TABLE (ID VARCHAR(1), counter INT)
INSERT INTO #table SELECT 'a', 5
INSERT INTO #table SELECT 'b', 3
INSERT INTO #table SELECT 'c', 31
;WITH cte (ID, counter) AS (
SELECT id, 1
FROM #table
UNION ALL
SELECT c.id, c.counter +1
FROM cte AS c
INNER JOIN #table AS t
ON t.id = c.id
WHERE c.counter + 1 <= t.counter
)
SELECT *
FROM cte
ORDER BY ID, Counter
I am new to SQL in general and even newer to MS SQL. I apologize if the title isn't clear on what I want.
I have two tables an old one that I want to derive data from into a new table. The tables have the exact same columns but different number of rows. The new table has multiple copies of each value in the old table, which only has 2 occurences. see below comparison of two columns: letter and amount.
New table:
A 0
A 0
A 0
B 0
B 0
old table:
A 12
A 0
B 10
B 0
C 23
What I want to achieve is adding the values of the amount column from the old table into just the first occurence of the leter in the new table like so:
A 12
A 0
A 0
B 10
B 0
Inner join causes all the values to be filled ( so all the A's are set to 12).
try this:
DECLARE #test1 TABLE(col1 varchar(2),idn int)
insert into #test1
VALUES('A',0),
('A',0),
('A',0),
('B',0),
('B',0)
DECLARE #test2 TABLE(col1 varchar(2),idn int)
insert into #test2
VALUES('A',12),
('A',0),
('B',10),
('B',0),
('C',23)
;WITH CTE as (select *,ROW_NUMBER() over (partition by col1 order by col1) as rn from #test1)
update c SET c.idn=b.idn
from CTE c inner join (select col1,SUM(idn) as idn from #test2
group by col1) b
on c.col1 = b.col1
where c.rn=1
select * from #test1
click here to see Demo
declare #t table
(
val varchar(2),
digit int
)
insert into #t(val, digit)values('A', 0)
insert into #t(val, digit)values('A', 0)
insert into #t(val, digit)values('A', 0)
insert into #t(val, digit)values('B', 0)
insert into #t(val, digit)values('B', 0)
declare #t1 table
(
val varchar(2),
digit int
)
insert into #t1(val, digit)values('A', 12)
insert into #t1(val, digit)values('A', 0)
insert into #t1(val, digit)values('B', 10)
insert into #t1(val, digit)values('B', 0)
insert into #t1(val, digit)values('C', 23)
Select k.val, isNull(sum(k.digit + k1.digit), 0) as Digit from
(
Select ROW_NUMBER() over(partition by val order by val) as rowid, * from #t
)K
Left Join
(
Select ROW_NUMBER() over(partition by val order by val) as rowid, * from #t1
)K1
on k.val = k1.val AND K.rowid = K1.rowid
group by k.val, K.rowid