Get a random value from a range in MS SQL? - sql-server-2008

Let suppose my table can have values from 000 to 999 (three digits and less than 1000)
Some of this values are filled. Let's suppose currently my table has
000,002,005,190 (001,004,003,006,..189,191,..,999 can be inserted into table)
and these values are randomly allocated 000 and 002 is in table but 001 is not in table yet.
How can I get the values that I can insert into table yet.

DECLARE #t TABLE
(VALUE CHAR(3))
INSERT #t
VALUES
('000'),('002'),('005'),('190')
;WITH rnCTE
AS
(
SELECT -1 + ROW_NUMBER() OVER (ORDER BY TYPE, number, name) AS rn
FROM master.dbo.spt_values
)
SELECT RIGHT('000' + CAST( rn AS VARCHAR(11)),3)
FROM rnCTE
WHERE NOT EXISTS ( SELECT 1 FROM #t
WHERE VALUE = rn
)
AND rn < 1000
EDIT
This query works by generating the complete list of possible numbers from a system table (master.dbo.spt_values) which is guaranteed to contain more than 1000 rows inside the CTE rnCTE. -1 is added to ROW_NUMBER to have the values start at 0 rather than 1.
The outer query zero pads the numbers for display, returning only those which are not in the source data and are less than 1000.

DECLARE #t TABLE(id INT)
INSERT INTO #t (id)
VALUES
(1),(19),(3)
;WITH numbers AS (
SELECT ROW_NUMBER() OVER(ORDER BY o.object_id,o2.object_id) RN FROM sys.objects o
CROSS JOIN sys.objects o2
), NotExisted AS(
SELECT * FROM numbers WHERE RN NOT IN (SELECT ID FROM #t)
AND RN<1000)
SELECT TOP 1 RN FROM NotExisted ORDER BY NEWID()

You will have to write a T-SQL to first query and find the gaps. There is no ready made SQL that will give you the gaps directly.

Related

SQL Query about percentage selection

I am trying to write a query for a condition:
If >=80 percent (4 or more rows as 4/5*100=80%) of the top 5 recent rows(by Date Column), for a KEY have Value =A or =B, then change the flag from fail to pass for the entire KEY.
Here is the input and output sample:
I have highlighted recent rows with green colour in the sample.
Can someone help me in this?
I tried till finding the top 5 recent rows by the foll code:
select * from(
select *, row_number() over (partition by "KEY") as 'RN' FROM (
select * from tb1
order by date desc))
where "RN"<=5
Couldnt figure what to be done after this
Test this:
WITH
-- enumerate rows per key group
cte1 AS ( SELECT *,
ROW_NUMBER() OVER (PARTITION BY `key` ORDER BY `date` DESC) rn
FROM sourcetable ),
-- take 5 recent rows only, check there are at least 4 rows with A/B
cte2 AS ( SELECT `key`
FROM cte1
WHERE rn <= 5
GROUP BY `key`
HAVING ( SUM(`value` = 'A') >= 4
OR SUM(`value` = 'B') >= 4 )
-- AND SUM(rn = 5) )
-- update rows with found key values
UPDATE sourcetable
JOIN cte2 USING (`key`)
SET flag = 'PASS';
5.7 version – Ayn76
Convert CTEs to subqueries. Emulate ROW_NUMBER() using user-defined variable.

Mysql - Accumulatively count the total on a row by row basis

I'm trying in MySql to count the number of users created each day and then get an accumulative figure on a row by row basis. I have followed other suggestions on here, but I cannot seem to get the accumulation to be correct.
The problem is that it keeps counting from the base number of 200 and not taking account of previous rows.
Where was I would expect it to return
My Sql is as follows;
SELECT day(created_at), count(*), (#something := #something+count(*)) as value
FROM myTable
CROSS JOIN (SELECT #something := 200) r
GROUP BY day(created_at);
To create the table and populate it you can use;
CREATE TABLE myTable (
id INT AUTO_INCREMENT,
created_at DATETIME,
PRIMARY KEY (id)
);
INSERT INTO myTable (created_at)
VALUES ('2018-04-01'),
('2018-04-01'),
('2018-04-01'),
('2018-04-01'),
('2018-04-02'),
('2018-04-02'),
('2018-04-02'),
('2018-04-03'),
('2018-04-03');
You can view this on SqlFiddle.
Use a subquery:
SELECT day, cnt, (#s := #s + cnt)
FROM (SELECT day(created_at) as day, count(*) as cnt
FROM myTable
GROUP BY day(created_at)
) d CROSS JOIN
(SELECT #s := 0) r;
GROUP BY and variables have not worked together for a long time. In more recent versions, ORDER BY also needs a subquery.

how to select rows of a table in 3th multiply order in sql like 3,6,9 etc

My table consists of 1024 rows and two columns.
I want to select and display rows like 3,4,6,etc..
Is it possible in sql query.
If possible what is the code for that..
In a relational database there is no concept of the "3rd" or "6th" row unless you can define a sort order.
Assuming you do have a column by which you can order the result in order to define a row as "3rd", or "4th", the following will work with Postgres:
select *
from (
select col1, col2, col3,
row_number() over (order by your_sort_column) as rn
from your_table
) t
where rn in (3,4,6);
Where your_sort_column is the one that defines the order or rows. This could be an incremental id or a timestamp column that stores the time of insert or update of the row.
In MySQL it can be accomplished as follows:
SELECT * from
(SELECT (#row := #row + 1) num, tab.* FROM your_table, (SELECT #row := 0) r
ORDER by your_sort_column) t
WHERE (num % 3) = 0
We have used a MODULO operator to get row which is at multiple of 3.
See it working at SQL Fiddle: http://www.sqlfiddle.com/#!2/192b0/4
In PostgreSQL it can be accomplished as follows:
select *
from (
select *, row_number() over (order by your_sort_column) as rn
from your_table
) t
where (rn % 3)=0 ;
http://www.sqlfiddle.com/#!15/cc649/5

How many different ways are there to get the second row in a SQL search?

Let's say I was looking for the second most highest record.
Sample Table:
CREATE TABLE `my_table` (
`id` int(2) NOT NULL AUTO_INCREMENT,
`name` varchar(255) NOT NULL,
`value` int(10),
PRIMARY KEY (`id`)
);
INSERT INTO `my_table` (`id`, `name`, `value`) VALUES (NULL, 'foo', '200'), (NULL, 'bar', '100'), (NULL, 'baz', '0'), (NULL, 'quux', '300');
The second highest value is foo. How many ways can you get this result?
The obvious example is:
SELECT name FROM my_table ORDER BY value DESC LIMIT 1 OFFSET 1;
Can you think of other examples?
I was trying this one, but LIMIT & IN/ALL/ANY/SOME subquery is not supported.
SELECT name FROM my_table WHERE value IN (
SELECT MIN(value) FROM my_table ORDER BY value DESC LIMIT 1
) LIMIT 1;
Eduardo's solution in standard SQL
select *
from (
select id,
name,
value,
row_number() over (order by value) as rn
from my_table t
) t
where rn = 1 -- can pick any row using this
This works on any modern DBMS except MySQL. This solution is usually faster than solutions using sub-selects. It also can easily return the 2nd, 3rd, ... row (again this is achievable with Eduardo's solution as well).
It can also be adjusted to count by groups (adding a partition by) so the "greatest-n-per-group" problem can be solved with the same pattern.
Here is a SQLFiddle to play around with: http://sqlfiddle.com/#!12/286d0/1
This only works for exactly the second highest:
SELECT * FROM my_table two
WHERE EXISTS (
SELECT * FROM my_table one
WHERE one.value > two.value
AND NOT EXISTS (
SELECT * FROM my_table zero
WHERE zero.value > one.value
)
)
LIMIT 1
;
This one emulates a window function rank() for platforms that don't have them. It can also be adapted for ranks <> 2 by altering one constant:
SELECT one.*
-- , 1+COALESCE(agg.rnk,0) AS rnk
FROM my_table one
LEFT JOIN (
SELECT one.id , COUNT(*) AS rnk
FROM my_table one
JOIN my_table cnt ON cnt.value > one.value
GROUP BY one.id
) agg ON agg.id = one.id
WHERE agg.rnk=1 -- the aggregate starts counting at zero
;
Both solutions need functional self-joins (I don't know if mysql allows them, IIRC it only disallows them if the table is the target for updates or deletes)
The below one does not need window functions, but uses a recursive query to enumerate the rankings:
WITH RECURSIVE agg AS (
SELECT one.id
, one.value
, 1 AS rnk
FROM my_table one
WHERE NOT EXISTS (
SELECT * FROM my_table zero
WHERE zero.value > one.value
)
UNION ALL
SELECT two.id
, two.value
, agg.rnk+1 AS rnk
FROM my_table two
JOIN agg ON two.value < agg.value
WHERE NOT EXISTS (
SELECT * FROM my_table nx
WHERE nx.value > two.value
AND nx.value < agg.value
)
)
SELECT * FROM agg
WHERE rnk = 2
;
(the recursive query will not work in mysql, obviously)
You can use inline initialization like this:
select * from (
select id,
name,
value,
#curRank := #curRank + 1 AS rank
from my_table t, (SELECT #curRank := 0) r
order by value desc
) tb
where tb.rank = 2
SELECT name
FROM my_table
WHERE value < (SELECT max(value) FROM my_table)
ORDER BY value DESC
LIMIT 1
SELECT name
FROM my_table
WHERE value = (
SELECT min(r.value)
FROM (
SELECT name, value
FROM my_table
ORDER BY value DESC
LIMIT 2
) r
)
LIMIT 1

Reassemble concatenated list with values from lookup table

I have 2 tables, T1 and T2. I want to join these 2 tables and return only 2 rows of data, replacing the integers in Item with their lookup values from T2.
Table T1
Item Date
------ ---------
1;4;5; 3/13/2013
1;2;3; 3/13/2013
Table T2
ID Desc
---- ------
1 Tree
2 Grass
3 Sand
4 Water
5 Bridge
Expected results:
Item Date
------------------ ---------
Tree;Water;Bridge; 3/13/2013
Tree;Grass;Sand; 3/13/2013
First, create a Split function which returns an integer and an order-preserving sequence number. Here is one example:
ALTER FUNCTION dbo.SplitInts
(
#List VARCHAR(MAX),
#Delimiter VARCHAR(32)
)
RETURNS TABLE
AS
RETURN
(
SELECT rn = ROW_NUMBER() OVER (ORDER BY Number),
Item = CONVERT(INT, Item)
FROM (SELECT Number, Item = LTRIM(RTRIM(SUBSTRING(#List, Number,
CHARINDEX(#Delimiter, #List + #Delimiter, Number) - Number)))
FROM (SELECT ROW_NUMBER() OVER (ORDER BY [object_id])
FROM sys.all_objects) AS n(Number)
WHERE Number <= CONVERT(INT, LEN(#List))
AND SUBSTRING(#Delimiter + #List, Number, 1) = #Delimiter
) AS y
);
GO
Then the following query does what you're after:
DECLARE #t1 TABLE
(
Item VARCHAR(MAX),
[Date] DATE -- terrible column name!
);
INSERT #t1 VALUES('1;4;5;','20130313'),('1;2;3;','20130313');
-- please use unambiguous date formats!
DECLARE #t2 TABLE
(
ID INT, -- another bad column name - what kind of ID?
[Desc] VARCHAR(255) -- another bad column name, this is a keyword!
);
INSERT #t2 VALUES(1,'Tree'),(2,'Grass'),
(3,'Sand'),(4,'Water'),(5,'Bridge');
;WITH x AS
(
SELECT t1.Item, Date, t2ID = i.Item, i.rn, n = t2.[Desc]
FROM #t1 AS t1 CROSS APPLY dbo.SplitInts(t1.Item, ';') AS i
INNER JOIN #t2 AS t2 ON i.Item = t2.ID
)
SELECT DISTINCT Item = (
SELECT n + ';' FROM x AS x2
WHERE x.Item = x2.Item
ORDER BY x2.rn FOR XML PATH,
TYPE).value(N'./type()[1]', N'varchar(max)'), [Date]
FROM x;
Strongly recommend you research normalization. A semi-colon-separated list is a terrible way to cram together independent values.