Best combination to satisfy a quantity - sql

I have a list of items with the same article but different quantities, and I want to find different subgroups of items that could satisfy a specific quantity.
UNIT
ITEM
Quantity
1
1
40
2
1
50
3
1
60
4
1
60
5
1
60
The quantity to satisfy is between 110 and 120
I want to find all the combinations of units that can reach the sum between 110 and 120.
My solution, for now, is to use sql programming, but don't know if can be done with a query.
declare #units table ( id int,Item int,Quantity int)
insert into #units
values
(1 ,1 ,40),
(2 ,1 ,50),
(3 ,1 ,60),
(4 ,1 ,60),
(5 ,1 ,60)
DECLARE #TEMP TABLE ( ID INT, QUANTITY INT , RANKc INT)
DECLARE #TEMP2 TABLE ( Rmin INT, Rmax INT , sumQ INT)
INSERT INTO #TEMP
SELECT
Id,
Quantity,
row_number() over (order by Quantity asc) as rank_c
FROM #units
where Item=1
select * from #TEMP
DECLARE #RMIN INT =1
DECLARE #RMax INT =2
DECLARE #MaxQ int=120
DECLARE #MinQ int=110
--DECLARE #Count int=1
DECLARE #SumQuantity int
WHILE (#RMax<=(SELECT MAX(RANKc) FROM #TEMP))
BEGIN
select #SumQuantity=sum(QUANTITY) from #TEMP where RANKc between #RMIN and #RMax
if (#SumQuantity between #MinQ and #MaxQ)
begin
insert into #TEMP2 values
(#RMIN,#RMax,#SumQuantity)
set #RMIN=#RMIN+1
set #RMax=#RMIN+1
--break;
end
IF (#SumQuantity < #MinQ)
begin
set #RMax=#RMax+1
end
if (#SumQuantity>#MaxQ)
begin
set #RMIN=#RMIN+1
set #RMax=#RMIN+1
end
END
select * from #TEMP2
Rmin
Rmax
sumQ
2
3
110
3
4
120
4
5
120

Related

PARTITION based on a column and GROUP BY another column [duplicate]

This question already has answers here:
Get top 1 row of each group
(19 answers)
Select top 10 records for each category
(14 answers)
Closed 6 months ago.
Please consider the below script:
declare #tbl Table
(
CustomerId INT,
CountryID int,
Amount int
);
insert into #tbl values
(1,1,100),
(1,2,200),
(1,3,300),
(1,4,400),
(2,1,800),
(2,1,1000),
(3,1,500),
(2,4,200),
(2,3,900),
(3,1,3000),
(5,1,100),
(5,2,200),
(5,4,5000),
(6,1,1000),
(6,3,900),
(7,2,400),
(8,3,4000),
(2,1,100),
(1,1,100)
Declare #Result Table
(
CountryID int,
CustomerID int,
SumAmount int
);
Declare #CountryID int;
DECLARE db_cursor CURSOR FOR
SELECT distinct CountryID
FROM #tbl
OPEN db_cursor
FETCH NEXT FROM db_cursor INTO #CountryID
WHILE ##FETCH_STATUS = 0
BEGIN
insert into #Result
select top 2 #CountryID, CustomerID, SUM(Amount)
from #tbl
where CountryID = #CountryID
group by CustomerId
order by 3 desc
FETCH NEXT FROM db_cursor INTO #CountryID
END
CLOSE db_cursor
DEALLOCATE db_cursor
select *
from #Result
It returns this result :
CountryID CustomerID SumAmount
----------------------------------
1 3 3500
1 2 1900
2 7 400
2 5 200
3 8 4000
3 6 900
4 5 5000
4 1 400
In fact I want to get Top 2 customers that have maximum Amount in each Country.
How can I get that result without CURSOR and single query?
Thanks
The solution is :
WITH T AS
(
SELECT CountryID, CustomerId, SUM(Amount) AS SumAmount,
RANK() OVER(PARTITION BY CountryID ORDER BY SUM(Amount) DESC) AS R
FROM #tbl
GROUP BY CountryID, CustomerId
)
SELECT *
FROM T
WHERE R <= 2
But remember that when you want a top n rank , you will not systematically have exactly n rows returning because of ex aequo... You can have more, you can have less, depending of which ranking function you use and how many equal mesure you are ranking...

Weekends between weekdays SQL

I have a list of weekday's dates and want to insert rows for weekends/public holidays dates and populate data from previous row in SQL. Pls help.
Your holidays depend on what country you are in. This is the logic used for inserting two days after finding a Friday, similar logic can be applied if you know how to identify holidays, and how long they last.
create table #temp (fechas date, value int)
insert into #temp values ('20160601',2)
insert into #temp values ('20160602',4)
insert into #temp values ('20160603',8)
insert into #temp values ('20160606',2)
insert into #temp values ('20160607',1)
--TABLE
select *, DATEPART(DW,fechas) as dayOfTheWk
into #temp2
from #temp
-- selecting Fridays
declare #Fridays TABLE (fechas date,value int, dayOfTheWk int, nRow int)
insert into #Fridays
select *, DATEPART(DW,fechas) as dayOfTheWk, ROW_NUMBER() over(order by fechas) from #temp
where DATEPART(DW,fechas) = 6
declare #i int = 1, #maxI int
select #maxI = count(1) from #Fridays
while(#i <= #maxI)
begin
declare #x int = 1
while (#x <= 2)
begin
insert into #temp2
select
DATEADD(day,#x,fechas) as fechas, value, DATEPART(DW,DATEADD(day,#x,fechas))
from #Fridays
where nRow = #i
select #x += 1
end
select #i += 1
end
select * from #temp2
fechas value dayOfTheWk
2016-06-01 2 4
2016-06-02 4 5
2016-06-03 8 6
2016-06-04 8 7
2016-06-05 8 1
2016-06-06 2 2
2016-06-07 1 3

How to insert records of the range in table when I define the range in first table in SQL Server 2012

Here I have two table, with the name Table A and Table B.
Table A:
ID From To
-------------------
1 985 992
2 1201 1207
3 1584 1589
Table B:
ID Numbers
---------------------------
1 985
2 986
3 987
4 988
5 989
6 990
7 991
8 992
9 1201
10 1202
11 1203
12 1204
13 1205
14 1206
and the number goes like this. And the table structure as well.
How can such kind of data can be insert. As I define range from 125- 135 in table A, all the number with in this range must be inserted at table B.
Thanks to all the well wisher for their valuable suggestion. Answer has been solve with using trigger.
CREATE TRIGGER trgAfterInsert on samplea
FOR INSERT
AS declare #id int, #from bigint, #to bigint, #number bigint;
select #id=i.id from inserted i;
select #from=i.fromnum from inserted i;
select #to=i.tonum from inserted i;
set #number=#from
while #number<=#to
begin
insert into sampleB (id, numbers) values (#id,#number);
set #number=#number+1
end
Finally the problem is solved. With this inserting data range in table A, data will be automatically inserted in table B with this trigger.
You can do it with a cursor and while loops,
DELCARE #Uid int, #Ustart int, #Uend int, #Ucounter;
DECLARE Ucursor CURSOR
FOR SELECT * FROM TableA ;
OPEN vend_cursor
FETCH NEXT FROM Ucursor
INTO #Uid,#Ustart,#Uend
WHILE ##FETCH_STATUS = 0
BEGIN
SET #Ucounter = #Ustart
WHILE #Ucounter <> #Uend
BEGIN
INSERT INTO TableB
VALUES (#Ucount) -- Set the identity on for id
SET #Ucounter += 1
END
FETCH NEXT FROM Ucursor
INTO #Uid,#Ustart,#Uend
END
CLOSE Ucursor;
Not sure if this is efficient but it works.
DECLARE #range INT = (SELECT [To] - [From] FROM #tableA WHERE [Id] = 1)
DECLARE #count INT = 0
WHILE (#count <= #range)
BEGIN
INSERT INTO #tableB
SELECT [From] + #count FROM #tableA
SET #count = #count + 1
END
I would suggest a recursive CTE:
with cte as (
select from as n, from, to
from a
union all
select n + 1, from, to
from cte
where n < to
)
select n
from cte;
To create a table, you can do:
with cte as (
select from as n, from, to
from a
union all
select n + 1, from, to
from cte
where n < to
)
select identity(), n
into b
from cte;
Notes:
I left the column names as you have them without escaping them. Obviously, from and to are keywords in SQL.
If you have a gap of more than 100, you'll want to use the MAXRECURSION option.
You can insert values just as easily as creating a new table.
Try this,
declare #t table(ID int,Froms int,Tos int)
insert into #t values
(1 , 985 , 992 )
,(2 , 1201 , 1207 )
,(3 , 1584 , 1589 )
declare #table2 table(id int identity(1,1),numbers int)
insert into #table2
select number from #t t
cross apply(
select distinct number from master..spt_values
where number>t.[froms] and number<=t.tos)ca
select * from #table2

SQL subset sum negative values

I have a table valued function that return the set of rows that matches a given sum, It works fine with positive values but not with negatives one.
Can someone modify this function to work with both positive and negative values (price field)
The function take a table with decimal values, then return the first combination of rows that match a given sum in the parameter :
For example if the #psum = 9 and the given table below :
n id price
1 1 4.00
2 2 4.00
3 3 5.00
4 4 6.00
5 5 8.00
The out put is :
select * from SubsetSum2(9)
n id price
3 3 5.00
2 2 4.00
alter FUNCTION [dbo].[SubsetSum2](#psum int )
RETURNS #tt table (n int,id int, price numeric(20,2))
AS
BEGIN
declare #t table (n int IDENTITY(1,1), id int, price numeric(20,2))
insert into #t -- note asc order of book prices
select 1, 4 union all
select 2, 4 union all
select 3, 5 union all
select 4, 6 union all
select 5, 8
declare #rows int, #p numeric(20,2), #sum numeric(20,2) set #sum= 9
delete from #t where price>#sum
set #p=(select sum(price) from #t)
if #p>= #sum
begin --1
set #rows=(select max(n) from #t)
declare #n int, #s numeric(20,2)
set #n=#rows+1 set #s=0
while 0=0
begin --2
while #n>1
begin --3
set #n=#n-1
if #s+(select price from #t where n=#n)<=#sum
and #s+(select sum(price) from #t where n<=#n)>=#sum
begin --4
set #s=#s+(select price from #t where n=#n)
insert into #tt select n, id, price from #t where n=#n
if #s=#sum return ;
end --4
end --3
set #n=(select min(n) from #tt)
set #s=#s-(select price from #tt where n=#n)
delete from #tt where n=#n
if #s=0 and (select sum(price) from #t where n<#n)<#sum break
end --2
end --1
return
END
Use Absolute function ABS(Price) for treating the negatives as positives

Fill up column with random values from different table

I have 2 tables. Table Table_View has 5 columns and 200 rows
and Table_Random has 3 columns with 10 rows
I need to poulate column Table_View.A_random with values from
Table_Random.FixValues. If the query reaches the end of row in Table_Random i.e row 10, it should start again with
values from the top row i.e row 1. until it fills up the 200 rows.
Given that all tables has primary keys.
Any Ideas?
Thanks in advance
This will work for any count of rows in destination and source tables.
The idea is to calculate count of rows in random table and then assign number rn % #c to each row in destination table. And then update based on join:
DECLARE #count INT = 21
DECLARE #i INT = 1
DECLARE #c INT = 0
DECLARE #t TABLE ( ID INT, Random INT )
DECLARE #r TABLE ( ID INT, Random INT )
INSERT INTO #r
VALUES ( 1, 10 ),
( 3, 20 ),
( 4, 30 ),
( 6, 40 ),
( 8, 50 ),
( 11, 60 ),
( 14, 70 ),
( 17, 80 ),
( 19, 90 ),
( 21, 100 )
WHILE #i <= #count
BEGIN
INSERT INTO #t
VALUES ( #i, NULL )
SET #i = #i + 1
END;
SELECT #c = COUNT(*)
FROM #r;
WITH ctet1
AS ( SELECT * , ROW_NUMBER() OVER ( ORDER BY ID ) AS rn
FROM #t
),
ctet2
AS ( SELECT * ,
CASE WHEN rn % #c = 0 THEN #c
ELSE rn % #c
END AS rnn
FROM ctet1
),
cter
AS ( SELECT * , ROW_NUMBER() OVER ( ORDER BY ID ) AS rn
FROM #r
)
UPDATE ct
SET Random = cr.Random
FROM ctet2 ct
JOIN cter cr ON cr.rn = ct.rnn
SELECT * FROM #t
Output:
ID Random
1 10
2 20
3 30
4 40
5 50
6 60
7 70
8 80
9 90
10 100
11 10
12 20
13 30
14 40
15 50
16 60
17 70
18 80
19 90
20 100
21 10
If you didn't want cycle update then no need for views, functions and needless stuff. Just update:
UPDATE #t SET Random = (SELECT TOP 1 Random FROM #r ORDER BY NEWID())
yes you can make this out.
First of all you need to create and view which'll return a single value from Random Table(Table_Random) for every call.
Create View vMyRand as
Select top 1 val from myRand order by NewID();
then create a function to return value from created view.
CREATE FUNCTION GetMyRand ()
RETURNS varchar(5)
--WITH EXECUTE AS CALLER
AS
BEGIN
Declare #RetValue varchar(5)
--#configVar =
Select #RetValue = val from vmyRand
RETURN(#retValue)
END;
Fiddle Demo Here
Full Code:
create table tab_1
(
id bigint identity(1,1),
name varchar(50),
email varchar(50)
)
insert into tab_1(name,email) values
('a','a#mail.com'), ('b','c#mail.com'),
('a1','a1#mail.com'), ('a2','a2#mail.com'),
('a3','a3#mail.com'), ('a4','a4#mail.com'),
('b1','b1#mail.com'),('b2','b2#mail.com')
create table myRand(val varchar(50))
insert into myRand values('Q1654'),('F2597'),
('Y9405'),('B6735'),('D8732'),('C4893'),('I9732'),
('L1060'),('H6720');
Create View vMyRand as
Select top 1 val from myRand order by NewID();
CREATE FUNCTION GetMyRand ()
RETURNS varchar(5)
--WITH EXECUTE AS CALLER
AS
BEGIN
Declare #RetValue varchar(5)
--#configVar =
Select #RetValue = val from vmyRand
RETURN(#retValue)
END;
Update Code:
update tab_1 set name=(select dbo.getMyRand())
Hope This'll Help You.
Thanks. :)