How to have different calculations in the same column? - sql

I have Users on my platform and each user has a different status attached to them. What I want to achieve is that based on the Users status I want a specific calculation to be done and displayed in the same column. Based on each Users status, the calculation involves 3 dates: Last Review Date, Actual End Date and Today's Date (I will be using the GETUTCDATE() function for this but for the purpose of this post I have added a date into this column).
Below we have the 6 different Statuses and the calculations that go with each:
Draft - If the user is in this status there is no calculation and the value in the column should be NULL
Active - If the user is in this status the calculation should be: the amount of days it has been from todays date and their Last Review Date.
Completed - If the user is in this status there is no calculation and the value in the column should be NULL
Withdrawn - If the user is in this status there is no calculation and the value in the column should be NULL
Temporarily Withdrawn - If the user is in this status the calculation should be: the amount of days it has been from todays date and their Actual End Date.
Archived - If the user is in this status there is no calculation and the value in the column should be NULL
See link to image for what the table looks like Table.
I am using Microsoft SQL Server Management Studio 18.

Consider using CASE on the display part. Something like:
SELECT
(...)
,CASE
WHEN status = 'Active' THEN <calculation for status Active>
WHEN status = 'Temporarily Withdrawn' THEN <calculation for status Temporarily Withdrawn>
ELSE NULL
END AS calculation
,(...)
FROM (...)

Related

Using multiple date parameters in SSRS

I'm trying to build a SSRS report with multiple parameters, and when I add a standard START_DATE and END_DATE parameter I get the expected results from my SQL query line:
WHERE SOME_FIELD BETWEEN (:P_START_DATE) and (:P_END_DATE)
I would like to do something like the following scenario:
Setup:
Date Field 1 & 2: WHERE NEXT_SHIP_DUE_DATE BETWEEN (:P_NEXT_SHIP_START_DATE) AND (:P_NEXT_SHIP_END_DATE)
Date Field 3 & 4: AND PRIMARY_IMAGE_DATE BETWEEN (:P_PID_START_DATE) AND (:P_PID_END_DATE)
All date parameters in SSRS report allow NULL values and are initially set to NULL.
User should be able to:
Get all records when no date field parameters are requested
Get only records based off the date fields that user selects This may be 1 or more.
Scenarios:
Example 1: User selects no date fields - returns no data
Example 2: User selects date fields 1 & 2 only - returns no data
Example 3: User selects all date fields same date range - RETURNS CORRECT DATA
Example 4: User selects all date fields different date ranges - SSRS error: date not valid for month specified
I've tried several different pieces of SQL but no luck so far. Any help would be appreciated!
You need to add some kind of null check, be careful of brackets.
WHERE (NEXT_SHIP_DUE_DATE BETWEEN (:P_NEXT_SHIP_START_DATE) AND (:P_NEXT_SHIP_END_DATE) OR ((:P_NEXT_SHIP_END_DATE) IS NULL AND (:P_NEXT_SHIP_START_DATE) IS NULL))
AND (PRIMARY_IMAGE_DATE BETWEEN (:P_PID_START_DATE) AND (:P_PID_END_DATE) OR ((:P_PID_END_DATE) IS NULL AND (:P_PID_START_DATE) IS NULL))

Query with two different where conditions

I have a table books where users donate number of books:
username books date
____________________________________
Jon 3 2017-06-12
Jon 2 2017-05-20
Mary 4 2017-05-12
I want something like
username This month Previous Month
_______________________________________________
Jon 3 2
You will need some kind of grouping or conditional sum operations. The simplest way to get the result in your question - though which may not actually scale to your needs - is:
select username
,sum(case when dateadd(datediff(month,0,[date]),0) = dateadd(datediff(month,0,getdate()),0) then books else 0 end as ThisMonth
,sum(case when dateadd(datediff(month,0,[date]),0) = dateadd(datediff(month,0,getdate())-1,0) then books else 0 end as PreviousMonth
from books
where username = 'Jon'
group by username
Obviously with more details in your question you could get a better suited answer and one that is not specific to SQL Server should you be on a different DBMS. That said, the conditional and date logic is the same in all other DBMS implementations, you may just need to change the function names.
This works by adding the number of months between an arbitrary start date (the 0s in the functions above) and a date value to get the date at the start of the month. The dates returned by this logic for both your date value and today (the getdate() function) will return the same start of the month where they are both within the same calendar month. By adding one less month (the -1 in the script above) you get the start of last month, which you can then also compare to your date value to get the number of books for last month.
The sum and group by is there to 'flatten' your data into the one row per username value.

Duplication of Sql Records

I am trying to build a reminder application using c# and, i want to employee the concept of repeat in my application [no repeat, daily, weekly ... ], but the problem i am facing is that how i shall store this reminder in the database.
I tried to duplicate the reminder and change it's date, but what if it has no end date then this one doesn't seem a very smart idea. And then i tried to keep one record in the database and when ever the date becomes past in case it's a repeated it modify the date to the next one, but here i facing the problem of how i search for reminders in a specific days. I wondered if there is a way that SQL can duplicate a record between two dates temporarily for the search.
So i am almost out of ideas right now, any help?!
I don't think you should change any data dynamically in the reminder records. You should add a variable called "remDayOfWeek" to the database -- this will be the day of the week that the user started if the user is to be reminded weekly. Let's say you scan once a day for users that need reminders. All users with daily reminders will need reminders. For users with weekly reminders, all those with "remDayOfWeek" equal to the current day of the week will get a reminder.
OK what I would suggest, is this:
Don't create individual reminders for each day you need a reminder. Give the DB the reminder, start/end dates, and the periodicity of the check (daily, weekly, monthly), and another column to keep track of the last time the user saw a reminder.
something like:
column: | ID | title | Desc | Start | End | Period | lastCheck |
---------------------------------------------------------------------------------------
type: | INT | varchar(100) | varchar(300)| Date | Date| INT (or Enum)| Date
The whole idea is, if the user skips a day you don't need to remind them twice, and you don't really care about what happened to expired reminders, just the most recent.
Assuming the following:
no-repeat = 0
daily = 1
weekly = 2
monthly = 3
you could pull all the reminders you need for a particular date by using: (assuming SQL Server, you didn't specify)
SELECT * FROM Reminder
WHERE (GetDate() BETWEEN Start AND End)
AND ((Period = 0 AND lastChecked IS NULL)
OR (Period = 1 AND GetDate() > DATEADD(day,1,lastChecked))
OR (Period = 2 AND GetDate() > DATEADD(week,1,lastChecked))
OR (Period = 3 AND GetDate() > DATEADD(month,1,lastChecked)));
If you want the reminder to be 24 hours/1 week/1 month exactly from the last time checked that will be fine. otherwise use CONVERT (date, GETDATE()) to ignore the time the user checked.
Finally, update lastChecked to the current time after the user dismisses a reminder.

storing data ranges - effective representation

I need to store values for every day in timeline, i.e. every user of database should has status assigned for every day, like this:
from 1.1.2000 to 28.05.2011 - status 1
from 29.05.2011 to 30.01.2012 - status 3
from 1.2.2012 to infinity - status 4
Each day should have only one status assigned, and last status is not ending (until another one is given). My question is what is effective representation in sql database? Obvious solution is to create row for each change (with the last day the status is assigned in each range), like this:
uptodate status
28.05.2011 status 1
30.01.2012 status 3
01.01.9999 status 4
this has many problems - if i would want to add another range, say from 15.02.2012, i would need to alter last row too:
uptodate status
28.05.2011 status 1
30.01.2012 status 3
14.02.2012 status 4
01.01.9999 status 8
and it requires lots of checking to make sure there is no overlapping and errors, especially if someone wants to modify ranges in the middle of the list - inserting a new status from 29.01.2012 to 10.02.2012 is hard to implement (it would require data ranges of status 3 and status 4 to shrink accordingly to make space for new status). Is there any better solution?
i thought about completly other solution, like storing each day status in separate row - so there will be row for every day in timeline. This would make it easy to update - simply enter new status for rows with date between start and end. Of course this would generate big amount of needless data, so it's bad solution, but is coherent and easy to manage. I was wondering if there is something in between, but i guess not.
more context: i want moderator to be able to assign status freely to any dates, and edit it if he would need to. But most often moderator will be adding new status data ranges at the end. I don't really need the last status. After moderator finishes editing whole month time, I need to generate raport based on status on each day in that month. But anytime moderator may want to edit data months ago (which would be reflected on updated raports), and he can put one status for i.e. one year in advance.
You seem to want to use this table for two things - recording the current status and the history of status changes. You should separate the current status out and move it up to the parent (just like the registered date)
User
===============
Registered Date
Current Status
Status History
===============
Uptodate
Status
Your table structure should include the effective and end dates of the status period. This effectively "tiles" the statuses into groups that don't overlap. The last row should have a dummy end date (as you have above) or NULL. Using a value instead of NULL is useful if you have indexes on the end date.
With this structure, to get the status on any given date, you use the query:
select *
from t
where <date> between effdate and enddate
To add a new status at the end of the period requires two changes:
Modify the row in the table with the enddate = 01/01/9999 to have an enddate of yesterday.
Insert a new row with the effdate of today and an enddate of 01/01/9999
I would wrap this in a stored procedure.
To change a status on one date in the past requires splitting one of the historical records in two. Multiple dates may require changing multiple records.
If you have a date range, you can get all tiles that overlap a given time period with the query:
select *
from t
where <periodstart> <= enddate and <periodend> >= effdate

SQL - state machine - reporting on historical data based on changeset

I want to record user states and then be able to report historically based on the record of changes we've kept. I'm trying to do this in SQL (using PostgreSQL) and I have a proposed structure for recording user changes like the following.
CREATE TABLE users (
userid SERIAL NOT NULL PRIMARY KEY,
name VARCHAR(40),
status CHAR NOT NULL
);
CREATE TABLE status_log (
logid SERIAL,
userid INTEGER NOT NULL REFERENCES users(userid),
status CHAR NOT NULL,
logcreated TIMESTAMP
);
That's my proposed table structure, based on the data.
For the status field 'a' represents an active user and 's' represents a suspended user,
INSERT INTO status_log (userid, status, logcreated) VALUES (1, 's', '2008-01-01');
INSERT INTO status_log (userid, status, logcreated) VALUES (1, 'a', '2008-02-01');
So this user was suspended on 1st Jan and active again on 1st of February.
If I wanted to get a suspended list of customers on 15th January 2008, then userid 1 should show up. If I get a suspended list of customers on 15th February 2008, then userid 1 should not show up.
1) Is this the best way to structure this data for this kind of query?
2) How do I query the data in either this structure or in your proposed modified structure so that I can simply have a date (say 15th January) and find a list of customers that had an active status on that date in SQL only? Is this a job for SQL?
This can be done, but would be a lot more efficient if you stored the end date of each log. With your model you have to do something like:
select l1.userid
from status_log l1
where l1.status='s'
and l1.logcreated = (select max(l2.logcreated)
from status_log l2
where l2.userid = l1.userid
and l2.logcreated <= date '2008-02-15'
);
With the additional column it woud be more like:
select userid
from status_log
where status='s'
and logcreated <= date '2008-02-15'
and logsuperseded >= date '2008-02-15';
(Apologies for any syntax errors, I don't know Postgresql.)
To address some further issues raised by Phil:
A user might get moved from active, to suspended, to cancelled, to active again. This is a simplified version, in reality, there are even more states and people can be moved directly from one state to another.
This would appear in the table like this:
userid from to status
FRED 2008-01-01 2008-01-31 s
FRED 2008-02-01 2008-02-07 c
FRED 2008-02-08 a
I used a null for the "to" date of the current record. I could have used a future date like 2999-12-31 but null is preferable in some ways.
Additionally, there would be no "end date" for the current status either, so I think this slightly breaks your query?
Yes, my query would have to be re-written as
select userid
from status_log
where status='s'
and logcreated <= date '2008-02-15'
and (logsuperseded is null or logsuperseded >= date '2008-02-15');
A downside of this design is that whenever the user's status changes you have to end date their current status_log as well as create a new one. However, that isn't difficult, and I think the query advantage probably outweighs this.
Does Postgres support analytic queries? This would give the active users on 2008-02-15
select userid
from
(
select logid,
userid,
status,
logcreated,
max(logcreated) over (partition by userid) max_logcreated_by_user
from status_log
where logcreated <= date '2008-02-15'
)
where logcreated = max_logcreated_by_user
and status = 'a'
/
#Tony the "end" date isn't necessarily applicable.
A user might get moved from active, to suspended, to cancelled, to active again. This is a simplified version, in reality, there are even more states and people can be moved directly from one state to another.
Additionally, there would be no "end date" for the current status either, so I think this slightly breaks your query?
#Phil
I like Tony's solution. It seems to most approriately model the situation described. Any particular user has a status for a given period of time (a minute, an hour, a day, etc.), but it is for a duration, not an instant in time. Since you want to know who was active during a certain period of time, modeling the information as a duration seems like the best approach.
I am not sure that additional statuses are a problem. If someone is active, then suspended, then cancelled, then active again, each of those statuses would be applicable for a given duration, would they not? It may be a vey short duration, such as a few seconds or a minute, but they would still be for a length of time.
Are you concerned that a person's status can change multiple times in a given day, but you want to know who was active for a given day? If so, then you just need to more specifically define what it means to be active on a given day. If it is enough that they were active for any part of that day, then Tony's answer works well as is. If they would have to be active for a certain amount of time in a given day, then Tony's solution could be modified to simply determine the length of time (in hours, or minutes, or days), and adding further restrictions in the WHERE clause to retrieve for the proper date, status, and length of time in that status.
As for there being no "end date" for the current status, that is no problem either as long as the end date were nullable. Simply use something like this "WHERE enddate <= '2008-08-15' or enddate is null".