Multiply by largest row and append to list - sql-server

I have the following in two tables:
(SFR_MAIN)
SQ. FT. AREA 1 2
400 86.6600 86.7300
500 82.3300 82.4000
600 78.9600 79.0200
700 76.2100 76.2700
800 73.9100 73.9700
900 71.9400 71.9900
1000 70.2200 70.2700
1100 68.7000 68.7500
1200 67.3400 67.3900
1300 66.1200 66.1600
1400 65.0000 65.0400
1600 63.0400 63.0800
1800 61.3600 61.3900
2000 59.8900 59.9300
2200 58.6000 58.6300
2400 57.4400 57.4700
2600 56.3900 56.4200
2800 55.4400 55.4700
3000 54.5700 54.6000
And
(MULT)
SQ. FT. AREA MULTIPLIER
3100 0.992
3200 0.986
3300 0.978
3400 0.971
3500 0.964
3600 0.958
3700 0.952
3800 0.946
4000 0.934
The idea would be to create a table with both. The problem is that the second is a multiplier that should be multiplied to that last(Largest) squarfootage in the first.
So 3100 would be .992 * 54.57 for column 1 and .992 * 54.6 for column 2, yes those are the actual names of the columns.
So the desired output would be:
SQ. FT. AREA 1 2
400 86.6600 86.7300
500 82.3300 82.4000
600 78.9600 79.0200
700 76.2100 76.2700
800 73.9100 73.9700
900 71.9400 71.9900
1000 70.2200 70.2700
1100 68.7000 68.7500
1200 67.3400 67.3900
1300 66.1200 66.1600
1400 65.0000 65.0400
1600 63.0400 63.0800
1800 61.3600 61.3900
2000 59.8900 59.9300
2200 58.6000 58.6300
2400 57.4400 57.4700
2600 56.3900 56.4200
2800 55.4400 55.4700
3000 54.5700 54.6000
3100 54.1334 54.1632
3200 53.8060 53.8356
3300 53.3695 53.3988
3400 52.9875 53.0166
3500 52.6055 52.6344
3600 52.2781 52.3068
3700 51.9506 51.9792
3800 51.6232 51.6516
4000 50.9684 50.9964
I can do one column like this:
select
[SQ. FT. AREA],
[1]
from test.dbo.SFR_MAIN
union all
Select
[SQ. FT. AREA],
MULTIPLIER * (select
m.[1]
from test.dbo.SFR_MAIN m
inner join
(
select max([SQ. FT. AREA]) as mmm
from test.dbo.SFR_MAIN
) tt on tt.mmm = m.[SQ. FT. AREA]
) as [1]
from test.dbo.mult
And if I only had the two columns I would just do it twice, but I have 16 columns. Is there a way to iterate through the columns to get them all side by side in a table?
I am using SQL Server 2012

Example
Select * From SFR_MAIN
Union All
Select B.[SQ. FT. AREA]
,[1] = A.[1]*B.MULTIPLIER
,[2] = A.[2]*B.MULTIPLIER
From (Select Top 1 with ties * from SFR_MAIN Order by [SQ. FT. AREA] Desc) A
Join MULT B on B.[SQ. FT. AREA]>A.[SQ. FT. AREA]
Returns

Iteration in databases is generally a bad idea. Databases work better on set based approaches. So, union as you describe but then use cross join on the record from the first set that has the highest sq foot and then just do the math.
SELECT [SQ. Ft. Area], [1],[2],[3],[4],[5],[6],[7],[8],[9],[10],[11],[12],[13],[14],[15],[16]
FROM sfr_Main
UNION ALL
SELECT A.[SQ. FT. Area]
, B.multiplier*[1] as 1
, B.multiplier*[2] as 2
, B.multiplier*[3] as 3
, B.multiplier*[4] as 4
, B.multiplier*[5] as 5
, B.multiplier*[6] as 6
, B.multiplier*[7] as 7
, B.multiplier*[8] as 8
, B.multiplier*[9] as 9
, B.multiplier*[10] as 10
, B.multiplier*[11] as 11
, B.multiplier*[12] as 12
, B.multiplier*[13] as 13
, B.multiplier*[14] as 14
, B.multiplier*[15] as 15
, B.multiplier*[16] as 16
FROM MULT A
CROSS JOIN (SELECT top 1 [SQ. Ft. Area], [1],[2],[3],[4],[5],[6],[7],[8],[9],[10],[11],[12],[13],[14],[15],[16]
FROM sfr_main
ORDER BY [sq.ft.area] desc) B

Related

SQL Query on Aggregating the sequence numbers

I have a table which has sequence numbers from 1 to 90000
so i wanted to know how to automatically assign the values to the sequence numbers
say for example from 1 to 1000 i want them to fall under 1000 bucket
from 1001 to 2000 under 2000 bucket
and so on up to 90000 records.
You can divide the number by 1000, floor it, and multiply it back by 1000:
SELECT 1000*FLOOR(num/1000) + 1, COUNT(*)
FROM mytable
GROUP BY FLOOR(num/1000)
The Modulo (%) operator is perfect for something like this...
So easy, it feels like it's cheating.
WITH
cte_n1 (n) AS (SELECT 1 FROM (VALUES (1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) n (n)),
cte_n2 (n) AS (SELECT 1 FROM cte_n1 a CROSS JOIN cte_n1 b),
cte_n3 (n) AS (SELECT 1 FROM cte_n2 a CROSS JOIN cte_n2 b),
Sequense (n) AS (
SELECT TOP 90000
ROW_NUMBER() OVER (ORDER BY (SELECT NULL))
FROM
cte_n3 a CROSS JOIN cte_n3 b
)
SELECT
SequenseNumber = s.n,
GroupNumber = s.n - (s.n % 1000)
FROM
Sequense s;
Results...
SequenseNumber GroupNumber
-------------------- --------------------
1 0
2 0
3 0
4 0
5 0
6 0
.........................
997 0
998 0
999 0
1000 1000
1001 1000
1002 1000
1003 1000
1004 1000
1005 1000
1006 1000
1007 1000
1008 1000
1009 1000
1010 1000
.........................
89990 89000
89991 89000
89992 89000
89993 89000
89994 89000
89995 89000
89996 89000
89997 89000
89998 89000
89999 89000
90000 90000
(90000 rows affected)
The code below does what you want, it uses CEILING and an algorithm that works from 0 to 90K literally consider that the num must be DECIMAL if you use int the decimal round is set to 0.
Test data
declare #tbl table(num decimal)
insert into #tbl
select 1 union
select 999 union
select 1000 union
select 1001 union
select 2001 union
select 3001 union
select 9999 union
select 10000 union
select 10001 union
select 15001 union
select 25001 union
select 77006 union
select 80000 union
select 90000
Query
SELECT distinct
num,
CASE WHEN num <= 10000 THEN 1000*CEILING(num/1000)
WHEN num <= 20000 THEN 10000 + 1000*CEILING((num-10000)/1000)
WHEN num <= 30000 THEN 20000 + 1000*CEILING((num-20000)/1000)
WHEN num <= 40000 THEN 30000 + 1000*CEILING((num-30000)/1000)
WHEN num <= 50000 THEN 40000 + 1000*CEILING((num-40000)/1000)
WHEN num <= 60000 THEN 50000 + 1000*CEILING((num-50000)/1000)
WHEN num <= 70000 THEN 60000 + 1000*CEILING((num-60000)/1000)
WHEN num <= 80000 THEN 70000 + 1000*CEILING((num-70000)/1000)
WHEN num <= 90000 THEN 80000 + 1000*CEILING((num-80000)/1000)
ELSE 0
END
FROM #tbl
Result
1 1000
999 1000
1000 1000
1001 2000
2001 3000
3001 4000
9999 10000
10000 10000
10001 11000
15001 16000
25001 26000
77006 78000
80000 80000
90000 90000
I'm going to assume that what Jason posted is what the OP is looking for. This is a slight variation using getnumsAB which was developed for exactly this type of thing. First we'll use it to create some sample data:
Sample data
if object_id('tempdb..#yourdata') is not null drop table #yourdata;
select SequenceNumber = rn
into #yourdata
from dbo.GetNumsAB(1,90000,1,1);
create unique clustered index uq_cl_yourdata on #yourdata(SequenceNumber);
To understand my solution first note this query:
select rn, n1, n2 from dbo.getnumsAB(0,90000,1000,1);
This returns:
rn n1 n2
----- -------- --------
1 0 1000
2 1000 2000
3 2000 3000
4 3000 4000
....
87 86000 87000
88 87000 88000
89 88000 89000
90 89000 90000
Solution
select y.SequenceNumber, GroupNumber = n1
from #yourdata y
join dbo.getnumsAB(0,90000,1000,1) gn
on y.SequenceNumber >= n1 and y.SequenceNumber < n2;
If i'm not missing something wouldn't you just use a case statement?
select case
when Sequence <= 1000
then '1000'
when Sequence <= 2000 and >= 1001
then '2000'
and so on up to 9000?

Return Maximum Value along with day + time value grouped by month

Using MS-SQL 2012. Having a real puzzle trying to retrieve specific datafields from a large climatology dataset.
I have stripped this large raw data file down to a temp table called #max_temp which correctly pulls back the max value for each day along with the time it occurred and day/month value for reference:
monthid month day time current_temp
1 12 24 12:45 9.1
1 12 25 12:25 8.3
1 12 26 23:55 8.6
1 12 27 00:00 8.6
1 12 28 13:15 5.9
1 12 29 12:50 5
1 12 30 13:32 6.3
1 12 31 12:49 6.9
2 1 1 23:59 12
2 1 2 01:12 12.7
2 1 3 03:55 6.2
What I want to retrieve is an output grouped by monthID, so returning:
monthid month day time current_temp
1 12 24 12:45 9.1
2 1 9 20:04 15.1 <<*not shown in above sample*>>
From looking at other similar questions I have tried the following the code but not getting to the end solution or the query fails.
select *
from (select t.*, ROW_NUMBER () over (partition by t.monthid, t.time order by t.current_temp desc) as rn
from #max_temp t) x
where rn=1
order by monthid asc
or
select monthid, day, time, current_temp
from #max_temp
where current_temp= (select max(current_temp) from #max_temp group by MonthID, day, time)
Thanks in advance for your help,
Elliot.
Remove t.time from the partition by like so:
select *
from (
select t.*, ROW_NUMBER () over (partition by t.monthid order by t.current_temp desc) as rn
from #max_temp t
) x
where rn=1
order by monthid asc
Having time in the partition would give you the greatest value for current_temp for each monthid and time, but since you just want the greatest current_temp for each monthid, remove time from that expression.

How to show order fulfilment in a SQL Server 2008 query

I am trying to think of a way on a SQL Server 2008 database to run through a sales order table and get open demand for a part, order it by due date, then look at a purchase order table and fulfill the sales orders by PO, ordering the PO supply by due date as well. At the same time, I need to show what PO(s) are fulfilling the sales order.
For example:
SO table
SO# DueDate Part Number Required QTY
---------------------------------------------
100 9/3/16 1012 2
101 9/12/16 1012 1
107 10/11/16 1012 4
103 10/17/16 1012 7
PO table:
PO# DueDate Part Number Ordered QTY
--------------------------------------------
331 9/1/16 1012 1
362 9/2/16 1012 1
359 9/24/16 1012 5
371 10/1/16 1012 3
380 10/10/16 1012 10
With this data, I would like to see this result:
SO# DueDate Part Number Required QTY PO number QTY Used QTY Remain
--------------------------------------------------------------------------
100 9/3/16 1012 2 331 1 0
100 9/3/16 1012 1 362 1 0
101 9/12/16 1012 1 359 1 4
107 10/11/16 1012 4 359 4 0
103 10/17/16 1012 7 371 3 0
103 10/17/16 1012 7 380 4 6
I have done this sales order fulfillment process before, but not to the point of breaking down what PO(s) are fulfilling the order, only to the point of summing all open supply, then running through and subtracting the supply from each sales order to get a running balance of supply left.
Many thanks in advance for your help.
I found a bit weird solution, hope it helps you. Maybe later I could optimize it, but now I post it as is:
;WITH cte AS (
SELECT 1 as l
UNION ALL
SELECT l+1
FROM cte
WHERE l <= 1000000
), SO_cte AS (
SELECT *,
ROW_NUMBER() OVER (ORDER BY DueDate ASC) as rn
FROM SO s
CROSS JOIN cte c
WHERE c.l <= s.[Required QTY]
), PO_cte AS (
SELECT *,
ROW_NUMBER() OVER (ORDER BY DueDate ASC) as rn
FROM PO p
CROSS JOIN cte c
WHERE c.l <= p.[Ordered QTY]
), almost_done AS (
SELECT DISTINCT
s.SO#,
s.DueDate,
s.[Part Number],
p.PO#,
s.[Required QTY],
p.[Ordered QTY]
FROM SO_cte s
LEFT JOIN PO_cte p
ON p.rn = s.rn
), final AS (
SELECT *,
ROW_NUMBER() OVER (ORDER BY DueDate) AS RN
FROM almost_done
)
SELECT f.SO#,
f.DueDate,
f.[Part Number],
f.[Required QTY],
f.PO#,
CASE WHEN f.[Ordered QTY]>f.[Required QTY]
THEN ISNULL(ABS(f1.[Required QTY]-f1.[Ordered QTY]),f.[Required QTY])
ELSE f.[Ordered QTY] END
as [QTY Used],
f.[Ordered QTY] -
CASE WHEN f1.PO# = f.PO#
THEN f1.[Ordered QTY]
ELSE
CASE WHEN f.[Ordered QTY]>f.[Required QTY]
THEN ISNULL(ABS(f1.[Required QTY]-f1.[Ordered QTY]),f.[Required QTY])
ELSE f.[Ordered QTY] END
END as [QTY Remain]
FROM final f
LEFT JOIN final f1
ON f.RN = f1.RN+ 1
AND (f.SO# = f1.SO# OR f.PO# = f1.PO#)
OPTION(MAXRECURSION 0)
Output for data you provided:
SO# DueDate Part Number Required QTY PO# QTY Used QTY Remain
100 2016-09-03 1012 2 331 1 0
100 2016-09-03 1012 2 362 1 0
101 2016-09-12 1012 1 359 1 4
107 2016-10-11 1012 4 359 4 0
103 2016-10-17 1012 7 371 3 0
103 2016-10-17 1012 7 380 4 6

How to split a numeric field into smaller segments in Sql Server

I have a table in SQL Server with two fields.
Total Group
35645 24
12400 55
30000 41
I want to split each group into smaller segments of fixed size 7000, with the remainder of each group into the last segment. So, the output should look like below.
Segment Total Group
1 7000 24
2 7000 24
3 7000 24
4 7000 24
5 7000 24
6 645 24
1 7000 55
2 5400 55
1 7000 41
2 7000 41
3 7000 41
4 7000 41
5 2000 41
This should do it:
declare #t table (Total int,[Group] int)
insert into #t(Total,[Group]) values
(35645,24 ),
(12400,55 ),
(30000,41 )
;With Numbers as (
select ROW_NUMBER() OVER (ORDER BY number)-1 n
from master..spt_values
)
select
n.n+1 as Segment,
CASE WHEN (n.n+1)*7000 < t.Total THEN 7000
ELSE t.Total - (n.n*7000) END as Total,
t.[Group]
from
#t t inner join
Numbers n on n.n*7000 < t.Total
(If you already have a Numbers table you can eliminate that part. I'm using spt_values just as a table that I know has plenty of rows in it, so that the ROW_NUMBER() expression should generate all of the necessary numbers)
Results:
Segment Total Group
-------------------- -------------------- -----------
1 7000 24
2 7000 24
3 7000 24
4 7000 24
5 7000 24
6 645 24
1 7000 55
2 5400 55
1 7000 41
2 7000 41
3 7000 41
4 7000 41
5 2000 41
I prepared following SELECT statement using SQL CTE expression and SQL numbers table function
declare #divisor int = 7000
;with CTE as (
select
Total,
[Group],
#divisor divisor,
(Total / #divisor) quotient,
(Total % #divisor) reminder
from t
), NT as (
SELECT i FROM dbo.NumbersTable(1, (select max(quotient) from CTE) ,1)
)
select
case when i = 0 then reminder else divisor end as Total,
[Group]
from (
select *
from CTE, NT
where quotient >= i
union all
select *, 0 as i
from CTE
where reminder >= 0
) t
order by [Group], i desc

Distribute values over value-range tiers

I have two tables: a list of Total Sales Per Employee, and a list of Compensation Tiers
Employee Sales | Comp Tiers
============== | ===================
EmpID Sales | TierID MaxAmt Rate
1 12000 | 1 10000 20% -- Up to $10k sales compensated at 20%
2 17000 | 2 15000 25% --The next $5k sales compensated at 25%
3 23000 | 3 20000 30% --The next $5k sales compensated at 30%
4 31000 | 4 25000 40% --The next $5k sales compensated at 40%
| 5 99999 50% --Any remaining sales compensated at 50%
Based on these inputs, I need to split each employee's Sales over each Tier based on each tier's MaxAmt value to calculate compensation rates at each tier. Further, I don't want to hard-code calculations for each tier as the number of tiers may change over time. (On second thought, I don't mind hard-coding, as long as it can handle UP-TO 5 tiers. Sounds fair?)
Desired Output:
EmpID Sales TierID TierAmt Rate Net
=========================================
1 12000 1 10000 20% 2000
1 12000 2 2000 25% 500
2 17000 1 10000 20% 2000
2 17000 2 5000 25% 1250
2 17000 3 2000 30% 600
3 23000 1 10000 20% 2000
3 23000 2 5000 25% 1250
3 23000 3 5000 30% 1500
3 23000 4 3000 40% 1200
4 31000 1 10000 20% 2000
4 31000 2 5000 25% 1250
4 31000 3 5000 30% 1500
4 31000 4 5000 40% 2000
4 31000 5 6000 50% 3000
I'm not unskilled with SQL, but I can't even fathom an appropriate strategy. Any ideas? Changes to table structure are permissible if it helps achieve the goal.
SQLFiddle
Lets make some test data:
DECLARE #EmpSales TABLE
(
EmpID INT,
Sales INT
)
INSERT INTO #EmpSales
VALUES
( 1, 12000 ),
( 2, 17000 ),
( 3, 23000 ),
( 4, 31000 );
DECLARE #CompTiers TABLE
(
TierID INT,
MaxAmount INT,
Rate DECIMAL(10,2)
)
INSERT INTO #CompTiers
VALUES
( 1, 10000, .20 ),
( 2, 15000, .25 ),
( 3, 20000, .30 ),
( 4, 25000, .40 ),
( 5, 99999, .50 );
Here I make a CTE to find all of the tiers and the previous (to get the top and bottom of the tier)
WITH Tiers AS
(
SELECT
n.TierID,
n.MaxAmount,
n.Rate,
ISNULL(p.MaxAmount, 0) PrevAmount
FROM #CompTiers n
LEFT JOIN #CompTiers p
ON p.TierID = n.TierID - 1
),
Lets take the tier CTE and cross join it against the sales picking only the tiers where the sales is greater than the prevamount (bottom of the tier).
SalesComp AS
(
SELECT *
FROM #EmpSales e
CROSS JOIN Tiers c
WHERE Sales > PrevAmount
)
Now that we have the data matched up, just clean it up with some cases:
SELECT
s.EmpID,
s.Sales,
s.TierID,
CASE
WHEN s.Sales > s.MaxAmount THEN s.MaxAmount - s.PrevAmount
ELSE s.Sales - s.PrevAmount
END TierAmount,
s.Rate,
CASE
WHEN s.Sales > s.MaxAmount THEN (s.MaxAmount - s.PrevAmount) * s.Rate
ELSE (s.Sales - s.PrevAmount) * s.Rate
END Net
FROM SalesComp s
ORDER BY EmpID, TierID
Here is the output:
EmpID Sales TierID TierAmount Rate Net
1 12000 1 10000 0.20 2000.00
1 12000 2 2000 0.25 500.00
2 17000 1 10000 0.20 2000.00
2 17000 2 5000 0.25 1250.00
2 17000 3 2000 0.30 600.00
3 23000 1 10000 0.20 2000.00
3 23000 2 5000 0.25 1250.00
3 23000 3 5000 0.30 1500.00
3 23000 4 3000 0.40 1200.00
4 31000 1 10000 0.20 2000.00
4 31000 2 5000 0.25 1250.00
4 31000 3 5000 0.30 1500.00
4 31000 4 5000 0.40 2000.00
4 31000 5 6000 0.50 3000.00
The idea behind the following query happens to be same as the one in Kevin Cook's answer and the two solutions differ mainly by syntax:
WITH TierRanges AS (
SELECT
*,
MinAmt = LAG(MaxAmt, 1, 0) OVER (ORDER BY MaxAmt)
FROM
CompTiers
)
SELECT
s.EmpID,
s.Sales,
t.TierID,
x.TierAmt,
t.Rate,
Net = x.TierAmt * t.Rate
FROM
EmpSales AS s
INNER JOIN
TierRanges AS t ON s.Sales > t.MinAmt
CROSS APPLY
(
SELECT CASE WHEN s.Sales > t.MaxAmt THEN t.MaxAmt ELSE s.Sales END - t.MinAmt
) AS x (TierAmt)
ORDER BY
s.EmpID,
t.TierID
;
The TierRanges CTE "enhances" the CompTiers row set with a MinAmt column to form a tier range together with MaxAmt, MinAmt being the previous tier's MaxAmt value (or 0, for the first tier). This can be considered a direct equivalent of the other answer's Tiers CTE, but you can see that here the LAG function is used instead of a self-join, and the former is likely to work faster than the latter.
The main query joins every row in EmpSales with every row in TierRanges where EmpSales.Sales exceeds TierRanges.MinAmt. (In the other answer this part is implemented separately from the main query as another CTE, SalesComp.) To get the tier amount, it subtracts the MinAmt value from either Sales or MaxAmt, depending on which one is lesser. Because the tier amount is needed twice in the query (once for the output as is and the second time to get Net), CROSS APPLY is used to avoid repetition of code.
Because of LAG, SQL Server 2012 is the minimum version required to run the query.
try using a full outer (cross) join between the tables as long as the sales amount is less than or equal to the tier max amount
With Beth's suggestion as a jumping-off point, I worked my way through the rest to come up with a workable solution. It starts with a CROSS JOIN to show all possible combinations of Sales and Tiers, filters out those with Sales out of range of their matched Tier, and uses a Recursive CTE to work through each Tier's calculation using the prior Tier results.
;WITH
/* CROSS JOIN Sales and Tiers, and then ***/
/** filter to only relevant Tiers for each Emp. **/
/*** Also include Prior Tier [MaxAmt] value. */
combos AS
( SELECT s.EmpID,s.Sales,t.TierID,
[MaxAmt-1] = t0.MaxAmt,
[MaxAmt] = t.MaxAmt
FROM EmpSales s
CROSS JOIN CompTiers t
LEFT JOIN CompTiers t0 ON t0.TierID = t.TierID-1 --Prior Tier
WHERE s.Sales > ISNULL(t0.MaxAmt,0) --Filter out irrelevant Tiers given Sales
),
cte AS
/* Calculate Tier1 Sales, then use Recursive CTE **/
/** to calculate other Tiers based on prior Tier values */
( SELECT c.*,
TierAmt = CAST(CASE
WHEN c.Sales > c.[MaxAmt]
THEN c.[MaxAmt]
ELSE c.Sales END AS MONEY)
FROM combos c
WHERE c.TierID = 1
UNION ALL
SELECT c.*,
TierAmt = CAST(CASE
WHEN c.Sales > c.[MaxAmt]
THEN c.[MaxAmt]-ISNULL(c.[MaxAmt-1],0.0)
ELSE c.Sales-ISNULL(c.[MaxAmt-1],0) END AS MONEY)
FROM cte
JOIN combos c ON c.EmpID = cte.EmpID
WHERE c.TierID = cte.TierID+1
)
/* Combine [TierAmt] and [Rate] at each Tier */
/** to calculate Net Pay amount. **/
SELECT cte.EmpID,cte.Sales,cte.TierID,cte.TierAmt,
t.Rate,
Net = t.Rate * cte.TierAmt
FROM cte
LEFT JOIN CompTiers t ON t.TierID = cte.TierID
ORDER BY EmpID,TierID
I invite feedback, enhancements, and alternatives.
SQLFiddle

Resources