How to find Running Multiplication - sql

Not sure is this the right title. I need to find the cumulative multiplication as like running total.
Searched the forum and got a excellent answer. But it is not the exact answer for me.
so modified the answer to my requirement.
SELECT *,
(SELECT CASE
WHEN Min(Abs(Column1)) = 0 THEN 0
ELSE Exp(Sum(Log(Abs(NULLIF(Column1, 0))))) -- the base mathematics
* Round(0.5 - Count(NULLIF(Sign(Sign(Column1) + 0.5), 1))%2, 0) -- pairs up negatives
END
FROM TEMP a
WHERE B.ID >= A.ID) as Running_Mul
FROM TEMP B
And I got my answer. Now Is there any better way of doing this in Sql Server 2008?
Sample data:
ID Column1
-- -------
1 1
2 2
3 4
4 8
5 -2
Expected Result:
ID Column1 Running_Mul
-- ------- -----------
1 1 1
2 2 2
3 4 8
4 8 64
5 -2 -128
Sql Fiddle

Your method is pretty reasonable. Good catch on the nullif() in the sum(), by the way. Although the else clause is computed only after the then, components of the else are calculated during the aggregation -- so log(0) would return an error.
I think there are some simpler ways to calculate the sign, such as:
power(-1, sum(case when column1 < 0 then 1 else 0 end))
or:
(case when sum(case when column1 < 0 then 1 else 0 end) % 2 = 0 then 1 else -1 end)
However, which version is "simpler" is a matter of opinion.

Here is another approach which I use in my SPs :
USE DB
GO
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED
GO
IF(OBJECT_ID('TEMP') IS NOT NULL)
DROP TABLE TEMP
CREATE TABLE TEMP (ID INT, Column1 INT)
INSERT INTO TEMP VALUES
(1,1),
(2,2),
(3,4),
(4,8),
(5,-2)
DECLARE #result TABLE(ID INT, Column1 INT, calc INT)
DECLARE #Calc INT = 1
INSERT INTO #result (ID,Column1)
SELECT ID,Column1 FROM TEMP ORDER BY ID
UPDATE #result SET #Calc = calc = Column1 * #Calc
SELECT * FROM #result
I found a blog in which different methods to solve such problem, have been compared. check here.

Related

SQL remove from running total

I have a problem that I don't know how to fix .. here is the code and wanting result
if object_id('tempdb..#A') IS NOT NULL drop table #A
create table #A (ID int, Value decimal(6,2), value2 decimal(6,2), Result decimal(6,2))
insert into #A (ID, Value, value2, Result)
values
(1, 10, 25, null),
(1, 10, 25, null),
(1, 10, 25, null),
(2, 10, 5, null),
(2, 10, 5, null),
select * from #A
So, I would like to take Value away from "value2", if there are left overs, just update it to 0, for next row i would take those "left overs" and use them to take away from, with next Value
I would like to get results like this...
ID Value value2 Result
1 10 25 0
----------------------------
1 10 25 0
----------------------------
1 10 25 5
----------------------------
2 10 5 5
----------------------------
2 10 5 10
So as you can see with ID 1 ... it would be:
10 - 25 = 0
10 - 15 = 0
10 - 5 = 5
I hope you understand what I am trying to do here ... let me know if I can explain more ...
You seem to want the cumulative sum of the difference, with no negative values allowed. Most databases support window functions, which include cumulative sums:
I am going to assume that id really specifies the ordering. You need some column that serves this purpose because SQL tables represent unordered sets and have no ordering.
But, something like this should work:
select a.*,
sum(case when value2 >= value then 0 else value - value2 end) over
(order by id) as result -- or whatever the column is that specifies the ordering
from #A a;
With help of Gordon and using some part of his idea ... i did something, that at this moment seems to work, but will need a lot of more testing
if object_id('tempdb..#testDataWithRunningTotal') IS NOT NULL drop table #testDataWithRunningTotal
select id, value, value2, cast(null as float) as Result
into #testDataWithRunningTotal
from #A order by id;
declare #runningTotal float = 0, #previousParentId int = null;
update #testDataWithRunningTotal
set
#runningTotal = Result = case when #previousParentId <> id
then value2 - value
else
case when ISNULL(#runningTotal,0) < 0 then value * (-1)
when value2 - value < 0 and ISNULL(#runningTotal,0) = 0 then value2
when value2 - value > 0 and ISNULL(#runningTotal,0) = 0 then value2 - value
else
case when #runningTotal - value < 0 and ISNULL(#runningTotal,0) = 0 then value
else #runningTotal - value
end
end
end,
#previousParentId = id
from #testDataWithRunningTotal
update tst
set Result = case when Result > 0
then 0
else Result * -1
end
from #testDataWithRunningTotal tst
select * from #testDataWithRunningTotal
So, I am keeping #runningTotal running with update, and allowing it to go under 0 ... once it goes less then 0 it means that is moment where SUM of value is greater then SUM of Value2 ... so i keep the record there, and at end of this calculation i do update.

How to get a non matching record in single row

Table1
ID
001
002
001
001
001
...
I want to check the id from table1 where id should be even. If id is different then i need to return 2 else 1
How to write a query for this?
For IDs
SELECT (CASE WHEN [ID]%2 = 1 THEN 1 ELSE 2 END)
FROM [table]
For ID COUNT :
SELECT (CASE WHEN COUNT([ID])%2 = 1 THEN 1 ELSE 2 END)
FROM [table]
GROUP BY [ID]
Please check this.
declare #t table (id varchar(50))
insert into #t values('001'),('001'),('002'),('002'),('001'),('002'),('002')
SELECT
CASE WHEN cast( [ID] as int) %2 = 1 THEN 1 ELSE 2 END oddOrEven
FROM #t
--for counting
;with cte as
(
SELECT [ID]%2 value,
CASE cast( [ID] as int) %2 when 1 THEN count(1) else count(2) END oddCount
FROM #t
group by id
)
select * from cte
If I understand the question correctly, a CASE statement is not necessary here. I'm assuming you want to return 2 when ID is even, and 1 when ID is odd? As long as there aren't any non-digit characters in the values of the ID column, you can do the following:
SELECT [ID], 2 - CAST([ID] AS int) % 2
FROM Table1
If you want to return 2 when ID is odd, and 1 when it is even (sorry, that wasn't clear from the question), then you can do this:
SELECT [ID], CAST([ID] AS int) % 2 + 1
FROM Table1

Combining SUM and CASE returns 0?

Against SQL Server, I'm essentially trying to calculate a value based on Year to Date, so I want to sum any values from July 16, 2012 and prior and display them. I'm using the following query (note that I've replaced parameters with simple integers to calculate the value for today):
SELECT SUM(CASE
WHEN (
(
dns.ODAY <= 16
AND (dns.fiscalyear + 1) = 13
AND dns.omonth = 7
)
OR
(
(dns.fiscalyear + 1) = 13
AND dns.omonth < 7
)
)
THEN dns.QtyShipped
ELSE 0
END) AS Shipped_Units
FROM myTable dns
However, this query is returning 0 for all rows. If I replace dns.QtyShipped with an integer, say 1, it still returns 0. So obviously the case statement isn't being evaluated correctly. Is my logic flawed? Or is it a syntax issue (e.g. I need more parentheses)?
Thanks!
Additional comments:
To test, I've ran the following query:
SELECT SUM(dns.QtyShipped)
FROM myTable dns
where
(dns.ODAY <= 16
AND (dns.fiscalyear + 1) = 13
AND dns.omonth = 7)
OR
((dns.fiscalyear + 1) = 13
AND dns.omonth < 7)
Which returns a very large number. This is confusing.
The code that you mentioned earlier is working absolutely fine. Please double check the values you are using to evaluate the conditions. For example, please confirm if for fiscalyear the value is 2013 or 13. I've used variables instead of column names in the code mentioned below and its returning the expected results:
declare #ODAY integer
set #ODAY=17
declare #fiscalyear int
set #fiscalyear=12
declare #omonth int
set #omonth=8
SELECT SUM(CASE
WHEN (
(
#ODAY <= 16
AND (#fiscalyear + 1) = 13
AND #omonth = 7
)
OR
(
(#fiscalyear + 1) = 13
AND #omonth < 7
)
)
THEN 1
ELSE 0
END) AS Shipped_Units
If I had to guess I would say that your year is being stored as 4 digits. At least that is the problem I ran into when I set up my test.
When I set up this test it worked:
CREATE TABLE myTable (fiscalyear int, omonth int, ODAY int, qtyshipped int)
INSERT INTO myTable VALUES (2012,1,1,1),
(12,1,1,1),
(12,2,1,1),
(12,3,1,1),
(12,4,1,1),
(13,1,1,1),
(12,7,1,1)
When I set up this test it failed:
CREATE TABLE myTable (fiscalyear int, omonth int, ODAY int, qtyshipped int)
INSERT INTO myTable VALUES (2012,1,1,1),
(2012,1,1,1),
(2012,2,1,1),
(2012,3,1,1),
(2012,4,1,1),
(2013,1,1,1),
(2012,7,1,1)
Is there any reason you aren't using actual dates? Your logic would be much simpler and if the dates are stored in your table then the query would probably be faster too.
EDIT: Here is an additional test you can run to be sure its your case causing the problem:
SELECT SUM(CASE
WHEN (
(
dns.ODAY <= 16
AND (dns.fiscalyear + 1) = 13
AND dns.omonth = 7
)
OR
(
(dns.fiscalyear + 1) = 13
AND dns.omonth < 7
)
)
THEN 0
ELSE dns.QtyShipped
END) AS Shipped_Units
FROM myTable dns
Basically flip the case around. Return 0 if you are true and the QtyShipped if not. If you get a value this way then the problem is in your case, if you don't then the problem is probably somewhere else in your query.

Use comparison signs inside a sql case statement

I'm looking for a way to build case statements in a sql select query using less than and greater than signs. For example, I want to select a ranking based on a variable:
DECLARE #a INT
SET #a = 0
SELECT CASE
WHEN #a < 3 THEN 0
WHEN #a = 3 THEN 1
WHEN #a > 3 THEN 2
END
I'd like to write it as:
DECLARE #a INT
SET #a = 0
SELECT CASE #a
WHEN < 3 THEN 0
WHEN 3 THEN 1
WHEN > 3 THEN 2
END
...but SQL doesn't let me use the < and > signs in this way. Is there a way that I can do this is SQL 2005, or do I need to use the code like in the first one.
The reason for only wanting the code there once is because it would make the code a lot more readable/maintainable and also because I'm not sure if SQL server will have to run the calculation for each CASE statement.
I'm looking for a VB.NET case statement equivelent:
Select Case i
Case Is < 100
p = 1
Case Is >= 100
p = 2
End Select
Maybe it's not possible in SQL and that's ok, I just want to confirm that.
You can use the SIGN function as
DECLARE #a INT
SET #a = 0
SELECT CASE SIGN(#a - 3)
WHEN -1 THEN 0
WHEN 0 THEN 1
WHEN 1 THEN 2
END
If #a is smaller than 3, then #a - 3 results in a negative int, in which SIGN returns -1.
If #a is 3 or greater, then SIGN returns 0 or 1, respectively.
If the output you want is 0, 1 and 2, then you can simplify even more:
DECLARE #a INT
SET #a = 0
SELECT SIGN(#a - 3) + 1
Using SIGN as suggested by #Jose Rui Santos seems a nice workaround. An alternative could be to assign the expression an alias, use a subselect and test the expression (using its alias) in the outer select:
SELECT
…,
CASE
WHEN expr < 3 THEN …
WHEN expr > 3 THEN …
END AS …
FROM (
SELECT
…,
a complex expression AS expr
FROM …
…
)
SELECT
CASE
WHEN ColumnName >=1 and ColumnName <=1 THEN 'Fail'
WHEN ColumnName >=6 THEN 'Pass'
ELSE 'Test'
END
FROM TableName

How to know if all the cells have the same value in some column

How to know if all the cells have the same value in some column (title changed)
I want to have a bit scalar value that tells me if all the values in a column equal something:
DECLARE #bit bit
SELECT #bit = TRUEFORALL(Name IS NOT NULL) FROM Contact
UPDATE
I now realized that I actually don't need the TrueForAll, what I do need is to make sure, that all values in a column are equal, for example, I want to know whether all Group.Items have the same price.
Why not?
select count( distinct price) from table
If returns 1, all values are the same... Add
where price is not null
if need be
For your updated requirement something like this would appear to do what you want:
DECLARE #IsSameGroup bit
SELECT #IsSameGroup = CASE WHEN COUNT(*) > 1 THEN 0 ELSE 1 END
FROM (SELECT Name FROM Contact GROUP BY Name) groups
When the count is greater the 1 you have two different names (or prices depending on what you group on)
Not very good for NULLs, but 2008 can do:
SELECT 1 WHERE 'Blue' = ALL ( SELECT Color FROM dbo.Hat )
OR
DECLARE #bit bit
SET #bit =
CASE ( SELECT 1 WHERE 'Blue' = ALL ( SELECT Color FROM dbo.Hat ))
WHEN 1 THEN 1 ELSE 0 END
UPDATE
All same color
SET #bit =
CASE(
SELECT 1 WHERE
(SELECT TOP(1) Color FROM dbo.Hat) = ALL ( SELECT Color FROM dbo.Hat )
)
WHEN 1 THEN 1 ELSE 0 END
Maybe this?
DECLARE #bit bit
if exists(SELECT Name FROM Contact WHERE Name IS NULL)
SET #bit = 0
ELSE
SET #bit = 1
This solves your first question:
SELECT
CASE
WHEN EXISTS(
SELECT 1
FROM Contact
WHERE Name IS NULL
) THEN 0
ELSE 1
END
ADDED:
This will solve your second:
SELECT
CASE
WHEN EXISTS(
SELECT TOP 1 1 FROM (
SELECT
ItemGroupName,
COUNT(Price) AS CNT
FROM ItemGroup
GROUP BY ItemGroupName
HAVING COUNT(Price) > 1
) t
) THEN 0
ELSE 1
END
By the way, when you use the exists function, its better to SELECT 1 (a constant) so less data gets returned