I have a table with 2 columns:
date | points
Data:
2015-01-30 00:00:00.000 | 1.2
2015-01-29 00:00:00.000 | 2
2015-01-30 00:00:00.000 | 5
2015-01-27 00:00:00.000 | 7
I want to sum point column based on date. So if I filter date with 2015-01-30 00:00:00.000 then the result would be like the one below:
2015-01-30 00:00:00.000 | 7.5
The record above is what it should look like.
I have a query but it returns
Error converting data type varchar to float.
My SQL code.
SELECT gpsdate, totkm
FROM (
SELECT gpsdate,
cast(cast(cast((SUM(KMRUN)) as float) as int) as nvarchar(50)) as totkm
,
RANK() OVER(PARTITION BY gpsdate
ORDER BY SUM(KMRUN) DESC) as R
FROM view_tracklist_report
GROUP BY gpsdate) as InnerQuery
WHERE InnerQuery.R = 1
Test Data
CREATE TABLE #Test ([date] DATETIME, points FLOAT);
INSERT INTO #Test
([date], points)
VALUES
('2015-01-30 00:00:00.000', 1.2),
('2015-01-29 00:00:00.000', 2),
('2015-01-30 00:00:00.000', 5),
('2015-01-27 00:00:00.000', 7)
Actual Code: Not sure why it should sum to 7.5 tho? 5+1.2=6.2
SELECT DISTINCT [date], SUM(points) OVER(PARTITION BY [date]) AS TotPoints
FROM #Test
Just group by:
Select gpsdate, Sum(CAST(KMRUN AS float)) as KMRUN
From view_tracklist_report
group BY gpsdate
This is not a good choice to store numerical values as varchars. If you do so you will have to convert them to numbers in every query. And this is not optimal.
So you have to convert your column tu FLOAT. Then you can simply call
SUM(KMRUN)
without any casting.
Now your SUM(KMRUN) is concatenating strings and giving result like
1.2257
Related
I have an ID column, and a time column. I want to group the IDs by average time.
IDs: 1234, 1234, 5678, 5678
Times: 13:21, 19:55, 14:25, 15:04
select ID,
avg(cast(CONCAT(left(cast(Time as varchar),2),substring(cast(Time as varchar),4,2)) as int)*1.0)
It does return a result, but I don't believe the average to be correct as the average time can be outside of normal time constraints (aka the minutes can be > 59).
time stores a point in time, not a duration. What would you do for a duration longer than a day? You should instead store either the duration in seconds, minutes, what have you, and format it as hh:mm etc. when you want to display it. Or better yet, store a start date and end date, which is more complete information, and you can always derive the duration (in whatever format you like) from that.
Anyway, dealing with what you have, and assuming this table and sample data:
CREATE TABLE dbo.BadChoices
(
ID int,
DurationWithWrongType time(0)
);
INSERT dbo.BadChoices(ID, DurationWithWrongType) VALUES
(1234, '13:21'),
(1234, '19:55'),
(5678, '14:25'),
(5678, '15:04');
You could I suppose do:
SELECT ID, AvgDuration = CONVERT(DECIMAL(10,2),
AVG(DATEDIFF(MINUTE, '00:00', DurationWithWrongType)*1.0))
FROM dbo.BadChoices
GROUP BY ID;
Output:
ID
AvgDuration
1234
998.00
5678
884.50
Example db<>fiddle
If you want the display to be HH:MM, and you know for sure your durations will always be < 24 hours, you could do:
;WITH src AS
(
SELECT ID, AvgDuration = CONVERT(DECIMAL(10,2),
AVG(DATEDIFF(MINUTE, '00:00', DurationWithWrongType)*1.0))
FROM dbo.BadChoices
GROUP BY ID
)
SELECT ID, AvgDuration,
AvgDurHHMMSS = CONVERT(time(0), DATEADD(SECOND, AvgDuration*60, '00:00'))
FROM src;
Output:
ID
AvgDuration
AvgDurHHMMSS
1234
998.00
16:38:00
5678
884.50
14:44:30
Example db<>fiddle
We have to cast to datetime to be able to cast to float. We can then find the average and cast back to datetime and then back to time.
A second alternative is to convert the time into minutes, get the average and then use dateadd() and cast back to time
create table times(
t time);
insert into times values
('13:21'),
('19:55'),
('14:25'),
('15:04');
GO
4 rows affected
select
cast(
cast(
avg(
cast(
cast(t
as datetime)
as float)
)
as datetime)
as time)
from times
GO
| (No column name) |
| :--------------- |
| 15:41:15 |
select
cast(
dateadd(second,
avg(
DateDiff(second,0,t)
),
2000-01-01)
as time)
from times
GO
| (No column name) |
| :--------------- |
| 15:41:15 |
db<>fiddle here
I'm working with the following table in SQL Server, which captures the start & end date/time for which an employee worked:
EMP_NO RECORD_DATE START_TIME END_TIME
123456 2020-07-04 10:00:00.0000000 14:30:00.0000000
I need to transpose the date/time values, so that it generates incremental records at 30 minute intervals with the Date/time values concatenated:
Expected Result:
EMP_NO SHIFT_WORKED
123456 2020-07-04 10:00
123456 2020-07-04 10:30
123456 2020-07-04 11:00
123456 2020-07-04 11:30
123456 2020-07-04 12:00
123456 2020-07-04 12:30
123456 2020-07-04 13:00
123456 2020-07-04 13:30
123456 2020-07-04 14:00
Sample code:
CREATE TABLE #HOURS (
EMP_NO INT NOT NULL,
RECORD_DATE DATETIME ,
START_TIME TIME NOT NULL,
END_TIME TIME NOT NULL
)
INSERT INTO #HOURS
VALUES (123456 ,' 2020-07-04',' 10:00',' 14:30')
A tally table will help you here. (I prefer to call it an "integers table"). This is just a table of integers. You can either actually have one persisted somewhere in the database, or create one "one the fly" with a CTE. There are a lot of different ways to create such a thing, some faster than others!
Once you have a tally table, you can generate any values that can be mathematically derived from that. In your case, datetime values in 30 minute increments. You can then join that onto your date range. Here's one way which generates a tally table as a CTE
with integers as (
select top 1000 -- as many as you need
i = -1 + row_number() over (order by a.number) -- start from 0
from master..spt_values a
cross join master..spt_values b
)
select h.emp_no,
shift_worked = dateadd(minute, i * 30, t.startdt)
from #hours h
cross apply ( -- this cross apply just makes the join to integers easier to read
select startdt = h.record_Date + cast(h.start_Time as datetime),
enddt = h.record_date + cast(h.end_time as datetime)
) t
join integers i on dateadd(minute, i * 30, t.startdt) < t.enddt
you can do this:
CREATE TABLE temp (EMP_NO INT ,SHIFT_WORKED datetime)
DECLARE #START_TIME TIME,#END_TIME TIME,#shift TIME,#emp int
SELECT #START_TIME=START_TIME,#END_TIME=END_TIME,#emp=EMP_NO FROM #HOURS
SET #shift=#START_TIME
WHILE DATEADD(MINUTE, 30, #shift) <=#END_TIME
BEGIN
INSERT INTO temp VALUES(#EMP,#shift)
SET #Shift = DATEADD(MINUTE, 30, #shift)
END
I'm trying to get sum of budget transaction line by month .Is there an error on my Query.
The result of my query is :
RECIDLine RecIDHeader Date Amount
5637157326 5637149076 2012-08-01 00:00:00.000 850.00
5637157342 5637149079 2012-12-01 00:00:00.000 1000.00
5637157343 5637149079 2012-12-01 00:00:00.000 80.00
5637157344 5637149079 2012-12-01 00:00:00.000 2700.00
But i want to get somthing like this :
RECIDLine RecIDHeader Date Amount
5637157326 5637149076 2012-08-01 00:00:00.000 850.00
5637157342 5637149079 2012-12-01 00:00:00.000 3780.00
This is my query :
IF OBJECT_ID('tempdb..#BudgetTransTmp') IS NOT NULL
DROP TABLE #BudgetTransTmp
Select
BudgetTransactionLine.RECID AS RecIdLine,
BUDGETTRANSACTIONHEADER.RECID AS RecIdHeader,
BudgetTransactionLine.DATE,
SUM(CAST((BudgetTransactionLine.TransactionCurrencyAmount ) as decimal(18,2))) AS Amount
INTO #BudgetTransTmp
FROM MicrosoftDynamicsAX.dbo.BudgetTransactionLine AS BudgetTransactionLine
INNER JOIN MicrosoftDynamicsAX.dbo.BUDGETTRANSACTIONHEADER AS BUDGETTRANSACTIONHEADER
ON BUDGETTRANSACTIONHEADER.RECID = BudgetTransactionLine.BUDGETTRANSACTIONHEADER
AND BUDGETTRANSACTIONHEADER.budgetTransactionType = '3'
AND BUDGETTRANSACTIONHEADER.PARTITION = #Partition
WHERE BudgetTransactionLine.PARTITION =#Partition
AND BudgetTransactionCode.DATAAREAID = 'USMF'
AND BudgetTransactionLine.DATE >= PeriodCalandarTmp.StartDate
AND BudgetTransactionLine.DATE <= PeriodCalandarTmp.EndDate
GROUP BY BudgetTransactionLine.DATE,
BUDGETTRANSACTIONHEADER.RECID,
BudgetTransactionLine.RECID
select * from #BudgetTransTmp
And I need to keep BudgetTransactionLine.RECID in select
You should not include BudgetTransactionLine.RecId (RecIdLine) in your GROUP BY.
If you need this column, then you must use one of Aggregate Function (for example in the SELECT part MIN(BudgetTransactionLine.RecId) AS RecIdLine.
just put you whole data in Simple CTE and just join with your Temp table. Please go through the query carefully insert your code according to that
Below the Sample data
declare #Table1 TABLE
(RECIDLine BIGint, RecIDHeader BIGint, Date varchar(30), Amount DECIMAL(18,2))
;
INSERT INTO #Table1
(RECIDLine, RecIDHeader, Date, Amount)
VALUES
(5637157326, 5637149076, '2012-08-01 00:00:00.000', 850.00),
(5637157342, 5637149079, '2012-12-01 00:00:00.000', 1000.00),
(5637157343, 5637149079, '2012-12-01 00:00:00.000', 80.00),
(5637157344, 5637149079, '2012-12-01 00:00:00.000', 2700.00)
;
;WITH CTE AS (
select RecIDHeader,Date,SUM(Amount)Amount,ROW_NUMBER()OVER(PARTITION BY RecIDHeader ORDER BY Date)RN from #Table1
GROUP BY RecIDHeader,Date )
Select T.RECIDLine,C.RecIDHeader,C.Amount,C.Amount FROM CTE C
INNER JOIN (select MIN(RECIDLine) RECIDLine, RecIDHeader from #Table1
GROUP BY RecIDHeader)T
ON T.RecIDHeader = C.RecIDHeader
Title sounds confusing but let me please explain:
I have a table that has two columns that provide a date range, and one column that provides a value. I need to query that table and "detail" the data such as this
Is it possible to do only using TSQL?
Additional Info
The table in question is about 2-3million records long (and growing)
Assuming the range of dates is fairly narrow, an alternative is to use a recursive CTE to create a list of all dates in the range and then join interpolate to it:
WITH LastDay AS
(
SELECT MAX(Date_To) AS MaxDate
FROM MyTable
),
Days AS
(
SELECT MIN(Date_From) AS TheDate
FROM MyTable
UNION ALL
SELECT DATEADD(d, 1, TheDate) AS TheDate
FROM Days CROSS JOIN LastDay
WHERE TheDate <= LastDay.MaxDate
)
SELECT mt.Item_ID, mt.Cost_Of_Item, d.TheDate
FROM MyTable mt
INNER JOIN Days d
ON d.TheDate BETWEEN mt.Date_From AND mt.Date_To;
I've also assumed an that date from and date to represent an inclusive range (i.e. includes both edges) - it is unusual to use inclusive BETWEEN on dates.
SqlFiddle here
Edit
The default MAXRECURSION on a recursive CTE in Sql Server is 100, which will limit the date range in the query to a span of 100 days. You can adjust this to a maximum of 32767.
Also, if you are filtering just a smaller range of dates in your large table, you can adjust the CTE to limit the number of days in the range:
WITH DateRange AS
(
SELECT CAST('2014-01-01' AS DATE) AS MinDate,
CAST('2014-02-16' AS DATE) AS MaxDate
),
Days AS
(
SELECT MinDate AS TheDate
FROM DateRange
UNION ALL
SELECT DATEADD(d, 1, TheDate) AS TheDate
FROM Days CROSS APPLY DateRange
WHERE TheDate <= DateRange.MaxDate
)
SELECT mt.Item_ID, mt.Cost_Of_Item, d.TheDate
FROM MyTable mt
INNER JOIN Days d
ON d.TheDate BETWEEN mt.Date_From AND mt.Date_To
OPTION (MAXRECURSION 0);
Update Fiddle
This can be achieved using Cursors.
I've simulated the test data provided and created another table with the name "DesiredTable" to store the data inside, and created the following cusror which achieved exactly what you are looking for:
SET NOCOUNT ON;
DECLARE #ITEM_ID int, #COST_OF_ITEM Money,
#DATE_FROM date, #DATE_TO date;
DECLARE #DateDiff INT; -- holds number of days between from & to columns
DECLARE #counter INT = 0; -- for loop counter
PRINT '-------- Begin the Date Expanding Cursor --------';
-- defining the cursor target statement
DECLARE Date_Expanding_Cursor CURSOR FOR
SELECT [ITEM_ID]
,[COST_OF_ITEM]
,[DATE_FROM]
,[DATE_TO]
FROM [dbo].[OriginalTable]
-- openning the cursor
OPEN Date_Expanding_Cursor
-- fetching next row data into the declared variables
FETCH NEXT FROM Date_Expanding_Cursor
INTO #ITEM_ID, #COST_OF_ITEM, #DATE_FROM, #DATE_TO
-- if next row is found
WHILE ##FETCH_STATUS = 0
BEGIN
-- calculate the number of days in between the date columns
SELECT #DateDiff = DATEDIFF(day,#DATE_FROM,#DATE_TO)
-- reset the counter to 0 for the next loop
set #counter = 0;
WHILE #counter <= #DateDiff
BEGIN
-- inserting rows inside the new table
insert into DesiredTable
Values (#COST_OF_ITEM, DATEADD(day,#counter,#DATE_FROM))
set #counter = #counter +1
END
-- fetching next row
FETCH NEXT FROM Date_Expanding_Cursor
INTO #ITEM_ID, #COST_OF_ITEM, #DATE_FROM, #DATE_TO
END
-- cleanup code
CLOSE Date_Expanding_Cursor;
DEALLOCATE Date_Expanding_Cursor;
The code fetches every row from your original table, then it calculates the number of days between DATE_FROM and DATE_TO columns, then using this number the script will create identical rows to be inserted inside the new table DesiredTable.
give it a try and let me know of the results.
You can generate an increment table and join it to your date From:
Query:
With inc(n) as (
Select ROW_NUMBER() over (order by (select 1)) -1 From (
Select 1 From (values(1), (1), (1), (1), (1), (1), (1), (1), (1), (1)) as x1(n)
Cross Join (values(1), (1), (1), (1), (1), (1), (1), (1), (1), (1)) as x2(n)
) as x(n)
)
Select item_id, cost, DATEADD(day, n, dateFrom), n From #dates d
Inner Join inc i on n <= DATEDIFF(day, dateFrom, dateTo)
Order by item_id
Output:
item_id cost Date n
1 100 2014-01-01 00:00:00.000 0
1 100 2014-01-02 00:00:00.000 1
1 100 2014-01-03 00:00:00.000 2
2 105 2014-01-08 00:00:00.000 2
2 105 2014-01-07 00:00:00.000 1
2 105 2014-01-06 00:00:00.000 0
2 105 2014-01-09 00:00:00.000 3
3 102 2014-02-14 00:00:00.000 3
3 102 2014-02-15 00:00:00.000 4
3 102 2014-02-16 00:00:00.000 5
3 102 2014-02-11 00:00:00.000 0
3 102 2014-02-12 00:00:00.000 1
3 102 2014-02-13 00:00:00.000 2
Sample Data:
declare #dates table(item_id int, cost int, dateFrom datetime, dateTo datetime);
insert into #dates(item_id, cost, dateFrom, dateTo) values
(1, 100, '20140101', '20140103')
, (2, 105, '20140106', '20140109')
, (3, 102, '20140211', '20140216');
Yet another way is to create and maintain calendar table, containing all dates for many years (in our app we have table for 30 years or so, extending every year). Then you can just link to calendar:
select <whatever you need>, calendar.day
from <your tables> inner join calendar on calendar.day between <min date> and <max date>
This approach allows to include additional information (holidays etc) in calendar table - sometimes very helpful.
I have a table that contains a DateField(DataType : DateTime) and TimeField(DataType : Float)
My output should be DateTime . My tables are in SQL Server 2008
Here is an example :
Table A
ID StartDate StartTime
1 2012-06-08 00:00:00.000 1223
2 2012-08-07 00:00:00.000 910
3 2012-05-02 00:00:00.000 1614
4 0094-07-13 00:00:00.000 1245
5 1994-04-18 00.00:00.000 2573
I need to get my output in such a way that I should it should validate for the correct time and correct date and append these two and insert into table B
Table B :
ID StartDateTime
1 2012-06-06 12:23:00.000
2 2012-08-07 09:10:00.000
3 2012-05-02 16:14:00.000
Note that I intentionally left rows 4 and 5 out of the result set; these rows should be ignored because they don't contain valid datetime or time data.
Have you considered correcting the design, and storing the date/time together, or at least storing date and time using the proper data types? In the meantime:
SELECT StartDate + STUFF(RIGHT('0' + RTRIM(StartTime), 4), 3, 0, ':')
FROM dbo.table
WHERE ISDATE(StartDate) = 1 AND CONVERT(INT, StartTime) < 2400
-- wow what a bunch of absolute garbage data you have
-- what Government agency are you paying to provide this data?
AND CONVERT(INT, StartTime) % 100 BETWEEN 0 AND 59;