Multpilcation Aggregate in Sql Server - sql

I am trying to create Multplication aggregate function from the below sql code.
declare #Floats as table (id int,value float)
insert into #Floats values (1,1)
insert into #Floats values (2,3)
insert into #Floats values (3,6)
SELECT *
FROM #Floats a
CROSS apply (SELECT CASE
WHEN MinVal = 0 THEN 0
WHEN Neg % 2 = 1 THEN -1 * Exp(ABSMult)
ELSE Exp(ABSMult)
END AS mul_value
FROM (SELECT
Sum(Log(Abs(NULLIF(Value, 0)))) AS ABSMult,
Sum(Sign(CASE
WHEN Value < 0 THEN 1
ELSE 0
END)) AS Neg,
Min(Abs(Value)) AS MinVal
FROM #Floats b
WHERE a.id >= b.id) foo) cs
Result
id value mul_value
-- ----- ---------
1 1 1
2 3 3
3 6 18
Here is the idea for function. Since we we need table type to pass table as input to function am creating one below
User defined table type
CREATE TYPE [dbo].[UDT_TEST] AS TABLE( value float )
Function code
CREATE FUNCTION udf_Mul(#values dbo.[UDT_TEST] readonly)
RETURNS FLOAT
AS
BEGIN
DECLARE #mul_value FLOAT
SELECT #mul_value=CASE
WHEN MinVal = 0 THEN 0
WHEN Neg % 2 = 1 THEN -1 * Exp(ABSMult)
ELSE Exp(ABSMult)
END
FROM (SELECT
--log of +ve row values
Sum(Log(Abs(NULLIF(Value, 0)))) AS ABSMult,
--count of -ve values. Even = +ve result.
Sum(Sign(CASE
WHEN Value < 0 THEN 1
ELSE 0
END)) AS Neg,
--anything * zero = zero
Min(Abs(Value)) AS MinVal
FROM #values) foo
RETURN #mul_value
END
Using the function like this.
SELECT *
FROM #Floats a
CROSS apply (SELECT dbo.udf_mul(value) ast
FROM #Floats b
WHERE a.id >= b.id) cs
Here the problem is the input dbo.udf_mul expects parameter to be of [dbo].[UDT_TEST] type but value column is of Float type.
Error :
Msg 206, Level 16, State 2, Line 7 Operand type clash: float is
incompatible with UDT_TEST Is there any way to achieve this?

I would not use scalar function for this type of operation. You end up with poor performance because query optimizer need to run it row-by-row without any optimalization. More info: SQL Server Functions: The Basics.
Second with float/decimal and LOG/EXP you will get approximation errors.
Third to pass data to function you can use XML like:
CREATE FUNCTION dbo.udf_Mul(#value xml)
RETURNS FLOAT
AS
BEGIN
DECLARE #mul_value FLOAT;
DECLARE #values AS TABLE ([value] float);
INSERT INTO #values([value])
SELECT [value] = t.c.value('(value)[1]', 'float')
FROM #value.nodes('//row') AS t(c);
SELECT #mul_value=CASE
WHEN MinVal = 0 THEN 0
WHEN Neg % 2 = 1 THEN -1 * Exp(ABSMult)
ELSE Exp(ABSMult)
END
FROM (SELECT
Sum(Log(Abs(NULLIF(Value, 0)))) AS ABSMult,
Sum(Sign(CASE
WHEN Value < 0 THEN 1
ELSE 0
END)) AS Neg,
Min(Abs(Value)) AS MinVal
FROM #values) foo
RETURN #mul_value
END
and calling it:
SELECT *
FROM Floats a
CROSS APPLY (SELECT dbo.udf_mul((SELECT [value]
FROM Floats b
WHERE a.id >= b.id
FOR XML PATH, ROOT('root')))
AS r) as cs(r);
SqlFiddleDemo
Output:
╔═════╦════════╦════════════════════╗
║ id ║ value ║ r ║
╠═════╬════════╬════════════════════╣
║ 1 ║ 1 ║ 1 ║
║ 2 ║ 3 ║ 3.0000000000000004 ║
║ 3 ║ 6 ║ 17.999999999999996 ║
║ 4 ║ 2 ║ 36 ║
╚═════╩════════╩════════════════════╝

Related

Sampling for a SQL Database

I have a column "steak" representing the amount of steak in pounds my firm has bought since day 1 of 2010.
I have another column "c_steak" representing the cumulative sum of pounds of steak.
╔═══╦════════════╦═════════════╗
║ ║ steak ║ c_steak ║
╠═══╬════════════╬═════════════╣
║ 1 ║ 0.2 ║ 0.2 ║
║ 2 ║ 0.2 ║ 0.4 ║
║ 3 ║ 0.3 ║ 0.7 ║
╚═══╩════════════╩═════════════╝
How do I sample the table such that a row is taken once we buy another 100 pounds of steak? (sample ONE row immediately after c_steak reaches 100, 200, 300, 400 etc).
Note(EDIT):
c_steak is float. It may not exactly hit 100, 200, 300....
If c_steak goes like ..., 99.5, 105.3, 107.1, ... then the row corresponding to 105.3 will be sampled.
if c_steak goes like ..., 99, 100.1, 100.2, 100.3, 105..., then the row corresponding to 100.1 will be sampled.
It almost certain you need LAG method. You can try like:
SELECT *
FROM (
SELECT c_steak
,lag(c_steak, 1, 0) OVER (ORDER BY id) lg
FROM myTable
) sub
WHERE cast(sub.c_steak as int) %100 - cast(sub.lg as int)% 100 < 0
The logic is that when you reach a sum of 100, 200 etc, the difference in modulus with the previous value should be negative.
e.g:
80%100 = 80 where as 101%100 = 1
195%100 = 95 where as 205%100 = 5
293%100 = 93 where as 320%100 = 20
etc
This works:
SELECT m2.id,m2.steak,m2.c_steak FROM t1 as m1 inner join t1 as m2 on m2.id = m1.id + 1 WHERE cast(m2.c_steak as int) % 100 < cast(m1.c_steak as int) % 100;
Look here:
DEMO
===========
EDIT (in case id column skips at all):
SELECT distinct m2.id,m2.steak,m2.c_steak FROM t1 as m1 inner join t1 as m2 on m2.id > m1.id WHERE cast(m2.c_steak as int) % 100 < cast(m1.c_steak as int) % 100;
DEMO
===========
You can use the MOD() function. Docs: https://dev.mysql.com/doc/refman/8.0/en/mathematical-functions.html#function_mod
SELECT * FROM Table WHERE MOD(c_steak, 100) = 0;
EDIT:
In response to OPs edit, you can use FLOOR() on c_steak to get an int. Docs: https://dev.mysql.com/doc/refman/8.0/en/mathematical-functions.html#function_floor
SELECT * FROM Table WHERE MOD(FLOOR(c_steak), 100) = 0;
You could have supplied some sample data.
Doing it now :
WITH
-- sample data , this will be in the table
input(id,steak_sold) AS (
SELECT 1,30.07
UNION ALL SELECT 2,30.01
UNION ALL SELECT 3,30.02
UNION ALL SELECT 4,30.03
UNION ALL SELECT 5,30.04
UNION ALL SELECT 6,30.05
UNION ALL SELECT 7,30.06
UNION ALL SELECT 8,30.07
UNION ALL SELECT 9,30.08
UNION ALL SELECT 10,30.09
UNION ALL SELECT 11,30.10
UNION ALL SELECT 12,30.11
UNION ALL SELECT 13,30.12
UNION ALL SELECT 14,30.13
UNION ALL SELECT 15,30.14
UNION ALL SELECT 16,30.15
UNION ALL SELECT 17,30.16
)
-- real WITH clause would begin here: creating running sum myself ....
,
runsum AS (
SELECT
*
, SUM(steak_sold) OVER(ORDER BY id) AS c_steak
FROM input
)
SELECT
*
FROM runsum
-- this running sum is above a certain 100
-- previous (running sum - steak_sold) below that 100
-- integer division by 100 of the two differs
WHERE c_steak//100 <> (c_steak - steak_sold) //100;
-- out id | steak_sold | c_steak
-- out ----+------------+---------
-- out 4 | 30.03 | 120.13
-- out 7 | 30.06 | 210.28
-- out 10 | 30.09 | 300.52
-- out 14 | 30.13 | 420.98
-- out 17 | 30.16 | 511.43
-- out (5 rows)
-- out
-- out Time: First fetch (5 rows): 53.018 ms. All rows formatted: 53.066 ms

Concatenating numbers from a single numeric column in SQL without using concat function

I have the following code for a single column table that has values from 0 to 9:
create table number(
value int
)
truncate table number
insert into number values(0);
insert into number values(1);
insert into number values(2);
insert into number values(3);
insert into number values(4);
insert into number values(5);
insert into number values(6);
insert into number values(7);
insert into number values(8);
insert into number values(9);
What I want is a query which should join the numbers without using concat (or any builtin function) function and gives me the following output in a tabular form:
0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
.
.
.
.
.
999
Really having a hard time doing it for the past 5 days. Need sincere help.
You could use CROSS JOIN and simple Math formula:
SELECT [num] = n1.value * 100 + n2.value * 10 + n3.value
FROM number n1, number n2, number n3
ORDER BY num
LiveDemo
Output:
╔═════╗
║ num ║
╠═════╣
║ 0 ║
║ 1 ║
║ 2 ║
║ 3 ║
║ 4 ║
║ 5 ║
║ 6 ║
║ 7 ║
║ ... ║
║ 999 ║
╚═════╝
EDIT:
Cross join will return records(tuples) (0,0,0), (0,0,1),(0,0,2), ...,(9,9,9) then you need to apply Positional Number System formula:
example:
Images from https://en.wikipedia.org/wiki/Positional_notation
╔════════════╦═══════════╦══════════╦════════════════════╗
║ 10^2 = 100 ║ 10^1 = 10 ║ 10^0 = 1 ║ num ║
╠════════════╬═══════════╬══════════╬════════════════════╣
║ 0 * 100 ║ 0 * 10 ║ 0 * 1 ║ 0 + 0 + 0 = 0 ║
║ 0 * 100 ║ 0 * 10 ║ 1 * 1 ║ 0 + 0 + 1 = 1 ║
║ 0 * 100 ║ 0 * 10 ║ 2 * 1 ║ 0 + 0 + 2 = 2 ║
║ ... ║ ... ║ ... ║ ... ║
║ 9 * 100 ║ 9 * 10 ║ 9 * 1 ║ 900 + 90 + 9 = 999 ║
╚════════════╩═══════════╩══════════╩════════════════════╝
While #lad2025 query is good based on certain formula.It uses 3 cross join.
Simpler way (not sure,if this is best)
Declare #number table(
value int
)
insert into #number values(0);
insert into #number values(1);
insert into #number values(2);
insert into #number values(3);
insert into #number values(4);
insert into #number values(5);
insert into #number values(6);
insert into #number values(7);
insert into #number values(8);
insert into #number values(9);
Declare #MaxLimit int=999
;
WITH CTE
AS (
SELECT value
FROM #number A
UNION ALL
SELECT a.value + 10
FROM CTE A
WHERE A.value < #MaxLimit-9
)
SELECT value
FROM cte
--WHERE value <= 999
ORDER BY value

Running Total until specific condition is true

I'm having a table representing the dealers cards and their rank. I'm now trying to make a query (as fast as possible) to set status on the game.
(As said before, only the dealer cards is shown)
W = Win
S = Stand
L = Loss
B = Blackjack (in two cards)
About the rules:
The dealer wins at 21, if it's in two cards its blackjack. If the rank is between 17 and 20 it's S = stand. Over 21 is a loss.
Ranks:
1 (ACE) - 1 or 11 rank. Counted as 11.
2-10 - 2-10 rank
11-13 (knight - king) - 10 rank
╔════╦══════╦════════╗
║ Id ║ Rank ║ Status ║
╠════╬══════╬════════╣
║ 1 ║ 1 ║ ║
║ 2 ║ 5 ║ ║
║ 3 ║ 8 ║ L ║ //24 = Loss
║ 4 ║ 3 ║ ║
║ 5 ║ 1 ║ ║
║ 6 ║ 7 ║ W ║ //21 = Win
║ 7 ║ 10 ║ ║
║ 8 ║ 1 ║ B ║ //21 = Blackjack
║ 9 ║ 10 ║ ║
╚════╩══════╩════════╝
I've tried to use a counter to check if it's blackjack and then I'm using a "RunningPoint" to check the sum of the cards.
I have now a solution bad it shows very bad performance when it's a lot of data. How would you do this and what can I do to optimize my query? When using more data I also need to use option (maxrecursion 0)
(When having 1 million rows it's not even possible to run this...)
My example: http://sqlfiddle.com/#!6/3855e/1
There's no efficient solution using plain SQL (including Windowed Aggregate Functons), at least nobody found one, yet :-)
Your recursive query performs bad because it's way too complicated, this is a simplified version:
Edit: Fixed the calculation (Fiddle)
WITH ctePoints AS
(
SELECT 1 AS id
,rank
,CASE
WHEN rank >= 10 THEN 10
WHEN rank = 1 THEN 11
ELSE rank
END AS Point
,1 AS Counter
FROM dbo.BlackJack
WHERE Id = 1
UNION ALL
SELECT t2.Id
,t2.rank
,CASE WHEN t1.Point < 17 THEN t1.Point ELSE 0 END
+ CASE
WHEN t2.rank >= 10 THEN 10
WHEN t2.rank = 1 THEN 11
ELSE t2.rank
END AS Point
,CASE WHEN t1.Point < 17 THEN t1.Counter + 1 ELSE 1 END AS Counter
FROM dbo.BlackJack AS t2
INNER JOIN ctePoints AS t1 ON t2.Id = t1.Id + 1
)
SELECT ctepoints.*
,CASE
WHEN Point < 17 THEN ''
WHEN Point < 20 THEN 'S'
WHEN Point > 21 THEN 'L'
WHEN Point = 21 AND Counter = 2 THEN 'B'
ELSE 'W'
END AS DealerStatus
FROM ctePoints
It's probably still too slow, because it processes row by row.
I usually use recursive SQL to replace cursor logic (because in my DBMS it's usually much faster) but a cursor update might actually be faster (Demo):
CREATE TABLE #BlackJack
(
id INT PRIMARY KEY CLUSTERED
,Rank INT
,DealerStatus CHAR(1)
);
insert into #BlackJack (Id, Rank)
values
(1, 1),(2, 5), (3, 8), (4, 3), (5, 1), (6, 7), (7, 10), (8, 1),(9, 10), (10, 10), (11,1);
DECLARE #Counter INT = 0
,#Point INT = 0
,#id int
,#Rank int
,#DealerStatus char(1)
DECLARE c CURSOR
FOR
SELECT id, Rank
FROM #BlackJack
ORDER BY id FOR UPDATE OF DealerStatus
OPEN c
FETCH NEXT FROM c INTO #id, #Rank
WHILE ##FETCH_STATUS = 0
BEGIN
SET #counter = #counter + 1
SET #Rank = CASE
WHEN #Rank >= 10 THEN 10
WHEN #Rank = 1 THEN 11
ELSE #Rank
END
SET #Point = #Point + #Rank
SET #DealerStatus = CASE
WHEN #Point < 17 THEN ''
WHEN #Point < 20 THEN 'S'
WHEN #Point > 21 THEN 'L'
WHEN #Point = 21 AND #Counter = 2 THEN 'B'
ELSE 'W'
END
IF #Point >= 17
BEGIN
UPDATE #BlackJack
SET DealerStatus = #DealerStatus
WHERE CURRENT OF c;
SET #Point = 0
SET #Counter = 0
END
FETCH NEXT FROM c INTO #id, #Rank
END
CLOSE c
DEALLOCATE c
SELECT * FROM #BlackJack ORDER BY id
Still #lad2025's "quirky update" is the fastest way to get the expected result, but it's using an undocumented feature and if a Service Pack breaks it there's no way to complain about it :-)
This solution is based on quirky update. More info here.
LiveDemo
Data and structures:
CREATE TABLE #BlackJack
(
id INT
,Rank INT
,running_total INT
,result NVARCHAR(100)
);
CREATE CLUSTERED INDEX IX_ROW_NUM ON #BlackJack(id);
insert into #BlackJack (Id, Rank)
values (1, 1),(2, 5), (3, 8), (4, 3), (5, 1),
(6, 7), (7, 10), (8, 1),(9, 10), (10, 10), (11,1);
Main query:
DECLARE #running_total INT = 0
,#number_of_cards INT = 0
,#prev_running_total INT = 0;
UPDATE #BlackJack
SET
#prev_running_total = #running_total
,#running_total = running_total = IIF(#running_total >= 20, 0, #running_total)
+ CHOOSE(Rank,11,2,3,4,5,6,7,8,9,10,10,10,10)
,result = CASE WHEN #running_total = 20 THEN 'S'
WHEN #running_total = 21 AND #number_of_cards = 2 THEN 'B'
WHEN #running_total = 21 THEN 'W'
WHEN #running_total > 21 THEN 'L'
ELSE NULL
END
,#number_of_cards = IIF(#prev_running_total >= 20, 0, #number_of_cards) + 1
FROM #BlackJack WITH(INDEX(IX_ROW_NUM))
OPTION (MAXDOP 1);
SELECT *
FROM #BlackJack
ORDER BY id;
Warning
If you use SQL Server < 2012 you need to replace IIF and CHOOSE with CASE. I don't check all Blackjack rules, only for provided sample. If something is wrong feel free to change CASE logic.
Second I extend base table BlackJack with auxiliary columns, but you can create any new table, if needed.
The key point is to read data sequentially based on clustered key ascending and do not allow parallel execution. Before you use it in production check how it behaves with large data set.

ROLLUP Function; Replace NULL with 'Total' w/ Column data type INT not VARCHAR

I'm having some problems replacing my ROLLUP NULL with a string value because my column data type is an Integer.
SELECT CASE
WHEN GROUPING(Column1) = 1 THEN 'Total'
ELSE Column1
END Column1, SUM(Column2) AS MySum
FROM MyTable
GROUP BY Column1 WITH ROLLUP;
I can put a numeric value in:
WHEN GROUPING(Column1) = 1 THEN '9999'
but I can't figure out how to convert to varchar if value is NULL and then replace with 'Total'.
Test Data
DECLARE #MyTable TABLE (Column1 INT,Column2 INT)
INSERT INTO #MyTable VALUES
(1,1),(1,2),(1,3),(2,1),(2,2),(2,3),(3,1),(3,2),(3,3)
SELECT CASE
WHEN GROUPING(Column1) = 1 THEN 'Total'
ELSE CAST(Column1 AS VARCHAR(10)) --<-- Cast as Varchar
END Column1
, SUM(Column2) AS MySum
FROM #MyTable
GROUP BY Column1
WITH ROLLUP;
Result Set
╔═════════╦═══════╗
║ Column1 ║ MySum ║
╠═════════╬═══════╣
║ 1 ║ 6 ║
║ 2 ║ 6 ║
║ 3 ║ 6 ║
║ Total ║ 18 ║
╚═════════╩═══════╝
Note
The reason you couldnt do what you were trying to do is because when you use a CASE statement in each case the returned datatype should be the same.
In above query I have just CAST the colum1 to varchar and it worked.

Select value as column

If I have a table containing:
game powerup used
memory 1 12
memory 2 10
bejeweled 2 88
bejeweled 3 54
...where the (number of) different possible values of powerup are unknown, is there a way to SELECT this as something like
game 1_used 2_used 3_used
memory 12 10
bejeweled 88 54
I'm interested in pure SQL or PostgreSQL answers.
Note: This is an academical question, I don't have a use case at the moment. Answer it for your enjoyment or for the good of the site, not my personal benefit :)
Try this one:
SELECT game
,MAX(CASE WHEN powerup = 1 THEN used ELSE NULL END) AS used1
,MAX(CASE WHEN powerup = 2 THEN used ELSE NULL END) AS used2
,MAX(CASE WHEN powerup = 3 THEN used ELSE NULL END) AS used3
FROM Table1
GROUP BY game;
Output:
╔═══════════╦════════╦═══════╦════════╗
║ GAME ║ USED1 ║ USED2 ║ USED3 ║
╠═══════════╬════════╬═══════╬════════╣
║ bejeweled ║ (null) ║ 88 ║ 54 ║
║ memory ║ 12 ║ 10 ║ (null) ║
╚═══════════╩════════╩═══════╩════════╝
See this SQLFiddle
DROP SCHEMA tmp CASCADE;
CREATE SCHEMA tmp ;
SET search_path=tmp;
CREATE TABLE meuk
( game varchar
, powerup INTEGER NOT NULL
, used INTEGER NOT NULL
, PRIMARY KEY (game,powerup)
);
INSERT INTO meuk(game, powerup, used) VALUES
( 'memory' , 1,12)
,( 'memory' , 2,10)
,( 'bejeweled' , 2,88)
,( 'bejeweled' , 3,54)
;
-- using a self-join as
-- a poor man's pivot.
WITH dom AS (
SELECT DISTINCT game AS game
FROM meuk
)
SELECT m0.game AS game
, m1.used AS used_1
, m2.used AS used_2
, m3.used AS used_3
FROM dom m0
LEFT JOIN meuk m1 ON m1.game = m0.game AND m1.powerup = 1
LEFT JOIN meuk m2 ON m2.game = m0.game AND m2.powerup = 2
LEFT JOIN meuk m3 ON m3.game = m0.game AND m3.powerup = 3
;
RESULTS:
game | used_1 | used_2 | used_3
-----------+--------+--------+--------
bejeweled | | 88 | 54
memory | 12 | 10 |
(2 rows)
CREATE TABLE test_1
( game varchar(10)
, powerup INTEGER NOT NULL
, used INTEGER NOT NULL
);
INSERT INTO test_1(game, powerup, used)
SELECT 'memory' , 1,12 UNION ALL
SELECT 'memory' , 2,10 UNION ALL
SELECT 'bejeweled' , 2,88 UNION ALL
SELECT 'bejeweled' , 3,54
select * from test_1
DECLARE #COLUMNS varchar(max)
SELECT #COLUMNS = COALESCE(#COLUMNS+'],[' ,'') + CAST(powerup as varchar)
FROM test_1
GROUP BY powerup
SET #COLUMNS = '[' + #COLUMNS + ']'
EXECUTE ('select * from test_1
pivot (SUM(used) for powerup IN ('+ #COLUMNS +')) as used_t')