i am trying to write a procedure that inserts rows into a temp table. the basis of the table is an insurance policy table listing the amount of the premium earned over the life of the policys. the original data consists of the trans_date (date sold) and the policy_start and policy_end dates. i.e. if the policy is 12 months long, we give each month 1/12 of the premium collected.
so something like
while trans_month < policy_end month
insert to tblUEPtmp
select dateadd(mm, 1, trans_date), earned_premium from tblpolicys
set trans_date = dateadd(mm, 1, trans_date)
(i know this is rubbush code but i completely baffled at the moment)
My problem is that i need to create the extra 11 rows of data and modify the transaction date to add 1 month each time until the modified transaction date = policy_end date.
i've researched using a CTE, but while loops aren't posible within a CTE..
is this something a multistatement table function could do?
Many thanks.
You can defo do this with a CTE, for example this little snippet will demonstrate how to do recursion using dates:
declare #start DATETIME = '2012-02-01'
declare #end DATETIME = '2013-02-01'
;with cte (date)
AS
(
SELECT #start
UNION ALL
SELECT DATEADD(mm,1,cte.date)
FROM cte WHERE DATEADD(mm,1,cte.date)<#end
)
select * from cte
That will generate a list of dates between #start & #end with month gaps.
You can
Use your real tables in place of the dummy dates
Perform an insert into...select ... from cte to insert your required data
If you can provide more detail about your table schema, I can probably help out with a more concrete example.
Something like this?
set #trans_date = ...
while #trans_date < #policy_end
begin
insert to tblUEPtmp
select trans_date, earned_premium
from tblpolicys
where {whatever}
set #trans_date = dateadd(mm, 1, #trans_date)
end
Related
Please help, I have a below sample data. How to find week days "Tuesday" and count between two days.
CREATE TABLE EmpDetails1 (id INT, name VARCHAR(25),startdate datetime,enddate datetime)
INSERT INTO EmpDetails1 VALUES(1,'TEST','01/01/2016','01/10/2016');
INSERT INTO EmpDetails1 VALUES(2,'TEST','01/01/2016','01/25/2016');
id name startdate enddate
1 Test 1 1/1/16 1/10/16
2 Test 2 1/1/16 1/25/16
output:
date count
1/5/16 1
1/12/16 3
I have tried with below query but not getting correct result
SELECT name,
DATENAME(WEEKDAY, startdate) as w1,
DATENAME(WEEKDAY, enddate) as w2,
startdate,enddate, count(*) OVER(PARTITION BY startdate,enddate) AS CountOfOrders from EmpDetails1 group by startdate , enddate,name
As mentioned already, you need a calendar. You can generate one dynamically but far better to create a static one since it serves so many useful purposes. This is but one of many discussions about how to generate one.
A calendar is just a special form of a tally table and I use Itzik's discussion as a basis for that. Both of these concepts are things you need to understand in most SQL query writing environments.
Once you have a calendar, you simply join your data table to the calendar and filter as needed. I did not understand exactly what you were trying to accomplish so I simply create the set of rows for "Tuesday".
declare #EmpDetails1 table (id int, name varchar(20), startdate date, enddate date);
insert #EmpDetails1 (id, name, startdate, enddate) values
(1, 'Test 1', '20210101', '20210110'),
(2, 'Test 2', '20210116', '20210126');
select emp.*, cal.*, datename(weekday, cal.caldt) as [day of week(eng)]
from #EmpDetails1 as emp
inner join calendar as cal
on cal.caldt between emp.startdate and emp.enddate
and datename(weekday, cal.caldt) = 'Tuesday'
order by emp.id, cal.caldt
;
Fiddle here to demonstrate. I must highlight the lazy usage of * as the column list but this is just a simple demo. Production code should generally always specify the columns needed completely.
If you examine the calendar table discussion, you will see that the day of week can be easily added to the table - it will never change. This will avoid the effort to calculate it in the query.
T-SQL Code:
CREATE PROCEDURE tuesdayCount #id_number INT AS
BEGIN
DECLARE #S_Date DATETIME
DECLARE #E_Date DATETIME
SET #S_Date = (SELECT startdate FROM EmpDetails1 WHERE id = #id_number)
SET #E_Date = (SELECT enddate FROM EmpDetails1 WHERE id = #id_number)
WHILE #S_Date <= #E_Date
IF (FORMAT(#S_Date, '%a') = 'Tue')
INSERT INTO TuesdayDates VALUES(#S_Date, 1)
SET #S_Date = DATEADD(DAY,1, #S_Date)
END
EXECUTE tuesdayCount #id_number=1;
EXECUTE tuesdayCount #id_number=2;
Code Steps:
First line I created a procedure named tuesdayCount with an input parameter(id_number)
Then I declared 2 variables (S_Date and E_Date) with DATETIME data type, then set them equal to startdate and endnote column values in the first row.(Please take note of id_number sp parameter in the where clause.)
Then I defined a while loop and if_ test to ensure the date is really a Tuesday. If that's so, then I inserted the date value(S_Date) into TuesdayDates table which I created myself beforehand to put the result set there. (maybe not so logical; but I did it anyway.)
After defining the sp proc, I called my functions with id_number parameter which is in fact the row number of your data set (EmpDetails1 Table)
Not a perfect solution :) but I hope It helps somehow.
I have a table that acts as a message log, with the two key tables being TIMESTAMP and TEXT. I'm working on a query that grabs all alerts (from TEXT) for the past 30 days (based on TIMESTAMP) and gives a daily average for those alerts.
Here is the query so far:
--goback 30 days start at midnight
declare #olderdate as datetime
set #olderdate = DATEADD(Day, -30, DATEDIFF(Day, 0, GetDate()))
--today at 11:59pm
declare #today as datetime
set #today = dateadd(ms, -3, (dateadd(day, +1, convert(varchar, GETDATE(), 101))))
print #today
--Grab average alerts per day over 30 days
select
avg(x.Alerts * 1.0 / 30)
from
(select count(*) as Alerts
from MESSAGE_LOG
where text like 'The process%'
and text like '%has alerted%'
and TIMESTAMP between #olderdate and #today) X
However, I want to add something that checks whether there were any alerts for a day and, if there are no alerts for that day, doesn't include it in the average. For example, if there are 90 alerts for a month but they're all in one day, I wouldn't want the average to be 3 alerts per day since that's clearly misleading.
Is there a way I can incorporate this into my query? I've searched for other solutions to this but haven't been able to get any to work.
This isn't written for your query, as I don't have any DDL or sample data, thus I'm going to provide a very simple example instead of how you would do this.
USE Sandbox;
GO
CREATE TABLE dbo.AlertMessage (ID int IDENTITY(1,1),
AlertDate date);
INSERT INTO dbo.AlertMessage (AlertDate)
VALUES('20190101'),('20190101'),('20190105'),('20190110'),('20190115'),('20190115'),('20190115');
GO
--Use a CTE to count per day:
WITH Tots AS (
SELECT AlertDate,
COUNT(ID) AS Alerts
FROM dbo.AlertMessage
GROUP BY AlertDate)
--Now the average
SELECT AVG(Alerts*1.0) AS DayAverage
FROM Tots;
GO
--Clean up
DROP TABLE dbo.AlertMessage;
You're trying to compute a double-aggregate: The average of daily totals.
Without using a CTE, you can try this as well, which is generalized a bit more to work for multiple months.
--get a list of events per day
DECLARE #Event TABLE
(
ID INT NOT NULL IDENTITY(1, 1)
,DateLocalTz DATE NOT NULL--make sure to handle time zones
,YearLocalTz AS DATEPART(YEAR, DateLocalTz) PERSISTED
,MonthLocalTz AS DATEPART(MONTH, DateLocalTz) PERSISTED
)
/*
INSERT INTO #Event(EntryDateLocalTz)
SELECT DISTINCT CONVERT(DATE, TIMESTAMP)--presumed to be in your local time zone because you did not specify
FROM dbo.MESSAGE_LOG
WHERE UPPER([TEXT]) LIKE 'THE PROCESS%' AND UPPER([TEXT]) LIKE '%HAS ALERTED%'--case insenitive
*/
INSERT INTO #Event(DateLocalTz)
VALUES ('2018-12-31'), ('2019-01-01'), ('2019-01-01'), ('2019-01-01'), ('2019-01-12'), ('2019-01-13')
--get average number of alerts per alerting day each month
-- (this will not return months with no alerts,
-- use a LEFT OUTER JOIN against a month list table if you need to include uneventful months)
SELECT
YearLocalTz
,MonthLocalTz
,AvgAlertsOfAlertingDays = AVG(CONVERT(REAL, NumDailyAlerts))
FROM
(
SELECT
YearLocalTz
,MonthLocalTz
,DateLocalTz
,NumDailyAlerts = COUNT(*)
FROM #Event
GROUP BY YearLocalTz, MonthLocalTz, DateLocalTz
) AS X
GROUP BY YearLocalTz, MonthLocalTz
ORDER BY YearLocalTz ASC, MonthLocalTz ASC
Some things to note in my code:
I use PERSISTED columns to get the month and year date parts (because I'm lazy when populating tables)
Use explicit CONVERT to escape integer math that rounds down decimals. Multiplying by 1.0 is a less-readable hack.
Use CONVERT(DATE, ...) to round down to midnight instead of converting back and forth between strings
Do case-insensitive string searching by making everything uppercase (or lowercase, your preference)
Don't subtract 3 milliseconds to get the very last moment before midnight. Change your semantics to interpret the end of a time range as exclusive, instead of dealing with the precision of your datatypes. The only difference is using explicit comparators (i.e. use < instead of <=). Also, DATETIME resolution is 1/300th of a second, not 3 milliseconds.
Avoid using built-in keywords as column names (i.e. "TEXT"). If you do, wrap them in square brackets to avoid ambiguity.
Instead of dividing by 30 to get the average, divide by the count of distinct days in your results.
select
avg(x.Alerts * 1.0 / x.dd)
from
(select count(*) as Alerts, count(distinct CAST([TIMESTAMP] AS date)) AS dd
...
I want to update my database (SQL Server Express) all the dates for specific ids.
I am displaying the ids I want to.
SELECT TOP (1000) ID, Dates
FROM tbl_table
WHERE (id IN (29695, 29700, 29701, 29702, 29703, 29704, 29705, 29706, 29707, 29708, 29709, 29710, 29711, 29712, 29713, 29714, 29715))
AND my dates in the database are like this:
Is there any way to update all the date columns with same date - 1 day?
For example: if we have 2019-12-20, update it to 2019-12-19?
For example if I want to do it in PHP, I would loop through this query and fetch all all dates. After I would remove one day like this:
date('m/d/Y', strtotime($date. ' - 1 days');
And create a query to update all the columns based on id. I just want to avoid that. Is there any SQL command that do that?
Thanks
The request below will update the rows you want by adding -1 days on each date:
UPDATE tbl_table
SET dates = Dateadd(day, -1, dates)
WHERE id IN ( 29695, 29700, 29701, 29702,
29703, 29704, 29705, 29706,
29707, 29708, 29709, 29710,
29711, 29712, 29713, 29714, 29715 )
DATEADD function takes 3 parameters:
the interval ( day, month, year ..)
the increment (the value to add or remove if negative)
an expression (wich is a datetime type)
See DATEADD documentation
To return a query with the previous day:
SELECT TOP (1000) ID, Dates, DATEADD(dd, -1, Dates) AS PreviousDay
FROM tbl_table
To update the table with the previous day:
UPDATE tbl_table
SET Dates = DATEADD(dd, -1, Dates)
FROM -- Put your conditions here
UPDATE tableName SET date= DATEADD(d,-1, date)
where ....
( here you put where clause for you required)
I'm trying to create a stock movement file from historical stock records, see picture:
I've been able to get the green movements and the orange value (had some luck union all to a 0 stock record for max(day) + 1 and removing anything less than today). Movements are then generated with a lag function.
The difficulty is there are no 0 stock records. i.e. fifth row in the example above there is no record so unable to create the correct movement to 0 with the -2. It would create movements of -10, -4, -4, 5, -3, -1, -3
Is there any easier way to identify the gaps and create phantom 0's, I know the dates (these are a mix of weekly that will become daily)?
There are several ways to get the missing gaps in this case it looks like you have a date every week (or 7 days) and in some case there is no date. So you can build a date table with a while loop and do a join and assinging Stock Opening Balance as 0 when null in your table. Here is something similar I am using table variables to simply but using a temp table would probably be better for you and if you do you don't need 2 tables just insert the missing to your temp table.
DECLARE #TableWithGaps AS TABLE ([Date] DATE, StockOpeningBalance INT)
INSERT INTO #TableWithGaps ([Date],StockOpeningBalance)
VALUES ('2016-04-04',10)
,('2016-04-11',6)
,('2016-04-18',2)
,('2016-05-09',7)
,('2016-05-16',4)
,('2016-05-23',3)
DECLARE #Dates AS TABLE ([Date] DATE)
DECLARE #MinDate DATE = '3/28/2016'
DECLARE #MaxDate DATE = '6/20/2016'
--Could also get first record as minimum date and last record, or set #maxdate = GETDATE()
--SELECT #MinDate = DATEADD(DAY,7,MIN([Date])), #MaxDate = MAX([Date]) FROM #TableWithGaps
WHILE #MinDate < DATEADD(DAY,1,#MaxDate)
BEGIN
INSERT INTO #Dates ([Date]) VALUES (#MinDate)
SET #MinDate = DATEADD(DAY,7,#MinDate)
END
SELECT
d.[Date]
,StockOpeningBalance = ISNULL(StockOpeningBalance,0)
,LAG(ISNULL(StockOpeningBalance,0),1,0) OVER (ORDER BY d.[Date])
,Movements = ISNULL(StockOpeningBalance,0) - LAG(ISNULL(StockOpeningBalance,0),1,0) OVER (ORDER BY d.[Date])
FROM
#Dates d
LEFT JOIN #TableWithGaps s
ON d.[Date] = s.[Date]
Also note, I prefer temp tables over table variables and they temp tables will perform better with a larger data set. If you start doing a ton with dates you may want to look at a date dimension table for purposes like this. If you are using Microsoft SSAS it has a built in tool to generate one that you can google/search for pretty quickly.
I am using MS SQL and need some advice on how to construct a query. Essentially I have file of fuel transactions (credit card swipes) and within it the current odometer reading is captured. I am trying to construct a query using the vehicle number (unique id for the vehicle), the transaction date, and the current odometer reading to calculate a new column that looks at a given fuel transaction, finds the prior transaction (based on transaction date) for that vehicle transaction and then calculates the miles that were driven between the two data points.
I am struggling with identifying the prior transaction. Any help would be appreciated to help me get started. I am not looking for the specific script, but just some pseudo code would help get me going.
If you want to get specific, here are the key columns. CompanyVehicleNumber, TransactionDate(format YYYYMMDD), TransactionTime(format HHMMSS), Odometer (e.g. 123456)
Thanks.
You can use APPLY to get the previous transaction:
SELECT
t.*, MilesDiff = t.odometer - x.odometer
FROM tbl t
OUTER APPLY(
SELECT TOP 1 odometer
FROM tbl
WHERE
CompanyVehicleNumber = t.CompanyVehicleNumber
AND (TransactionDate + TransactionTime) < (t.TransactionDate + t.TransactionTime)
ORDER BY (TransactionDate + TransactionTime) DESC
) x(odometer)
Note here that you need to convert the TransactionDate and TransactionTime to a DATETIME to be able to compare the transactions.
Here is one way to convert dates and times:
DECLARE #date VARCHAR(8) = '20130101'
DECLARE #time VARCHAR(6) = '053000'
SELECT
CAST(#date AS DATETIME) +
CAST(LEFT(#time, 2) + ':' + SUBSTRING(#time, 3, 2) + ':' + RIGHT(#time, 2) AS DATETIME)