How to generate batch number in SQL Server - sql

Any idea on how to generate batch numbers into a batch_number column in SQL Server table from 1 to 3 and repeat it again as shown below in the script?
Thanks in advance
Declare #mTestTable table
(
id int,
city varchar(50),
batch_number int
)
--Insert some sample data
Insert into #mTestTable values (1, 'London')
Insert into #mTestTable values (2, 'Karachi')
Insert into #mTestTable values (3, 'New York')
Insert into #mTestTable values (4, 'Melbourne')
Insert into #mTestTable values (5, 'Beijing')
Insert into #mTestTable values (6, 'Tokyo')
Insert into #mTestTable values (7, 'Moscow')
I need to generate batch number from 1 to 3 and repeat it again.
Id City Batch_Number
1 London 1
2 Karachi 2
3 New York 3
4 Melbourne 1
5 Beijing 2
6 Tokyo 3
7 Moscow 1

Use row_number() and some arithmetic:
with toupdate as (
select t.*, 1 + ((row_number() over (order by id) - 1) % 3) as new_batch_no
from testtable
)
update toupdate
set batch_number = new_batch_number;

update #mTestTable
set batch_number = (id-1) % 3 + 1
If the IDs won't be sequential, then one way to do it is using a cursor passing through it. Simply count the rows one by one and update the appropriate batch number.

Related

Find data by multiple Lookup table clauses

declare #Character table (id int, [name] varchar(12));
insert into #Character (id, [name])
values
(1, 'tom'),
(2, 'jerry'),
(3, 'dog');
declare #NameToCharacter table (id int, nameId int, characterId int);
insert into #NameToCharacter (id, nameId, characterId)
values
(1, 1, 1),
(2, 1, 3),
(3, 1, 2),
(4, 2, 1);
The Name Table has more than just 1,2,3 and the list to parse on is dynamic
NameTable
id | name
----------
1 foo
2 bar
3 steak
CharacterTable
id | name
---------
1 tom
2 jerry
3 dog
NameToCharacterTable
id | nameId | characterId
1 1 1
2 1 3
3 1 2
4 2 1
I am looking for a query that will return a character that has two names. For example
With the above data only "tom" will be returned.
SELECT *
FROM nameToCharacterTable
WHERE nameId in (1,2)
The in clause will return every row that has a 1 or a 3. I want to only return the rows that have both a 1 and a 3.
I am stumped I have tried everything I know and do not want to resort to dynamic SQL. Any help would be great
The 1,3 in this example will be a dynamic list of integers. for example it could be 1,3,4,5,.....
Filter out a count of how many times the Character appears in the CharacterToName table matching the list you are providing (which I have assumed you can convert into a table variable or temp table) e.g.
declare #Character table (id int, [name] varchar(12));
insert into #Character (id, [name])
values
(1, 'tom'),
(2, 'jerry'),
(3, 'dog');
declare #NameToCharacter table (id int, nameId int, characterId int);
insert into #NameToCharacter (id, nameId, characterId)
values
(1, 1, 1),
(2, 1, 3),
(3, 1, 2),
(4, 2, 1);
declare #RequiredNames table (nameId int);
insert into #RequiredNames (nameId)
values
(1),
(2);
select *
from #Character C
where (
select count(*)
from #NameToCharacter NC
where NC.characterId = c.id
and NC.nameId in (select nameId from #RequiredNames)
) = 2;
Returns:
id
name
1
tom
Note: Providing DDL+DML as shown here makes it much easier for people to assist you.
This is classic Relational Division With Remainder.
There are a number of different solutions. #DaleK has given you an excellent one: inner-join everything, then check that each set has the right amount. This is normally the fastest solution.
If you want to ensure it works with a dynamic amount of rows, just change the last line to
) = (SELECT COUNT(*) FROM #RequiredNames);
Two other common solutions exist.
Left-join and check that all rows were joined
SELECT *
FROM #Character c
WHERE EXISTS (SELECT 1
FROM #RequiredNames rn
LEFT JOIN #NameToCharacter nc ON nc.nameId = rn.nameId AND nc.characterId = c.id
HAVING COUNT(*) = COUNT(nc.nameId) -- all rows are joined
);
Double anti-join, in other words: there are no "required" that are "not in the set"
SELECT *
FROM #Character c
WHERE NOT EXISTS (SELECT 1
FROM #RequiredNames rn
WHERE NOT EXISTS (SELECT 1
FROM #NameToCharacter nc
WHERE nc.nameId = rn.nameId AND nc.characterId = c.id
)
);
A variation on the one from the other answer uses a windowed aggregate instead of a subquery. I don't think this is performant, but it may have uses in certain cases.
SELECT *
FROM #Character c
WHERE EXISTS (SELECT 1
FROM (
SELECT *, COUNT(*) OVER () AS cnt
FROM #RequiredNames
) rn
JOIN #NameToCharacter nc ON nc.nameId = rn.nameId AND nc.characterId = c.id
HAVING COUNT(*) = MIN(rn.cnt)
);
db<>fiddle

calculation of points for each student

I want to calculate the remaining points.
in calculation formula : total points =TotalPoints - plenty + Bonus
total points 100
eg. 1
st_id
plenty
Bonus
remaining Points
1
5
0
95
1
3
1
93
1
2
0
91
1
3
2
90
2
1
0
99
2
3
1
96
2
2
0
94
first row calculation for st_id 1: 100 -5 +0= 95
second row calculation for st_id1: 95 - 3 +1=93 here i have to take the previous remaining points. and so on...
can i make a function or use cte to calculate remaining point.
or any other solution for this problem.
SQL tables represent unordered sets (technically "multisets"). There is no ordering unless a column specifies the ordering.
Your results depend on an ordering, so let me assume that you have such a column.
The solution is then cumulative sums:
select t.*,
100 - sum(plenty - bonus) over (partition by st_id order by <ordering col>) as remaining
from t;
I am bit confused, how did you get 100 for the first row in formula "first row calculation for st_id 1: 100 -5 +0= 95" and 95 for second formula "st_id1: 95 - 3 +1=93". Finally, I anticipated that, you might have a column named TotalPoint in your table, so, I write a code. Please Check and let me know->
DECLARE #Points table(st_id int,plenty int,Bonus int,TotalPoints int);
INSERT INTO #Points (st_id, plenty,Bonus,TotalPoints) VALUES (1, 5,0,100);
INSERT INTO #Points (st_id, plenty,Bonus,TotalPoints) VALUES (1, 3,1,95);
INSERT INTO #Points (st_id, plenty,Bonus,TotalPoints) VALUES (1, 2,0,91);
INSERT INTO #Points (st_id, plenty,Bonus,TotalPoints) VALUES (1, 3,0,90);
INSERT INTO #Points (st_id, plenty,Bonus,TotalPoints) VALUES (2, 1,0,99);
INSERT INTO #Points (st_id, plenty,Bonus,TotalPoints) VALUES (2, 3,1,96);
INSERT INTO #Points (st_id, plenty,Bonus,TotalPoints) VALUES (2, 2,0,94);
Select *,(TotalPoints-plenty-Bonus) AS RemainingPoint FROM #Points

Find the Range from a Table

I have a table with the following values
id Level Threshold
1 1 5000
2 2 10000
3 3 15000
What i need to achieve is that when i pass 6000 , I need to get Level 1.
12000 Level 2 and 16000 Level 3?
6000 - Level 1
12000 - Level 2
16000 - Level 3
Can someone let me know how this can be achieved?
What I understood from your question is that when user will give 6000 then it should check which value is less than 6000 so it's 5000 and it's level is 1 same as when 12000 so it has two output as 5000 (level1) and 10000 (level2) but you need maximum one so it is 10000 (Level2). So according to this understanding the query is :
select max(LEVEL) from Table where Threshold< 6000;
How about an SQL question like this?
SELECT One.Level, One.Threshold
FROM
TableName AS One,
(SELECT MAX(Threshold) AS Maximum FROM TableName WHERE Threshold <= :value) AS Two
WHERE One.Threshold = Two.Maximum
Replace :value with 6000, 12000, 16000 or whatever value you are interested in. The inner query finds the maximum threshold that the value has reached. The outer query returns the level number for threshold.
Disclaimar: I have not tested this.
Try Below example, might be you want below one
create table #temp (id int, value int)
insert into #temp values (1, 6000)
insert into #temp values (2, 12000)
insert into #temp values (3, 15000)
insert into #temp values (4, 16000)
select * from #temp
select id, ceiling(convert(float,value)/6000) as level, value from #temp
Fiddle Demo
create table temp (id int, level int, Threshold int);
insert into temp values (1,1, 5000);
insert into temp values (2,2, 10000);
insert into temp values (3,3, 15000);
select max(LEVEL) from temp where Threshold<= 8000; (8000 or any other value)

Recursive Teradata Query

I'm trying to query the below table into a consolidated and sorted list, such as:
Beginning list:
GROUP_ID MY_RANK EMP_NAME
1 1 Dan
1 2 Bob
1 4 Chris
1 3 Steve
1 5 Cal
2 1 Britt
2 2 Babs
2 3 Beth
3 1 Vlad
3 3 Eric
3 2 Mike
Query Result:
1 Dan, Bob, Steve, Chris, Cal
2 Britt, Babs, Beth
3 Vlad, Mike, Eric
It needs to use a recursive query because the list is much longer. Also, I have to sort by my_rank to get them in sequential order. Thanks in advance. I've tried about 10 examples found on different forums, but I'm stuck. Also, don't worry about truncating the any trailing/leading commas.
CREATE TABLE MY_TEST (GROUP_ID INTEGER NOT NULL, MY_RANK INTEGER NOT NULL, EMP_NAME VARCHAR(18) NOT NULL);
INSERT INTO MY_TEST VALUES (1, 1, 'Dan');
INSERT INTO MY_TEST VALUES (1, 2, 'Bob');
INSERT INTO MY_TEST VALUES (1, 4, 'Chris');
INSERT INTO MY_TEST VALUES (1, 3, 'Steve');
INSERT INTO MY_TEST VALUES (1, 5, 'Cal');
INSERT INTO MY_TEST VALUES (2, 1, 'Britt');
INSERT INTO MY_TEST VALUES (2, 2, 'Babs');
INSERT INTO MY_TEST VALUES (2, 3, 'Beth');
INSERT INTO MY_TEST VALUES (3, 1, 'Vlad');
INSERT INTO MY_TEST VALUES (3, 3, 'Eric');
INSERT INTO MY_TEST VALUES (3, 2, 'Mike');
What's your Teradata release? Are XML-Services installed?
SELECT * FROM dbc.FunctionsV
WHERE FunctionName = 'XMLAGG';
If this function exists you can avoid recursion (which is not very efficient anyway):
SELECT GROUP_ID,
TRIM(TRAILING ',' FROM
CAST(XMLAGG(EMP_NAME || ',' ORDER BY MY_RANK) AS VARCHAR(10000)))
FROM MY_TEST
GROUP BY 1
Something like this should work:
WITH RECURSIVE employees(Group_ID , Employees, MY_RANK) AS
(
--Recursive Seed
SELECT
GROUP_ID,
--Cast as a big fat varchar so it can hold all of the employees in a list.
CAST(EMP_NAME as VARCHAR(5000)),
--We need to include MY_RANK so we can choose the next highest rank in the recursive term below.
MY_RANK
FROM
MY_TEST
WHERE
--filter for only my_rank = 1 because that's where we want to start
MY_RANK = 1
UNION ALL
--Recursive Term
SELECT
employees.GROUP_ID,
employees.EMP_NAME || ', ' || MY_TEST.EMP_NAME,
MY_TEST.MY_RANK
FROM
employees
INNER JOIN MY_TEST ON
--Joining on Group_id and Rank+1
employees.GROUP_ID = MY_TEST.GROUP_ID AND
employees.MY_RANK + 1 = MY_TEST.MY_RANK
)
SELECT GROUP_ID, Employees FROM employees;

SQL: Test if Number Contains Digits of Another Number

I'm interested in using SQL to test if one number, stored in column DOW, contains the digits of another number, also stored in DOW, regardless of separation by other digits. Here are the current numbers with which I'm working, although I may have more to deal with in the future:
23
236
1234
12346
123456
67
If the query checks 123456 against 236, it needs to return true. Vice versa, 236 against 123456 returns false. Another example is 1234 returns true when checked against 23, but 67 returns false when checked against 12346. If I've not provided enough information in this question, please ask for clarification.
Simplified Version of my Query:
SELECT t1.DOW, t2.DOW
FROM table t1, table t2
WHERE /* t2.DOW contains all digits regardless of separation in t1.DOW */
Thanks!
I assume DOW is Day of Week and only has numbers 1-7
SELECT T1.DOW,T2.DOW
FROM T1,T2
WHERE NOT EXISTS(SELECT *
FROM (VALUES('%1%'),
('%2%'),
('%3%'),
('%4%'),
('%5%'),
('%6%'),
('%7%')) Nums(N)
WHERE (T2.DOW LIKE N AND T1.DOW NOT LIKE N))
SQL Server 2008 answer though AFAIK this is standard SQL.
This can be handled as a stored procedure which turns a value into a string and inspects the string for each of the digits in the pattern value.
The guts of the stored procedure would have something like
function has_digits (pattern, value)
string s = string (value)
count = 0
for each char in pattern
if instr (s, char) > 0 // for mysql
count = count + 1
return count == pattern.length()
(The specifics of this greatly depend on which SQL flavor is used.)
This would be used like
SELECT *
FROM sometable
WHERE has_digits (236, somefield);
First, I want to make sure everybody understands that I firmly believe this should be in a stored procedure. Also, that I will probably spend most of tomorrow trying to figure out a suitable punishment for what I'm about to write.
Let's assume we have two tables. The first table contains the digits we want to search for.
CREATE TABLE search_digits (
digit integer NOT NULL
);
INSERT INTO search_digits VALUES (2), (3);
We're going to search for the digits 2 and 3.
The second table contains the values we want to search within.
CREATE TABLE dow_digits (
dow integer NOT NULL,
digit integer NOT NULL,
CONSTRAINT dow_digits_pkey PRIMARY KEY (dow, digit),
);
INSERT INTO dow_digits VALUES (23, 2);
INSERT INTO dow_digits VALUES (23, 3);
INSERT INTO dow_digits VALUES (236, 2);
INSERT INTO dow_digits VALUES (236, 3);
INSERT INTO dow_digits VALUES (236, 6);
INSERT INTO dow_digits VALUES (1234, 1);
INSERT INTO dow_digits VALUES (1234, 2);
INSERT INTO dow_digits VALUES (1234, 3);
INSERT INTO dow_digits VALUES (1234, 4);
INSERT INTO dow_digits VALUES (12346, 1);
INSERT INTO dow_digits VALUES (12346, 2);
INSERT INTO dow_digits VALUES (12346, 3);
INSERT INTO dow_digits VALUES (12346, 4);
INSERT INTO dow_digits VALUES (12346, 6);
INSERT INTO dow_digits VALUES (123456, 1);
INSERT INTO dow_digits VALUES (123456, 2);
INSERT INTO dow_digits VALUES (123456, 3);
INSERT INTO dow_digits VALUES (123456, 4);
INSERT INTO dow_digits VALUES (123456, 5);
INSERT INTO dow_digits VALUES (123456, 6);
INSERT INTO dow_digits VALUES (67, 6);
INSERT INTO dow_digits VALUES (67, 7);
We can find at least some of the values for dow that contain the digits 2 and 3 with a simple query.
select d1.dow from dow_digits d1
inner join search_digits d2 on d1.digit = d2.digit
group by dow
having count(distinct d1.digit) = (select count(distinct digit)
from search_digits);
dow
--
23
236
1234
12346
123456
That seems to work. It's not clear what the OP expects if the search integer is 233, so I'm going to ignore that case for now. I want to finish this quickly, then step in front of a truck.
The next question is, can we build search_digits on the fly? In PostgreSQL, sort of.
SELECT UNNEST(ARRAY[2,3]) as digit;
digit
--
2
3
Drop the table search_digits, and wrap that in a CTE.
with search_digits as (
select unnest(array[2,3]) as digit
)
select d1.dow from dow_digits d1
inner join search_digits d2 on d1.digit = d2.digit
group by dow
having count(distinct d1.digit) = (select count(distinct digit)
from search_digits);
dow
--
23
236
1234
12346
123456
Next question. Can we build dow_digits on the fly? In PostgreSQL, sort of. Need to know how many digits in the longest number. Let's say no more than six.
select dow, digit
from (select dow, unnest(array[substring((dow)::text from 1 for 1),
substring((dow)::text from 2 for 1),
substring((dow)::text from 3 for 1),
substring((dow)::text from 4 for 1),
substring((dow)::text from 5 for 1),
substring((dow)::text from 6 for 1)]) digit
from dow ) d
where d.digit <> '';
dow digit
--
23 2
23 3
236 2
236 3
236 6
1234 1
1234 2
1234 3
1234 4
12346 1
12346 2
12346 3
12346 4
12346 6
123456 1
123456 2
123456 3
123456 4
123456 5
123456 6
67 6
67 7
233 2
233 3
233 3
Pulling all that together into a single statement . . .
with search_digits as (
select unnest(array[1,2,3,4,6]) digit
)
select dow
from (select dow, digit
from (select dow, unnest(array[substring((dow)::text from 1 for 1),
substring((dow)::text from 2 for 1),
substring((dow)::text from 3 for 1),
substring((dow)::text from 4 for 1),
substring((dow)::text from 5 for 1),
substring((dow)::text from 6 for 1)]) digit
from dow
) arr
where arr.digit <> ''
) d
inner join (select distinct digit from search_digits) sd
on sd.digit = d.digit::integer
group by dow
having count(distinct d.digit) = (select count(distinct digit)
from search_digits)
dow
--
12346
123456
Oh, I can feel karma points slipping away . . . where's that truck?
For MySQL only:
CREATE TABLE num --- a help table
( i INT PRIMARY KEY
) ;
INSERT INTO num
VALUES
(1), (2), (3), (4)
, (5), (6), (7), (8)
, (9),(10),(11),(12)
,(13),(14),(15),(16)
,(17),(18),(19),(20) ; --- maximum number of digits = 20
Then, you can use this (horrible) query, which first converts numbers to strings and add % between each digit (23 would be converted to '%2%3%') and then tests WHERE 23456 LIKE '%2%3% to match if 23456 contains 23:
SELECT t1.DOW
, t2.DOW
, (t1.DOW LIKE test) AS t1_contains_t2
FROM t1
JOIN
( SELECT t2.DOW
, CONCAT( '%'
, GROUP_CONCAT( SUBSTRING( t2.DOW, num.i, 1 )
ORDER BY num.i
SEPARATOR '%' )
, '%'
) AS test
FROM t2
JOIN num
ON (SUBSTRING(t2.DOW,num.i,1) != "" )
GROUP BY t2.DOW
) AS temp ;
The t1_contains_t2 column will have 1 or 0 depending on whether t1.DOW contains t2.DOW
Or you can use WHERE (t1.DOW LIKE test) in the query.