SQL subset sum negative values - sql

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

Related

Best combination to satisfy a quantity

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

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

How to create loop based on value of row?

I have problem when I use my query bellow to have a looping inside the cursor.
data in table1 will be like this:
id | data
----|---------
A | 4
B | 2
C | 5
the result in table2 should be like this:
id | data
----|---------
A | 1
A | 1
A | 1
A | 1
B | 1
B | 1
C | 1
C | 1
C | 1
C | 1
C | 1
I have SQL query with cursor like this:
DECLARE #table2 table ( id VARCHAR(500), data INTEGER)
DECLARE Cur CURSOR FOR
SELECT id, data FROM table1
OPEN Cur
WHILE ( ##FETCH_STATUS = 0 )
BEGIN
DECLARE #LoopNum INTEGER
DECLARE #tempID VARCHAR(255)
DECLARE #tempDATA INTEGER
FETCH NEXT FROM Cur INTO #tempID, #tempDATA
set #LoopNum = 0
WHILE #LoopNum < #tempDATA
BEGIN
INSERT INTO table2 (id, data)
VALUES( #tempID, 1)
SET #LoopNum = #LoopNum + 1
END
END
CLOSE Cur
DEALLOCATE Cur
SELECT * FROM table2
but the query didn't work. is there something wrong with my query?
Thank you.
Use this query to the expected result.
CREATE TABLE #test
(id CHAR(1),data INT)
INSERT #test VALUES ('A',4)
INSERT #test VALUES('B',2)
INSERT #test VALUES('C',5);
SELECT s.id, 1 AS data
FROM #test s
INNER JOIN
master.dbo.spt_values t ON t.type='P'
AND t.number BETWEEN 1 AND s.data
Note: Refer this Why (and how) to split column using master..spt_values?
You actually don't need a loop
IF OBJECT_ID('TEMPDB..#TEMP') IS NOT NULL
DROP TABLE #TEMP
SELECT 'A' AS ID, 4 AS DATA
INTO #TEMP UNION
SELECT 'B', 2 UNION
SELECT 'C', 5
;WITH CTE AS
(
SELECT 1 AS NUMBER
UNION ALL
SELECT NUMBER + 1
FROM CTE
WHERE NUMBER < 100
)
SELECT T.ID, 1
FROM CTE C
INNER JOIN #TEMP T
ON C.NUMBER <= T.DATA
ORDER BY T.ID
Carefull that if you want ot generate a large set of numbers in the CTE it may become slower.
Use a Recursive CTE which will help you to loop through the records.
CREATE TABLE #test
(id CHAR(1),data INT)
INSERT #test
VALUES ('A',4),('B',2),('C',5);
WITH cte
AS (SELECT 1 AS da,id,data
FROM #test a
UNION ALL
SELECT da + 1,id,data
FROM cte a
WHERE da < (SELECT data
FROM #test b
WHERE a.id = b.id))
SELECT id,
1 AS data
FROM cte
ORDER BY id
i used two loops
1. for each row
2. for number for duplicate insert
SET NOCOUNT on;
DECLARE #t table(row int IDENTITY(1,1),id varchar(10),data int)
INSERT INTO #t
SELECT * from xyz
DECLARE #x table(id varchar(10),data int) --table to hold the new data
DECLARE #i int=(SELECT count (*) from xyz) --number of rows main table
DECLARE #y int --number of duplicate
DECLARE #p int=1 --number of rows
WHILE #i!=0 --loop until last row of main table
BEGIN
SET #y=(SELECT data FROM #t WHERE row=#p) --set #y for number of 'row duplicate'
WHILE #y!=0
BEGIN
INSERT INTO #x
SELECT id,1
FROM #t
WHERE row=#p
SET #y=#y-1
END
SET #p=#p+1
SET #i=#i-1
END
SELECT * FROM #x

Select non-existing rows

Let say I have a table:
ColumnA ColumnB
---------------------------------
1 10.75
4 1234.30
6 2000.99
How can I write a SELECT query that will result in the following:
ColumnA ColumnB
---------------------------------
1 10.75
2 0.00
3 0.00
4 1234.30
5 0.00
6 2000.99
You can use a CTE to create a list of numbers from 1 to the maximum value in your table:
; with numbers as
(
select max(ColumnA) as nr
from YourTable
union all
select nr - 1
from numbers
where nr > 1
)
select nr.nr as ColumnA
, yt.ColumnB
from numbers nr
left join
YourTable yt
on nr.nr = yt.ColumnA
order by
nr.nr
option (maxrecursion 0)
See it working at SQL Fiddle.
Please try:
declare #min int, #max int
select #min=MIN(ColumnA), #max=MAX(ColumnA) from tbl
select
distinct number ColumnA,
isnull(b.ColumnB, 0) ColumnB
from
master.dbo.spt_values a left join tbl b on a.number=b.ColumnA
where number between #min and #max
Create a TallyTable (or NumbersTable) - see this question: What is the best way to create and populate a numbers table?
With that table create an insert statement:
INSERT INTO YourTable (ColumnA, ColumnB)
SELECT Number FROM NumberTable
WHERE
NOT EXISTS (SELECT 1 FROM YourTable WHERE NumberTable.Number = YourTable.ColumnA)
-- Adjust this value or calculate it with a query to the maximum of the source table
AND NumberTable.Number < 230130
DECLARE #t TABLE (ID INT,Val DECIMAL(10,2))
INSERT INTO #t (ID,Val) VALUES (1,10.75)
INSERT INTO #t (ID,Val) VALUES (4,6.75)
INSERT INTO #t (ID,Val) VALUES (7,4.75)
declare #MinNo int
declare #MaxNo int
declare #IncrementStep int
set #MinNo = 1
set #MaxNo = 10
set #IncrementStep = 1
;with C as
(
select #MinNo as Num
union all
select Num + #IncrementStep
from C
where Num < #MaxNo
)
select Num,
CASE WHEN Val IS NOT NULL THEN Val ELSE 0.00 END AS NUMBER
from C
LEFT JOIN #t t
ON t.ID = c.Num
You could use a number-table or following trick to generate a sequence which you can LEFT OUTER JOIN with your table. I assume you want to determine the boundaries dynamically:
WITH Seq AS
(
SELECT TOP ((SELECT Max(ColumnA)FROM Table1) - (SELECT Min(ColumnA) FROM Table1) + 1)
Num = (SELECT Min(ColumnA) FROM Table1)+ Row_number() OVER (ORDER BY [object_id]) -1
FROM sys.all_objects)
SELECT ColumnA = Seq.Num,
ColumnB = COALESCE(t.ColumnB ,0.00)
FROM Seq
LEFT OUTER JOIN Table1 t
ON Seq.Num = t.ColumnA
Demo with your sample.
Worth reading: http://www.sqlperformance.com/2013/01/t-sql-queries/generate-a-set-1
I have my collect of table functions like these.
create function dbo.GetNumbers(#Start int, #End int)
returns #Items table
(
Item int
)
as
begin
while (#Start <= #End)
begin
insert into #Items
values (#Start)
set #Start = #Start + 1
end
return
end
Then I can use it to left join to my data table and every value will be there.
declare #min int, #max int
set #min = 10
set #max = 20
select gn.Item
from dbo.GetNumbers(#min, #max) gn
I have similar table functions for date ranges, times, timezones, etc.