SQL To Track Rules - sql

I am using MS SQL Server and have a stored procedure where I evaluate transactions based on certain rules and mark each row as eligible or not based on these rules. For example, a transaction from prior year is ineligible, certain products may not be eligible.
I also want to record the reason why the transaction is ineligible. For example, from prior year, ineligible product, etc. I have a table that lists all ineligibility codes.
I apply rules sequentially and record the first reason for ineligibility in the field eligCode defined as int.
But I cannot seem to figure out how to code this in the stored procedure. Any help would be greatly appreciated. Thanks in advance.

This should give you head start:
CREATE PROCEDURE [dbo.YourSprocName]
AS
BEGIN
SELECT CASE
WHEN datepart(year, transaction_date) = datepart(year, getdate()) - 1 --if transaction_date year = previous year
THEN 'Ineligible'
WHEN product_type = < something > -- non eligible products
THEN 'Non-eligible'
ELSE 'Eligible'
END AS transaction_status
,CASE
WHEN ineligibility_code = 1 -- assuming 1 is one of the ineligbility code
THEN 'Bad transaction'
ELSE 'Unknown'
END AS ineligibilty_reason_desc
FROM YourTable
WHERE yourColumn = < condition >
END

declare #eligCode as int
declare #TransID as int
....
select #TransID = NNN -- some value declared above
#eligCode = CASE
WHEN YEAR(TRANSACTION_DATE) < YEAR(getdate()) THEN 1
WHEN [CONDITION2] THEN 2
WHEN [CONDITION3] THEN 3
WHEN [CONDITION4] THEN 4
ELSE 0
END
...
Insert into LOG_TABLE (Transaction_ID, eligCode) values (#TransID, #eligCode)

Related

My SQL - How to run different WHERE statements on different weekdays, like one for monday and another for friday

I missunderstod this a lot and thanks again for all the help.
I eventually found a solution for my case and it didn't even involve anything like
IF Monday THEN SELECT
I didn't even needed a CASE-statement.
What I had to do was to put different WHERE statements with an OR in between.
I have stated my final code bolow:
SELECT * FROM
(SELECT
t.pay_date
, t.supp_name
, t.client AS row_client
, t.ip_status
, t.bank_account
, t.remitt_curr AS remitt_curr
, t.remitt_id
, t.apar_id
, t.payment_id
FROM
aipheader t
WHERE
DATEPART(DW, GETDATE()) IN (2,3,4)
AND
t.pay_date BETWEEN '2019-01-01' AND DATEADD(DAY,-4,GETDATE())
OR
DATEPART(DW, GETDATE()) IN (5,6)
AND
t.pay_date BETWEEN '2019-01-01' AND DATEADD(DAY,-2,GETDATE())) x
ORDER BY 1
The statement above will execute the OR-block that is true, in my case the one with the rigt weekday.
********* OLD QUESTIONS BELOW ***********
I want to run different SELECT statements on different days off the week.
I can actually get the following code to work:
select
case
when DATEPART(DW, GETDATE()) = 4 then
(select 'HELLO' )
ELSE
(select 'GOODBYE')
end
On a wendsday the code above returns "HELLO", every other workday it would returns "Goodbye". So far so good!
As long as the select statement only return one value it seams to work, but I realy want a full table like below.
The only differance here should be the [* FROM table] part, and it breaks it all:
select
case
when DATEPART(DW, GETDATE()) = 4 then
(select * FROM table1)
ELSE
(select * FROM table2)
end
If I can't even make the above to work I can not make different SELECTs for different workdays, so this is the hard ting to figure out.
I have tried to encapsulate things with () and add a few SELECTs in different ways but it do not work, so it is probably some SQL principal.
I get an error that I think says that this is ok if it is only one value that's returned:
SqlState 37000 Native 116 [Microsoft][ODBC SQL Server Driver][SQL
Server]Only one expression can be specified in the select list when
the subquery is not introduced with EXISTS.
THANKS FOR ALL OF THE REPLYS I NOW UNDERSTAND IT A BIT BETTER.
I was not looking to add a column, I tought I could make a complete new SELECT statement. I now see that my CASE statement only adds a column, as you point out.
So then my first ide might be a better aproce, to actually modify the WHERE-clause in the select statement.
I have now tried that but it does not work.
SELECT * FROM aipheader t
WHERE
CASE
WHEN DATEPART(DW, GETDATE()) = 4 THEN
t.pay_date = '2019-10-15'
ELSE
t.pay_date = '2019-10-17'
END
It returns the error:
SqlState 37000 Native 102 [Microsoft][ODBC SQL Server Driver][SQL
Server]Incorrect syntax near '='.
I have tried the simpler:
SELECT * FROM aipheader t
WHERE
t.pay_date BETWEEN DATEADD(DAY,-120,GETDATE()) AND DATEADD(DAY,-5,GETDATE())
The above is a simpler form, but that one returns the right values.
But the one obove that one with the CASE-clause return the error about "="-sign.
Maybee the CASE part in a WHERE statement do not work.
After your edit, the correct way to filter your date would be the following:
SELECT
*
FROM
aipheader t
WHERE
t.pay_date = CASE WHEN DATEPART(DW, GETDATE()) = 4 THEN '2019-10-15' ELSE '2019-10-17' END
However, do you really want to hard-code the 2019-10-15 and 2019-10-17 dates? Seems like these should be computed automatically as time goes.
Be careful when using DATEPART with DW, since the week number starting point can actually change depending on the server's and/or current session settings. Check this example:
DECLARE #TestDate DATE = '2020-01-01' -- Wednesday
SET DATEFIRST 1 -- 1: Monday, 7: Sunday
SELECT DATEPART(DW, #TestDate) -- Returns 3
SET DATEFIRST 7 -- 1: Sunday, 7: Saturday
SELECT DATEPART(DW, #TestDate) -- Returns 4!
So whenever checking for a particular day of the week, make sure to force the DATEFIRST session parameter to a particular value so it's consistent with your checks.
I do not fully understand what you are trying to achieve.
Of course your statement will not work, because your select statement in the case when returns multiple columns and rows. That cannot work, because your case when statement will display one column!
So what you can do is the following:
select
case
when DATEPART(DW, GETDATE()) = 4 then
'HELLO'
ELSE
'GOODBYE'
end as [someField],
*
from
[table]
This query will have all rows and columns from table as output + the someField displaying HELLO or GOODBYE.
If you want to display different data from the table on the different dates (wednesday or not wednesday), you can alter the query this way:
select
case
when DATEPART(DW, GETDATE()) = 4 then
[table].[field1]
ELSE
[table].[field2]
end as [someField],
*
from
[table]
Is this ur looking for ? Sub query should return 1 row and specify col name
select
case
when DATEPART(DW, GETDATE()) = 4 then
(select top 1 col1 from table1 )
ELSE
(select top 1 col2 from table2 )
end as result
From table
CASE WHEN can only return a single value for each THEN, and never a Result Set with multiple rows and/or columns. That is just how CASE WHEN works.
You can achieve what you need like this:
DECLARE #DAY INT = DATEPART(DW, GETDATE())
select * FROM table
WHERE (#DAY = 4 AND table.SomeCol = SomeValue)
OR (#DAY = 5 AND table.SomeCol = SomeOtherValue)
... repeat for other days
Or like this:
DECLARE #DAY INT = DATEPART(DW, GETDATE())
IF #DAY = 4
(select * FROM table WHERE table.SomeCol = SomeValue)
ELSE IF #DAY = 5
(select * FROM table WHERE table.SomeCol = SomeOtherValue)
... repeat for other days
Or like this:
DECLARE #DAY INT = DATEPART(DW, GETDATE())
select * FROM table
WHERE #DAY = 4 AND table.SomeCol = SomeValue
UNION ALL
select * FROM table
WHERE #DAY = 5 AND table.SomeCol = SomeOtherValue
UNION ALL
... repeat for other days
My preference would probably be either the 1st or 2nd form.
A query must return before-known columns. If the tables table1 and table2 have different columns, you cannot write one query that returns, say, table1's five columns one day and table2's nine columns another.
As long as the resulting columns stay the same, however, you can join optionally. Here is a small example:
select
p.project_id,
p.project_name,
coalesce(c1.name, c2.name) as team_member,
coalesce(c1.salary, c2.salary * (1.0 + c2.tax / 100.0)) as team_member_salary,
coalesce(c1.job_name, c2.job_title) as team_member_job
from project p
left join crew1 c1 on c1.project_id = p.project_id and datepart(dw, getdate()) = 4
left join crew2 c2 on c2.project_id = p.project_id and datepart(dw, getdate()) <> 4
order by p.project_id;
Not sure I really understand what you are trying to achieve here. This example assumes that you are reading from different tables based on the date and will return all of the rows from both tables:
select * from (
select 'HELLO' as col1, * from table1 where DATEPART(DW, GETDATE()) = 4
UNION ALL
select 'GOODBYE' as col1, * from table2 where DATEPART(DW, GETDATE()) <> 4
)
order by col1

MS SQL Server 2012 query speed / optimization

First, I apologize if this has been answered elsewhere, but I was unable to find anything today. If it has been answered, the lack is me, not the search system.
I am having an issue where in a stored procedure works fairly quickly until I get to a specific point.
I have a database of POS sales data for a restaurant chain.
Among the various tables we have, the ones I need for this query are:
Item (the definitions of the various items; each row includes the SALES category the item belongs to; see caveats below)
Category (the definitions of the various categories items can be in)
CategoryItem (the mapping between the above)
HstItem (the historical sales of the items)
Caveats: There are 2 types of categories each item can be in:
sales categories: each item can be in one sales category at a time.
reporting categories: each item can be in a arbitrary number of these categories simultaneously.
I am needing to get sales totals for 6 date ranges for a specific REPORTING category (week to date, wtd ly, period to date, ptd ly, year to date, ytd ly).
All of that said, for all of my code the query / procedure runs in a decent amount of time, until to the section for the reporting category.
My select statement currently includes the following WHERE clause:
where hstitem.itemid in (select fkitemid from categoryitem where categoryitemid = ##)
I am looking for a more efficient / faster way to execute this.
Any assistance is greatly appreciated. Thanks in advance.
EDIT:
The full original query is as follows:
insert into #GnG (GStoreID, CurWkGNG, CurWkGNGLY,CurYTDGNG,CurYTDGNGLY,CurrPTDGNG,CurrPTDGNGLY)
select
hgi.FKStoreId,
CurWkGnG = sum(case when hgi.DateOfBusiness between #SDate and #EDate then hgi.price else 0 end),
CurWkGnGLY = sum(case when hgi.DateOfBusiness between #SDateLY and #EDateLY then hgi.price else 0 end),
CurYTDGnG =
case
when convert(varchar(10),opendate,126) between convert(varchar(10),#LYTDStart,126) and convert(varchar(10),#LYTDEnd,126) then sum(case when hgi.DateOfBusiness between DATEADD(day, (DATEPART(week, opendate) * 7 + DATEPART(weekday, opendate)) - (DATEPART(week, DATEADD(year, 1, opendate)) * 7 + DATEPART(weekday, DATEADD(year, 1, opendate))), DATEADD(year, 1, opendate)) and #CYTDEnd then hgi.price else 0 end)
else sum(case when hgi.DateOfBusiness between #CYTDStart and #CYTDEnd then hgi.price else 0 end)
end,
CurYTDGnGLY = sum(case when hgi.DateOfBusiness between #LYTDStart and #LYTDEnd then hgi.price else 0 end),
CurrPTDGnG = sum(case when hgi.DateOfBusiness between #CurrPtDStart and #CurrPtDEnd then hgi.price else 0 end),
CurrPTDGnGLY = sum(case when hgi.DateOfBusiness between #CurrPtDLYStart and #CurrPtDlyEnd then hgi.price else 0 end)
from hstGndItem hgi
join #StoresIncluded si
on hgi.FKStoreID = si.StoreID
where hgi.fkitemid in
(select fkitemid from categoryitem where categoryitemid = 25)
group by hgi.fkstoreid, opendate, comping
order by hgi.fkstoreid
Try converting the "IN" to a inner join like so :
FROM hstitem h inner join categoryitem c on c.fkitemid = h.itemid
where c.categoryitemid = ##
You can use WHERE EXISTS instead of IN to check if the ID exists in the table:
WHERE EXISTS
(
SELECT ci.fkitemid
FROM categoryitem ci
WHERE ci.categoryitemid = ## AND ci.fkitemid = hstitem.itemid
)
The difference between the IN clause and using EXISTS is that the sub-query inside the WHERE EXISTS will exit prematurely after a match have been found while the IN clause would wait until the sub-query is finished.
I apologize for the lag in my response here.
I found AN answer. Dont know if it is the right one or not, but it works for us.
I removed the code to use a sub select from the where, and am now generating a new table to hold the values that should pull. The new code to populate the table runs each morning around 0600. I then am having the main code simply join to that table to pull the single answer, rather than perform math based on the sub-query.
Thank you all for the suggestions.

Use IF statement within a calulated field

I am trying to determine all sales within the current financial year and wanted to incorporate an If statement as a calculated field to do so. Please see my code below.
SELECT RtnUserId, COUNT(*) AS Returns_, RtnDt, COUNT(RtnDt) AS DtRet,
IF
BEGIN
YEAR(RtnDt) = YEAR(GETDATE()) AND MONTH(RtnDt) >= 4
RETURN 'INDATE'
END as Financial_Period
FROM dbo.vw_AN_Admin_VendorReturns
WHERE (RtnUserId = 'BEND1') OR
(RtnUserId = 'DEENA') OR
(RtnUserId = 'RICHARDK2')
GROUP BY RtnUserId, RtnDt,
IF
BEGIN
YEAR(RtnDt) = YEAR(GETDATE()) AND MONTH(RtnDt) >= 4
RETURN'INDATE'
END

How to add check for January in SQL statement

I'm creating a report using SQL to pull logged labor hours from our labor database for the previous month. I have it working great, but need to add logic to prevent it from breaking when it runs in January. I've tried adding If/Then statements and CASE logic, but I don't know if I'm just not doing it right, or if our system can't process it. Here's the snippet that pulls the date range:
SELECT
...
FROM
...
WHERE
...
AND
YEAR(ENTERDATE) = YEAR(current date) AND MONTH(ENTERDATE) = (MONTH(current date)-1)
Just use AND as a barrier like this. In January, the second clause will be executed instead of the first one:
SELECT
...
FROM
...
WHERE
...
AND
(
(
(MONTH(current date) > 1) AND
(YEAR(ENTERDATE) = YEAR(current date) AND MONTH(ENTERDATE) = (MONTH(current date)-1))
-- this one gets used from Feb-Dec
)
OR
(
(MONTH(current date) = 1) AND
(YEAR(ENTERDATE) = YEAR(current date) - 1 AND MONTH(ENTERDATE) = 12)
-- alternatively, in Jan only this one gets used
)
)
If your report is always going to be for the previous month, then I think the simplest idea is to declare the year and month of the previous month and then reference those in the Where clause. For example:
Declare LastMo_Month Integer = MONTH(DATEADD(MONTH,-1,getdate()));
Declare LastMo_Year Integer = YEAR(DATEADD(MONTH,-1,getdate()));
Select ...
Where MONTH(EnterDate) = #LastMo_Month
and YEAR(EnterDate) = #LastMo_Year
You could even take it a step further and allow the report to be created for any number of months ago:
Declare Delay Integer = -1;
Declare LastMo_Month Integer = MONTH(DATEADD(MONTH,#Delay,getdate()));
Declare LastMo_Year Integer = YEAR(DATEADD(MONTH,#Delay,getdate()));
Select ...
Where MONTH(EnterDate) = #LastMo_Month
and YEAR(EnterDate) = #LastMo_Year
Hope this helps.
PS - This is my first answer on StackOverflow, so sorry if the formatting isn't right!
if(month(getdate()) = 1)
begin
your jan logic
end
else
begin
your logic
end
The above answer with the Case is ok, but running a CASE on a huge result set would be pretty costly
WHERE
...
AND
DATEPART(yy,ENTERDATE) = DATEPART(yy,DATEADD(m,-1,ENTERDATE))
AND DATEPART(m,ENTERDATE) = DATEPART(m,DATEADD(m,-1,ENTERDATE))
Which Dialect of SQL are you speaking?
As opposed to doing it all with case statements, just use the built it date / time functions to subtract a month from the current date, which should handle crossing year boundaries.
TransACT
WHERE
YEAR(ENTERDATE) = year(dateadd(MONTH,-1, CURRENT_TIMESTAMP))
AND MONTH(ENTERDATE) = month(dateadd(MONTH,-1, CURRENT_TIMESTAMP))
Mysql
WHERE
YEAR(ENTERDATE) = YEAR(date_sub(curdate(),INTERVAL 1 MONTH))
AND MONTH(ENTERDATE) = MONTH(date_sub(curdate(),INTERVAL 1 MONTH) )
Try adding the previous month and year to your SELECT statement:
SELECT
...
,CASE MONTH(current date)
WHEN 1 THEN 12
ELSE MONTH(current date)-1
END AS previous_month
,CASE MONTH(current date)
WHEN 1 THEN YEAR(current date)-1
ELSE YEAR(current date)
END AS previous_year
FROM
...
WHERE
...
AND YEAR(ENTERDATE) = previous_year
AND MONTH(ENTERDATE) = previous_month
This should allow you to set the value before the WHERE comparison. This should be the most performant way to perform this procedure, as it avoids creating two entirely separate clauses or using OR.

Better way to calculate utilisation

I have a rather complicated (and very inefficient) way of getting utilisation from a large list of periods (Code below).
Currently I'm running this for a period of 8 weeks and it's taking between 30 and 40 seconds to return data.
I need to run this regularly for periods of 6 months, 1 year and two years which will obviously take a massive amount of time.
Is there a smarter way to run this query to lower the number of table scans?
I have tried several ways of joining the data, all seem to return junk data.
I've tried to comment the code as much as I can but if anything is unclear let me know.
Table Sizes:
[Stock] ~12,000 records
[Contitems] ~90,000 records
Pseudocode for clarity:
For each week between Start and End:
Get list of unique items active between dates (~12,000 rows)
For each unique item
Loop through ContItems table (~90,000 rows)
Return matches
Group
Group
Return results
The Code
DECLARE #WEEKSTART DATETIME; -- Used to pass start of period to search
DECLARE #WEEKEND DATETIME; -- Used to pass end of period to search
DECLARE #DC DATETIME; -- Used to increment dates
DECLARE #INT INT; -- days to increment for each iteration (7 = weeks)
DECLARE #TBL TABLE(DT DATETIME, SG VARCHAR(20), SN VARCHAR(50), TT INT, US INT); -- Return table
SET #WEEKSTART = '2012-05-01'; -- Set start of period
SET #WEEKEND = '2012-06-25'; -- Set end of period
SET #DC = #WEEKSTART; -- Start counter at first date
SET #INT = 7; -- Set increment to weeks
WHILE (#DC < #WEEKEND) -- Loop through dates every [#INT] days (weeks)
BEGIN
SET #DC = DATEADD(D,#INT,#DC); -- Add 7 days to the counter
INSERT INTO #TBL (DT, SG, SN, TT, US) -- Insert results from subquery into return table
SELECT #DC, SUB.GRPCODE, SubGrp.NAME, SUM(SUB.TOTSTK), SUM(USED)
FROM
(
SELECT STK.GRPCODE, 1 AS TOTSTK, CASE (SELECT COUNT(*)
FROM ContItems -- Contains list of hires with a start and end date
WHERE STK.ITEMNO = ContItems.ITEMNO -- unique item reference
AND ContItems.DELDATE <= DATEADD(MS,-2,DATEADD(D,#INT,#DC)) -- Hires starting before end of week searching
AND (ContItems.DOCDATE#5 >= #DC -- Hires ending after start of week searching
OR ContItems.DOCDATE#5 = '1899-12-30 00:00:00.000')) -- Or hire is still active
WHEN 0 THEN 0 -- None found return zero
WHEN NULL THEN 0 -- NULL return zero
ELSE 1 END AS USED -- Otherwise return 1
FROM Stock STK - List of unique items
WHERE [UNIQUE] = 1 AND [TYPE] != 4 -- Business rules
AND DATEPURCH < #DC AND (DATESOLD = '1899-12-30 00:00:00.000' OR DATESOLD > DATEADD(MS,-2,DATEADD(D,#INT,#DC))) -- Stock is valid between selected week
) SUB
INNER JOIN SubGrp -- Used to get 'pretty' names
ON SUB.GRPCODE = SubGrp.CODE
GROUP BY SUB.GRPCODE, SubGrp.NAME
END
-- Next section gets data from temp table
SELECT SG, SN, SUM(TT) AS TOT, SUM(US) AS USED, CAST(SUM(US) AS FLOAT) / CAST(SUM(TT) AS FLOAT) AS UTIL
FROM #TBL
GROUP BY SG, SN
ORDER BY TOT DESC
I have two suggestions.
First, rewrite the query to move the "select" statement from the case statement to the from clause:
SELECT #DC, SUB.GRPCODE, SubGrp.NAME, SUM(SUB.TOTSTK), SUM(USED)
FROM (SELECT STK.GRPCODE, 1 AS TOTSTK,
(CASE MAX(Contgrp.cnt) -- Or hire is still active
WHEN 0 THEN 0 -- None found return zero
WHEN NULL THEN 0 -- NULL return zero
ELSE 1
END) AS USED -- Otherwise return 1
FROM Stock STK left outer join -- List of unique items
(SELECT itemno, COUNT(*) as cnt
FROM ContItems -- Contains list of hires with a start and end date
WHERE ContItems.DELDATE <= DATEADD(MS,-2,DATEADD(D,#INT,#DC)) AND -- Hires starting before end of week searching
(ContItems.DOCDATE#5 >= #DC OR -- Hires ending after start of week searching
ContItems.DOCDATE#5 = '1899-12-30 00:00:00.000'
)
group by ITEMNO
) ContGrp
on STK.ITEMNO = ContItems.ITEMNO
WHERE [UNIQUE] = 1 AND [TYPE] != 4 AND -- Business rules
DATEPURCH < #DC AND (DATESOLD = '1899-12-30 00:00:00.000' OR DATESOLD > DATEADD(MS,-2,DATEADD(D,#INT,#DC))) -- Stock is valid between selected week
) SUB INNER JOIN SubGrp -- Used to get 'pretty' names
ON SUB.GRPCODE = SubGrp.CODE
GROUP BY SUB.GRPCODE, SubGrp.NAME
In doing this, I found a something suspicious. The case statement is operating at the level of "ItemNo", but the grouping is by "GrpCode". So, the "Count(*)" is really returning the sum at the group level. Is this what you intend?
The second is to dispense with the WHILE loop, if you have multiple weeks. To do this, you just need to convert DatePurch to an appropriate week. However, if the code usually runs on just one or two weeks, this effort may not help very much.
Well, replacing the DATEADD functions in the WHERE clauses at first.
You already have
SET #DC = DATEADD(D,#INT,#DC);
Why not declare another local variable for deletion date:
WHILE (#DC < #WEEKEND) -- Loop through dates every [#INT] days (weeks)
BEGIN
SET #DC = DATEADD(D,#INT,#DC);
DECLARE #DeletionDate DATETIME = DATEADD(MS,-2,DATEADD(D,#INT,#DC));
And use it in the case statement:
CASE (SELECT COUNT(*) .... AND ContItems.DELDATE <= #DeletionDate ....
And also in the outer where clause...
Then you need to make sure that you have correctly indexed your tables.