Advanced discount system in stored procedure - sql-server-2008

I am making a stored procedure for monthly sales. In the stored procedure we have a Discount. This discount can be fetched from three different tables. If the discount is not in id.rabatt, it should fetch from dp.rabatt, if its not there, it should fetch from ds.rabatt. So the first two ones can be empty, while the last one always has a discount..
Im having big trouble designing the WHEN part of the procedure. Please take a look and help me on the way if you have time:
CASE (
when
Isnull(id.rabatt, Isnull(u.rabatt, id.rabatt)) then..
when
Isnull(dp.rabatt, Isnull(x.rabatt, dp.rabatt)) then..
when
Isnull(ds.rabatt, Isnull(y.rabatt, ds.rabatt)) then..
end)
AS 'Discount',
The reason i have to use Isnull is that inside each Discount table, i also have two different discounts, one that lasts forever(2999) and one that have a selected period. Like i show here:
LEFT OUTER JOIN discount AS id
ON id.identifiers = isa.identifiers
AND id.store = BV.name
AND id.from_date <= isa.sales_date
AND id.to_date >= isa.sales_date
AND id.to_date < '2999-01-01'
LEFT OUTER JOIN discount AS u
ON u.identifiers = isa.identifiers
AND u.to_date = '2999-01-01'
The two others tables are designed in similar ways.

You can use the coalesce function in a similar way that you are using the IsNull function. There are some subtle differences between IsNull and Coalesce, but the significant difference that would benefit your code is that you can have multiple parameters without needing to nest it.
Your code: Isnull(id.rabatt, Isnull(u.rabatt, id.rabatt))
Is the same as: Coalesce(id.rabatt, u.rabatt, id.rabatt)
Next... there are 2 general forms for case/when.
Case (Some Condition)
When (Value 1) Then ...
When (Value 2) Then ...
Else (Default Value)
End
Or
Case When (SomeCondition = Value1) Then ...
When (SomeCondition = Value2) Then ...
Else DefaultValue
End
Looking at your code snippet, it appears as though you are using the second form, but you don't have a comparison operator in the when part. I think you want something like this...
CASE When Coalesce(id.rabatt, u.rabatt, id.rabatt) Is Not NULL then..
When Coalesce(dp.rabatt, x.rabatt, id.rabatt) Is Not NULL then..
When Coalesce(ds.rabatt, y.rabatt, id.rabatt) Is Not NULL then..
Else (Put a default value here)
end AS [Discount]

Related

How to use IF search condition as "true" value

I am creating a report generator which allows me to build custom reports by selecting tables and fields which are saved in the database. Each table, field, calculated value, etc. is treated as a separate entity, with no direct connection to any other entity. They are all self-contained.
I have a number of count fields/columns, which I currently have working, using code similar to the following:
sum(case when usasf_teams.division_levelid=7 then 1 else 0 end) as Dance
My problem is that there are quite a few of these columns, and most of the values are zero. This makes it hard to spot the non-zero values, which are what we want to see.
I know I can use the IF statement to return either the number or a space, but I don't want to put the server through the work of doing the same operation twice for every column in every row. I would like to do something like this:
if(sum(case when usasf_teams.division_levelid=7 then 1 else 0 end),
{{use the comparison value}}, ' ') as Dance
This way, the sum/case operation is only done once for each field.
Is this possible?
First, you can simplify your logic to:
sum(usasf_teams.division_levelid = 7) as Dance
Then, I would recommend changing the 0 values to NULL rather than a space:
nullif(sum(usasf_teams.division_levelid = 7), 0) as Dance
If you are going to return a real space, you need to be careful of types. You could do this as;
select (case when sum(usasf_teams.division_levelid = 7) > 0
then cast(sum(usasf_teams.division_levelid = 7) as char)
else ' '
end)
You don't need to worry about doing the sum() twice. The expense in an aggregation is manipulating the data, not (in general) calculating the aggregation functions themselves.

Check multiple columns for validity

Is this a legitimate/good way of checking multiple column values in a row (in order to send to the user the appropriate feedback)?
select
(date_expires < now()) as expired,
(date_deleted is not null) as active
from sometable where row='foo';
It seems awfully convenient to me (this way I can make one query and conditionally alert the user to what exactly is wrong with the row they are trying to access), but I actually haven't seen it very much so I'm worried there is a big downside that I'm not seeing. Thanks!
Your method works, it's just going to give back separate columns. If you want one column to hold the result, then it's some case statement fun.
select
case when date_expires < now() then 'Inactive'
else 'active'
end as status
from sometable where row='foo';
That would give you status with inactive/active as it's two values. I just went with else active, you can put in a series of when this condition then 'this value' if you wanted.

ORA 01797- operator must be followed by any or all

I am testing a condition like this in the where clause of a subquery. But I am getting the error "operator must be followed by any or all" when I execute the SSRS report.
dbase is oracle. And i need to use IN with parameter because the parameter in SSRS report is multivalued. I am using a separate function to generate dates that go in :P_Date.
I need to check if this date is = or < or null . All three conditions need to be tested.
where
trunc(tt.fyh_fecha) IN (:P_Date) OR
trunc(tt.fyh_fecha) <(:P_Date) OR
trunc(tt.fyh_fecha) IS NULL AND
tc.cod_tree = 'blue' AND
tt.color_flower = 'pink'
This doesnt seem directly possible - you are trying to use a parameter containing an array of values against the < operator which only expects one value. Your design doesnt make any logical sense to me either (<= multiple dates?), but anyway ...
I would add a join to a Calendar / Date Dimension table, where I would apply the IN (:P_Date) criteria to get a list of Date values as a deliberate cross join.
Then I would replace:
trunc(tt.fyh_fecha) IN (:P_Date) OR
trunc(tt.fyh_fecha) <(:P_Date) OR
with:
trunc(tt.fyh_fecha) <= Dim_Date.Date_Value

Access - Left Join returns #Error instead of Null

I've asked a similar question already, but I've now simplified the tables/queries enough to put up an example database with (hopefully) descriptive naming:
https://docs.google.com/file/d/0B2PZcGkhNyd4THpWa01fTjVvSWM/edit?usp=sharing
There is one query, ChainsCasesPerMonthPerStorePreviousMonthRange, which works fine. It takes data from two tables and the QueryDatesPrevious query, to return data for the previous period to the one specified in the QueryDates table. Everything seems okay up to this stage.
But when I run the query LeftJoinReturnsError, the three extra chains in the Chains table return #Error instead of returning the expected Null.
If I change QueryDatesPrevious from a query to a table everything works fine, so this seems to be where the problem lies, but I can't seem to solve it, even using an Iif(IsNull, Null, 0) condition.
An extra 50 rep points to the person who solves it, as long as I can work out how to transfer them across. :)
(previous question if you're interested: Access 2007 - Left Join to a query returns #Error instead of Null)
-- EDIT UPDATE --
Output would look something like this, although I don't remember the exact data I put in the test database:
Chain CasesPerMonthPerStore
AgriStore 2.33
Agricultural Export
2B Pencils 3.6
Bob's Markets
So basically, any chain in the Chain table that isn't in the other tables should return Null as part of the left join.
This is rather ugly too, but it seems to work:
Run the following query to create a table named [tblChainsCasesPerMonthPerStorePreviousMonthRange]:
SELECT ChainsCasesPerMonthPerStorePreviousMonthRange.*
INTO tblChainsCasesPerMonthPerStorePreviousMonthRange
FROM ChainsCasesPerMonthPerStorePreviousMonthRange;
Create a query named [updChainsCasesPerMonthPerStorePreviousMonthRange] as an Update query that saves the results of [ChainsCasesPerMonthPerStorePreviousMonthRange] into [tblChainsCasesPerMonthPerStorePreviousMonthRange]:
INSERT INTO tblChainsCasesPerMonthPerStorePreviousMonthRange
SELECT ChainsCasesPerMonthPerStorePreviousMonthRange.*
FROM ChainsCasesPerMonthPerStorePreviousMonthRange;
Paste the following Function into a standard VBA Module
Public Function RemakeTable() As Variant
Dim qdf As DAO.QueryDef
Debug.Print "Executing RemakeTable()..."
CurrentDb.Execute "DELETE FROM tblChainsCasesPerMonthPerStorePreviousMonthRange", dbFailOnError
Set qdf = CurrentDb.QueryDefs("updChainsCasesPerMonthPerStorePreviousMonthRange")
qdf.Execute
Set qdf = Nothing
RemakeTable = Null
End Function
Update your [LeftJoinReturnsError] query to
SELECT
Chains.Chain,
tblChainsCasesPerMonthPerStorePreviousMonthRange.CasesPerMonthPerStore,
RemakeTable() AS Junk
FROM
Chains
LEFT JOIN
tblChainsCasesPerMonthPerStorePreviousMonthRange
ON Chains.Chain=tblChainsCasesPerMonthPerStorePreviousMonthRange.Chain;
The final query has an extra column named [Junk], but at least we get the desired result.
Note: I put the Debug.Print in the VBA Function to verify that it only gets called once, not once for each row in the query.
Great post on the bug. I hadn't seen that, but it's quite obvious that's exactly what's happening. The only way I can see to deal with it since Access doesn't support lateral joins or SQL Server's APPLY clause, is to elevate the expression to the select clause in the top level query. The following gives you the output you want:
SELECT Chains.Chain, (SELECT ChainsCasesPerMonthPerStorePreviousMonthRange.CasesShipped/(DateDiff("m",ChainsCasesPerMonthPerStorePreviousMonthRange.StartDatePrevious,ChainsCasesPerMonthPerStorePreviousMonthRange.EndDatePrevious)+1)/ChainsCasesPerMonthPerStorePreviousMonthRange.NumberOfStores AS Expr1 from ChainsCasesPerMonthPerStorePreviousMonthRange WHERE ChainsCasesPerMonthPerStorePreviousMonthRange.Chain = Chains.Chain) as Expr2
FROM Chains;
It's pretty ugly, but it works. Of course in SQL Server or another DB, you wouldn't need lateral joins because the process outer joins with expressions correctly.
Okay, so I set up another query that just returns the chains that should return a Null value for CasesPerMonthPerStore, but are actually returning #Error in my test database:
SELECT Chains.Chain
FROM Chains LEFT JOIN ChainsNumStoresPreviousMonthRange ON Chains.Chain = ChainsNumStoresPreviousMonthRange.Chain
WHERE ChainsNumStoresPreviousMonthRange.NumberOfStores Is Null;
So in my written example in the question above this query would return:
Chain
Agricultural Export
Bob's Markets
I then UNION these chains, along with a Null field, to the query that returns all of the chains that do return a value for CasesPerMonthPerStore:
SELECT *
FROM (SELECT ChainsCasesPerMonthPerStorePreviousMonthRange.Chain, ChainsCasesPerMonthPerStorePreviousMonthRange.CasesPerMonthPerStore
FROM ChainsCasesPerMonthPerStorePreviousMonthRange
UNION ALL
SELECT ChainsNotInPreviousPeriod.Chain, NULL
FROM ChainsNotInPreviousPeriod) AS UnionQuery
ORDER BY Chain;
So the secret seems to be to separate the calculated field away from the left join. It's the first time I've come across this error, and I don't know whether this approach would work in every situation, but it worked for me. :)
Thanks for your time Brian & Gord,

Setting a default value in a blank field in an Access query

This may have been answered elsewhere, but I can't find it!
I'm combining 2 queries from different sources onto a single query for calculation and reporting purposes. Fields which are not common to both sources show as blank if there is no data, and can't then be used in arithmetic.
I want to set a default of zero where blank, instead of having to knife and fork the query into another table and run an update query over all the blank fields.
There's got to be something simpler! HELP!
Give this a try:
SELECT
nz(value1, 0), nz(value2,0), nz(value3,0)
FROM table1 left outer join table2 on table1.column = table2.column
unless your query resembles what John answered with (a cartesian) then his answer is more appropriate.... (cartesians are dangerous if not used correctly... depending on how big the individual tables are allowed to become you can kill an access application by using them)
You'll either want to use Nz() or iif(), depending on whether that "blank" really is just a blank (empty string) or Null.
So your options are:
SELECT Nz(source1.a + source2.b,0) FROM source1, source2
or:
SELECT iif(source1.a + source2.b <> "", source1.a + source2.b, 0) FROM source1, source2