SQL remove from running total - sql

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.

Related

SQL - Return 0 When results are empty [duplicate]

I need to know how to return a default row if no rows exist in a table. What would be the best way to do this? I'm only returning a single column from this particular table to get its value.
Edit: This would be SQL Server.
One approach for Oracle:
SELECT val
FROM myTable
UNION ALL
SELECT 'DEFAULT'
FROM dual
WHERE NOT EXISTS (SELECT * FROM myTable)
Or alternatively in Oracle:
SELECT NVL(MIN(val), 'DEFAULT')
FROM myTable
Or alternatively in SqlServer:
SELECT ISNULL(MIN(val), 'DEFAULT')
FROM myTable
These use the fact that MIN() returns NULL when there are no rows.
If your base query is expected to return only one row, then you could use this trick:
select NVL( MIN(rate), 0 ) AS rate
from d_payment_index
where fy = 2007
and payment_year = 2008
and program_id = 18
(Oracle code, not sure if NVL is the right function for SQL Server.)
This would be eliminate the select query from running twice and be better for performance:
Declare #rate int
select
#rate = rate
from
d_payment_index
where
fy = 2007
and payment_year = 2008
and program_id = 18
IF ##rowcount = 0
Set #rate = 0
Select #rate 'rate'
How about this:
SELECT DEF.Rate, ACTUAL.Rate, COALESCE(ACTUAL.Rate, DEF.Rate) AS UseThisRate
FROM
(SELECT 0) DEF (Rate) -- This is your default rate
LEFT JOIN (
select rate
from d_payment_index
--WHERE 1=2 -- Uncomment this line to simulate a missing value
--...HERE IF YOUR ACTUAL WHERE CLAUSE. Removed for testing purposes...
--where fy = 2007
-- and payment_year = 2008
-- and program_id = 18
) ACTUAL (Rate) ON 1=1
Results
Valid Rate Exists
Rate Rate UseThisRate
----------- ----------- -----------
0 1 1
Default Rate Used
Rate Rate UseThisRate
----------- ----------- -----------
0 NULL 0
Test DDL
CREATE TABLE d_payment_index (rate int NOT NULL)
INSERT INTO d_payment_index VALUES (1)
This snippet uses Common Table Expressions to reduce redundant code and to improve readability. It is a variation of John Baughman's answer.
The syntax is for SQL Server.
WITH products AS (
SELECT prod_name,
price
FROM Products_Table
WHERE prod_name LIKE '%foo%'
),
defaults AS (
SELECT '-' AS prod_name,
0 AS price
)
SELECT * FROM products
UNION ALL
SELECT * FROM defaults
WHERE NOT EXISTS ( SELECT * FROM products );
*SQL solution
Suppose you have a review table which has primary key "id".
SELECT * FROM review WHERE id = 1555
UNION ALL
SELECT * FROM review WHERE NOT EXISTS ( SELECT * FROM review where id = 1555 ) AND id = 1
if table doesn't have review with 1555 id then this query will provide a review of id 1.
I figured it out, and it should also work for other systems too. It's a variation of WW's answer.
select rate
from d_payment_index
where fy = 2007
and payment_year = 2008
and program_id = 18
union
select 0 as rate
from d_payment_index
where not exists( select rate
from d_payment_index
where fy = 2007
and payment_year = 2008
and program_id = 18 )
One table scan method using a left join from defaults to actuals:
CREATE TABLE [stackoverflow-285666] (k int, val varchar(255))
INSERT INTO [stackoverflow-285666]
VALUES (1, '1-1')
INSERT INTO [stackoverflow-285666]
VALUES (1, '1-2')
INSERT INTO [stackoverflow-285666]
VALUES (1, '1-3')
INSERT INTO [stackoverflow-285666]
VALUES (2, '2-1')
INSERT INTO [stackoverflow-285666]
VALUES (2, '2-2')
DECLARE #k AS int
SET #k = 0
WHILE #k < 3
BEGIN
SELECT #k AS k
,COALESCE(ActualValue, DefaultValue) AS [Value]
FROM (
SELECT 'DefaultValue' AS DefaultValue
) AS Defaults
LEFT JOIN (
SELECT val AS ActualValue
FROM [stackoverflow-285666]
WHERE k = #k
) AS [Values]
ON 1 = 1
SET #k = #k + 1
END
DROP TABLE [stackoverflow-285666]
Gives output:
k Value
----------- ------------
0 DefaultValue
k Value
----------- ------------
1 1-1
1 1-2
1 1-3
k Value
----------- ------------
2 2-1
2 2-2
Assuming there is a table config with unique index on config_code column:
CONFIG_CODE PARAM1 PARAM2
--------------- -------- --------
default_config def 000
config1 abc 123
config2 def 456
This query returns line for config1 values, because it exists in the table:
SELECT *
FROM (SELECT *
FROM config
WHERE config_code = 'config1'
OR config_code = 'default_config'
ORDER BY CASE config_code WHEN 'default_config' THEN 999 ELSE 1 END)
WHERE rownum = 1;
CONFIG_CODE PARAM1 PARAM2
--------------- -------- --------
config1 abc 123
This one returns default record as config3 doesn't exist in the table:
SELECT *
FROM (SELECT *
FROM config
WHERE config_code = 'config3'
OR config_code = 'default_config'
ORDER BY CASE config_code WHEN 'default_config' THEN 999 ELSE 1 END)
WHERE rownum = 1;
CONFIG_CODE PARAM1 PARAM2
--------------- -------- --------
default_config def 000
In comparison with other solutions this one queries table config only once.
Do you want to return a full row? Does the default row need to have default values or can it be an empty row? Do you want the default row to have the same column structure as the table in question?
Depending on your requirements, you might do something like this:
1) run the query and put results in a temp table (or table variable)
2) check to see if the temp table has results
3) if not, return an empty row by performing a select statement similar to this (in SQL Server):
select '' as columnA, '' as columnB, '' as columnC from #tempTable
Where columnA, columnB and columnC are your actual column names.
Insert your default values into a table variable, then update this tableVar's single row with a match from your actual table. If a row is found, tableVar will be updated; if not, the default value remains. Return the table variable.
---=== The table & its data
CREATE TABLE dbo.Rates (
PkId int,
name varchar(10),
rate decimal(10,2)
)
INSERT INTO dbo.Rates(PkId, name, rate) VALUES (1, 'Schedule 1', 0.1)
INSERT INTO dbo.Rates(PkId, name, rate) VALUES (2, 'Schedule 2', 0.2)
Here's the solution:
---=== The solution
CREATE PROCEDURE dbo.GetRate
#PkId int
AS
BEGIN
DECLARE #tempTable TABLE (
PkId int,
name varchar(10),
rate decimal(10,2)
)
--- [1] Insert default values into #tempTable. PkId=0 is dummy value
INSERT INTO #tempTable(PkId, name, rate) VALUES (0, 'DEFAULT', 0.00)
--- [2] Update the single row in #tempTable with the actual value.
--- This only happens if a match is found
UPDATE #tempTable
SET t.PkId=x.PkId, t.name=x.name, t.rate = x.rate
FROM #tempTable t INNER JOIN dbo.Rates x
ON t.PkId = 0
WHERE x.PkId = #PkId
SELECT * FROM #tempTable
END
Test the code:
EXEC dbo.GetRate #PkId=1 --- returns values for PkId=1
EXEC dbo.GetRate #PkId=12314 --- returns default values
This is what I used for getting a default value if no values are present.
SELECT IF (
(SELECT COUNT(*) FROM tbs.replication_status) > 0,
(SELECT rs.last_replication_end_date FROM tbs.replication_status AS rs
WHERE rs.last_replication_start_date IS NOT NULL
AND rs.last_replication_end_date IS NOT NULL
AND rs.table = '%s' ORDER BY id DESC LIMIT 1),
(SELECT CAST(UNIX_TIMESTAMP (CURRENT_TIMESTAMP(6)) AS UNSIGNED))
) AS ts;

How to find Running Multiplication

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.

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.

Getting average from 3 columns in SQL Server

I have a table with 3 columns (smallint) in SQL Server 2005.
Table Ratings
ratin1 smallint,
ratin2 smallint
ratin3 smallint
These columns can have values from 0 to 5.
How can I select the average value of these fields, but only compare fields where the value is greater then 0.
So if the column values are 1, 3 ,5 - the average has to be 3.
if the values are 0, 3, 5 - The average has to be 4.
This is kind of quick and dirty, but it will work...
SELECT (ratin1 + ratin2 + ratin3) /
((CASE WHEN ratin1 = 0 THEN 0 ELSE 1 END) +
(CASE WHEN ratin2 = 0 THEN 0 ELSE 1 END) +
(CASE WHEN ratin3 = 0 THEN 0 ELSE 1 END) +
(CASE WHEN ratin1 = 0 AND ratin2 = 0 AND ratin3 = 0 THEN 1 ELSE 0 END) AS Average
#mwigdahl - this breaks if any of the values are NULL. Use the NVL (value, default) to avoid this:
Sum columns with null values in oracle
Edit: This only works in Oracle. In TSQL, try encapsulating each field with an ISNULL() statement.
There should be an aggregate average function for sql server.
http://msdn.microsoft.com/en-us/library/ms177677.aspx
This is trickier than it looks, but you can do this:
SELECT dbo.MyAvg(ratin1, ratin2, ratin3) from TableRatings
If you create this function first:
CREATE FUNCTION [dbo].[MyAvg]
(
#a int,
#b int,
#c int
)
RETURNS int
AS
BEGIN
DECLARE #result int
DECLARE #divisor int
SELECT #divisor = 3
IF #a = 0 BEGIN SELECT #divisor = #divisor - 1 END
IF #b = 0 BEGIN SELECT #divisor = #divisor - 1 END
IF #c = 0 BEGIN SELECT #divisor = #divisor - 1 END
IF #divisor = 0
SELECT #result = 0
ELSE
SELECT #result = (#a + #b + #c) / #divisor
RETURN #Result
END
select
(
select avg(v)
from (values (Ratin1), (Ratin2), (Ratin3)) as value(v)
) as average
You can use the AVG() function. This will get the average for a column. So, you could nest a SELECT statement with the AVG() methods and then SELECT these values.
Pseudo:
SELECT col1, col2, col3
FROM (
SELECT AVG(col1) AS col1, AVG(col2) AS col2, AVG(col3) AS col3
FROM table
) as tbl
WHERE col1 IN (0, 3, 5)
etc.

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