SQL ROUND decimal and display as varchar - sql-server

I have some decimal fields in my table. I am trying to ROUND them and display in the desired format. Here is an example:
CREATE TABLE #SummaryData(
[ColA] [decimal](18, 1) NULL,
[ColB] [decimal](18, 2) NULL,
[ColC] [decimal](18, 4) NULL,
[ColD] [decimal](18, 4) NULL,
[ColE] [decimal](18, 4) NULL
)
INSERT INTO #SummaryData ([ColA],[ColB],[ColC],[ColD],[ColE])
VALUES (36754.0 ,9090.07, 2.4507 ,33536.0000 ,0.0073)
INSERT INTO #SummaryData ([ColA],[ColB],[ColC],[ColD],[ColE])
VALUES (54978.0 , 12535.32 , 9.9419,47041.0000, 0.0088)
INSERT INTO #SummaryData ([ColA],[ColB],[ColC],[ColD],[ColE])
VALUES (53501.0, 13346.62, 2.8152, 32371.0000, 0.0042)
SELECT [ColA],[ColB],[ColC],[ColD],[ColE]
FROM #SummaryData
-- We need data to be displayed like this for the purpose of reporting
ColA ColB ColC ColD ColE
----------------------------------------
36,754 9090 2.46 33.5% 0.7%
I tried to use ROUND and convert to varchar but did not work. Not sure how to get rid of zeros after decimal and apply "," (commas). Any suggestions?

SELECT SUBSTRING(CONVERT(VARCHAR, CAST(cola as money), 1), 1, LEN(CONVERT(VARCHAR, CAST(cola as money), 1)) - 3) AS ColA,
CONVERT(INT, ColB) AS ColB,
CONVERT(NUMERIC(18,2), ColC) AS ColC,
FORMAT(ColD / 100000., 'p1') AS ColD,
FORMAT(ColE, 'p1') AS ColE
FROM #SummaryData
Here's the output:
36,754 9090 2.45 33.5 % 0.7 %
54,978 12535 9.94 47.0 % 0.9 %
53,501 13346 2.82 32.4 % 0.4 %

SELECT FORMAT(ColA, '#,###') AS ColA,
CAST(ColB AS INT) AS ColB,
CAST(ColC AS INT) AS ColC,
FORMAT(ColD / 100000., 'p1') AS ColD,
FORMAT(ColE, 'p1') AS ColE
FROM #SummaryData
The output is:
ColA ColB ColC ColD ColE
36,754 9090 2 33.5 % 0.7 %
54,978 12535 9 47.0 % 0.9 %
53,501 13346 2 32.4 % 0.4 %

Related

calculate car parking hours in sql server

I have one doubt in sql server. How to calculate car login and logout time details?
table: cardetails
Here need to calculate each car how many hours spend in parking area.
creatE TABLE [dbo].[CarDetails](
[carid] [int] NULL,
[DateTimeDetail] [datetime] NULL,
[Flag] [varchar](50) NULL
)
INSERT [dbo].[CarDetails] ([carid], [DateTimeDetail], [Flag]) VALUES (1, CAST(N'2019-01-20T19:05:00.000' AS DateTime), N'in')
GO
INSERT [dbo].[CarDetails] ([carid], [DateTimeDetail], [Flag]) VALUES (1, CAST(N'2019-01-20T22:30:00.000' AS DateTime), N'out')
GO
INSERT [dbo].[CarDetails] ([carid], [DateTimeDetail], [Flag]) VALUES (2, CAST(N'2019-01-20T20:30:10.000' AS DateTime), N'in')
GO
INSERT [dbo].[CarDetails] ([carid], [DateTimeDetail], [Flag]) VALUES (2, CAST(N'2019-01-21T02:10:10.000' AS DateTime), N'out')
GO
INSERT [dbo].[CarDetails] ([carid], [DateTimeDetail], [Flag]) VALUES (3, CAST(N'2019-01-23T07:07:40.000' AS DateTime), N'in')
GO
INSERT [dbo].[CarDetails] ([carid], [DateTimeDetail], [Flag]) VALUES (3, CAST(N'2019-01-23T10:50:40.000' AS DateTime), N'out')
GO
INSERT [dbo].[CarDetails] ([carid], [DateTimeDetail], [Flag]) VALUES (3, CAST(N'2019-01-23T11:00:10.000' AS DateTime), N'in')
GO
INSERT [dbo].[CarDetails] ([carid], [DateTimeDetail], [Flag]) VALUES (3, CAST(N'2019-01-23T14:15:30.000' AS DateTime), N'out')
GO
INSERT [dbo].[CarDetails] ([carid], [DateTimeDetail], [Flag]) VALUES (2, CAST(N'2019-01-21T08:20:10.000' AS DateTime), N'in')
GO
INSERT [dbo].[CarDetails] ([carid], [DateTimeDetail], [Flag]) VALUES (2, CAST(N'2019-01-21T10:20:10.000' AS DateTime), N'out')
GO
These are the records:
carid DateTimeDetail Flag
1 2019-01-20 19:05:00.000 in
1 2019-01-20 22:30:00.000 out
2 2019-01-20 20:30:10.000 in
2 2019-01-21 02:10:10.000 out
3 2019-01-23 07:07:40.000 in
3 2019-01-23 10:50:40.000 out
3 2019-01-23 11:00:10.000 in
3 2019-01-23 14:15:30.000 out
2 2019-01-21 08:20:10.000 in
2 2019-01-21 10:20:10.000 out
Based on above data I want output like below :
carid | DateTimeDetails | Totaltime(hh:mm:ss)
1 |2019-01-20 | 03:25:00
2 |2019-01-20 | 05 :49:40
2 |2019-01-21 | 02:00:00
3 |2019-01-23 | 06:58:20
I tried like below
select a.carid , sum(datediff(mm,b.datetimedetail,a.datetimedetail))as totalmm from CarDetails a join CarDetails b
on a.carid=b.carid
where a.datetimedetail<=(select max(c.[DateTimeDetail]) from CarDetails c join CarDetails a on a.carid=c.carid
)
group by a.carid
please tell me how to write query to achive this task in sql server
This doesn't quite give you the expected results in your post, however, I have good reason for it not to be, and I suspect your expected results are wrong:
--Because, from experience, people can somehow enter things twice before exiting...
WITH Grp AS(
SELECT CD.carid,
CD.DateTimeDetail,
CD.Flag,
COUNT(CASE Flag WHEN 'Out' THEN 1 END) OVER (PARTITION BY carid ORDER BY CD.DateTimeDetail ASC
ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING) AS Grp
FROM dbo.CarDetails CD),
InOut AS(
SELECT carid,
MIN(CASE Flag WHEN 'In' THEN Grp.DateTimeDetail END) AS TimeIn,
MAX(CASE Flag WHEN 'Out' THEN Grp.DateTimeDetail END) AS [TimeOut]
FROM Grp
GROUP BY carid, grp)
SELECT carid,
CONVERT(date,TimeIn) AS DateTimeDetails,
CONVERT(time(0),DATEADD(SECOND,SUM(DATEDIFF(SECOND,TimeIn, [TimeOut])),'00:00:00')) AS TotalTime
FROM InOut
GROUP BY carid,
CONVERT(date,TimeIn)
ORDER BY carid ASC;
This gives the results below:
carid DateTimeDetails TotalTime
----------- --------------- ----------------
1 2019-01-20 03:25:00
2 2019-01-20 05:40:00
2 2019-01-21 02:00:00
3 2019-01-23 06:58:20
Notice I have a 05:40:00 for car 2 on 2019-01-20. Car 2 enters at 20:30:10 and leaves at 02:10:10, which is 5 hours, and 40 minutes later; not 05 hours 49 minutes and 40 seconds later. if you have that time in your expected results for a reason, you need to explain why.
Note: This will not work if a car stays more than 24 hours! You didn't respond to my question, so I have assumed not. If they can, SQL Server does not support times great than 24 hours, therefore you will be better returning the number of seconds, and having your application display a 24+ hour time.
you can try this
select carid,dateIn, cast(DATEADD(ms, SUM(DATEDIFF(ms, '00:00:00.000', timeout)), '00:00:00.000') as time) timeOut
from (
select carid,cast([DateTimeDetail]as DATE) DateIn ,
(select top 1 [DateTimeDetail]
from [CarDetails] b
where b.carid=a.carid and b.[DateTimeDetail]>a.[DateTimeDetail] and flag='out' )
-[DateTimeDetail] timeOut
from [CarDetails] a
where Flag='in') c
group by carid,dateIn
This gives the results below:
carid dateIn timeOut
1 2019-01-20 03:25:00.0000000
2 2019-01-20 05:40:00.0000000
2 2019-01-21 02:00:00.0000000
3 2019-01-23 06:58:20.0000000

Comparing columns containing NULLS

Suppose I have this table:
CREATE TABLE t (id int, cola int, colb int);
INSERT INTO t VALUES (1, 1, 1), (2, 1, 2), (3, 1, NULL), (4, NULL, 4);
id cola colb
1 1 1 1
2 2 1 2
3 3 1 NULL
4 4 NULL 4
I want to find the rows where cola does not equal colb. I would expect rows 2-4 to be returned when I write:
SELECT * FROM t WHERE cola != colb
But instead only the second row is returned. In order to pull rows 2-4 I have to write:
SELECT * FROM t
WHERE cola != colb
OR (cola IS NULL AND colb IS NOT NULL)
OR (cola IS NOT NULL AND colb IS NULL)
Is there a cleaner way to do this?
Tested here: http://rextester.com/VJYFX19301
probably the easiest way would be to use an isnull() wrapper, say:
SELECT * FROM t WHERE isnull(cola, -1) != isnull(colb, -1)
assuming this is a fairly small table. there are some potential performance caveats to joining on a function, as this is doing, but maybe in your case it's ok?

How do I return a result for one column that is being filtered out by the other columns?

I know my question is confusing, so let's just say let me try to explain with this code and picture below:
SET DATEFIRST 1
SELECT DISTINCT
r.JobNo,
ROUND(r.TotEstHrs, 2) AS TotEstHrs,
SUM(ROUND(t.ManHrs, 2)) AS ManHrs
FROM TimeTicketDet t JOIN OrderRouting r ON r.JobNo = t.JobNo AND r.WorkCntr
= 'Assembly'
WHERE CAST(t.TicketDate AS DATE) >= DATEADD(DAY, 1 - ((DATEPART(WEEKDAY,
GETDATE()) + ##DATEFIRST) % 7), CAST(GETDATE() AS DATE))
AND CAST(t.TicketDate AS DATE) < DATEADD(DAY, 8-DATEPART(dw, GETDATE()),
CONVERT(DATE, GETDATE()))
AND t.PiecesFinished = 1
AND t.WorkCntr IN ('150')
AND ROUND(t.ManHrs*60, 2) > 15
GROUP BY r.JobNo, ROUND(r.TotEstHrs, 2)
ORDER BY r.JobNo
So I want to return only the items that were finished this week (i.e. that have a 1 in the PiecesFinished column). However, I want to be able to sum all the ManHrs for that finished piece. So for example, job 20139-01 was finished this week, however, it was worked on last week as well, it just wasn't finished until this week. I want to be able to capture that information in my ManHrs column. So if you look at 20139-01 in my Desired vs What I'm Getting tables, you'll see that in the ManHrs column, I'm only able to return 1.5 ManHrs for that job, because my code is only summing where PiecesFinished = 1 for this week, the .25 ManHrs is filtered out by both criteria in the Where clause, so I'm unable to capture that time in my result.
I tried doing a subquery, but that didn't work, I was thinking maybe a self-join would work, but I'm having a really hard time wrapping my head around this, hoping someone can guide me in the right direction. Thanks
Somethint like this:
with cte AS(
select JobNo,
sum(ManHrs)
from TimeTicketDet t1
where exists (select *
from TimeTicketDet t2
where t2.JobNo = t1.JobNo and t2.PiecesFinished = 1)
group by JobNo
)
select t.*, r.TotEstHrs
from cte t join OrderRouting r
N r.JobNo = t.JobNo AND r.WorkCntr = 'Assembly';
Try it like this...
IF OBJECT_ID('tempdb..#OrderRouting', 'U') IS NOT NULL
DROP TABLE #OrderRouting;
CREATE TABLE #OrderRouting (
WorkCntr VARCHAR(10) NOT NULL,
JobNo CHAR(8) NOT NULL,
TotEstHrs DECIMAL(9,2) NOT NULL
);
INSERT #OrderRouting (WorkCntr, JobNo, TotEstHrs) VALUES
('Assemply', '20139-01', 1.5),
('Assemply', '20139-02', 2.5),
('Assemply', '20139-03', 2.75),
('Assemply', '20139-04', 1),
('Assemply', '20139-05', 2);
IF OBJECT_ID('tempdb..#TimeTicketDet', 'U') IS NOT NULL
DROP TABLE #TimeTicketDet;
CREATE TABLE #TimeTicketDet (
TicketDate DATE NOT NULL,
WorkCntr VARCHAR(10) NOT NULL,
JobNo CHAR(8) NOT NULL,
ManHrs DECIMAL(9,2) NOT NULL,
PiecesFinished INT NOT NULL
);
INSERT #TimeTicketDet (TicketDate, WorkCntr, JobNo, ManHrs, PiecesFinished) VALUES
('2017-08-31', 'Assembly', '20139-01', 0.25, 0),
('2017-09-08', 'Assembly', '20139-01', 1.5, 1),
('2017-09-05', 'Assembly', '20139-02', 1.75, 0),
('2017-09-08', 'Assembly', '20139-02', 0.5, 1),
('2017-09-08', 'Assembly', '20139-04', 1.25, 1);
--SELECT * FROM #OrderRouting oro;
--SELECT * FROM #TimeTicketDet ttd;
SELECT
oro.JobNo,
oro.TotEstHrs,
tts.ManHrs
FROM
#OrderRouting oro
JOIN (
SELECT
ttd.JobNo,
ManHrs = SUM(ttd.ManHrs)
FROM
#TimeTicketDet ttd
GROUP BY
ttd.JobNo
) tts
ON oro.JobNo = tts.JobNo;
Results...
JobNo TotEstHrs ManHrs
-------- --------------------------------------- ---------------------------------------
20139-01 1.50 1.75
20139-02 2.50 2.25
20139-04 1.00 1.25

Selecting records for deletions based on relationship with previous and next records

I have a SQL Server 2014 table with millions of gps coordinates, each at a particular time. However the interval between the registrations is not fixed and varies from 1 second to a couple of hours. I only want to keep one measurement every 4 minutes, so the other records have to be deleted.
I tried a WHILE loop in T-SQL that traverses every record, with inside the loop a select statement with a double CROSS APPLY to only return a record if it sits in beween 2 other records which are not more than 4 minutes apart. However this strategy turns out to be too slow.
Can this be done with a set-based solution ? Or is there a way to speed-up this query ? (the test query below is just printing, not yet deleting)
SELECT * INTO #myTemp FROM gps ORDER BY TimePoint asc
declare #Id Uniqueidentifier
declare #d1 varchar(19)
declare #d2 varchar(19)
declare #d3 varchar(19)
While EXISTS (select * from #myTemp )
BEGIN
select top 1 #Id = ID FROM #myTemp order by TimePoint asc
SELECT
#d1 = convert(varchar(19), a.justbefore, 121),
#d2 = convert(varchar(19), b.tijdstip, 121),
#d3 = convert(varchar(19), c.justafter, 121)
FROM Gps B CROSS APPLY
(
SELECT top 1 TimePoint as justbefore
FROM Gps
WHERE (B.TimePoint > TimePoint ) AND (B.Id = #Id )
ORDER by TimePoint desc
) A
CROSS APPLY (
SELECT top 1 TimePoint as justafter
FROM Gps
WHERE (Datediff(n,A.justbefore,TimePoint ) between -4 AND 0)
AND (B.TimePoint < TimePoint )
ORDER by TimePoint asc
) C
print 'ID=' + Cast(#id as varchar(50))
+ ' / d1=' + #d1 + ' / d2=' + #d2 + ' / d3=' + #d3
DELETE #myTemp where Id = #id
END
--
Sample data:
Id TimePoint Lat Lon
1 20170725 13:05:27 12,256 24,123
2 20170725 13:10:27 12,254 24,120
3 20170725 13:10:29 12,253 24,125
4 20170725 13:11:55 12,259 24,127
5 20170725 13:11:59 12,255 24,123
6 20170725 13:14:28 12,254 24,126
7 20170725 13:16:52 12,259 24,121
8 20170725 13:20:53 12,257 24,125
In this case records 3,4,5 should be deleted.
Record 7 should stay as the gap between 7 and 8 is longer than 4 minutes.
Looking at the numbers... It looks like 1 & 2 stay (5 mins apart)...3, 4, & 5 should go... 6 stays (4 mins from 2)... 7 should go (only 2 mins from 6) and 8 stays (6 mins from 6)...
If this is correct, the following will do what you're looking for...
IF OBJECT_ID('tempdb..#TestData', 'U') IS NOT NULL
DROP TABLE #TestData;
CREATE TABLE #TestData (
Id INT NOT NULL PRIMARY KEY CLUSTERED,
TimePoint DATETIME2(0) NOT NULL,
Lat DECIMAL(9,3),
Lon DECIMAL(9,3)
);
INSERT #TestData (Id, TimePoint, Lat, Lon) VALUES
(1, '20170725 13:05:27', 12.256, 24.123),
(2, '20170725 13:10:27', 12.254, 24.120),
(3, '20170725 13:10:29', 12.253, 24.125),
(4, '20170725 13:11:55', 12.259, 24.127),
(5, '20170725 13:11:59', 12.255, 24.123),
(6, '20170725 13:14:28', 12.254, 24.126),
(7, '20170725 13:16:52', 12.259, 24.121),
(8, '20170725 13:20:53', 12.257, 24.125);
-- SELECT * FROM #TestData td;
--================================================================================
WITH
cte_AddLag AS (
SELECT
td.Id, td.TimePoint, td.Lat, td.Lon,
MinFromPrev = DATEDIFF(mi, LAG(td.TimePoint, 1) OVER (ORDER BY td.TimePoint), td.TimePoint)
FROM
#TestData td
),
cte_TimeGroup AS (
SELECT
*,
TimeGroup = ISNULL(SUM(al.MinFromPrev) OVER (ORDER BY al.TimePoint ROWS UNBOUNDED PRECEDING) / 4, 0)
FROM
cte_AddLag al
)
SELECT TOP 1 WITH TIES
tg.Id,
tg.TimePoint,
tg.Lat,
tg.Lon
FROM
cte_TimeGroup tg
ORDER BY
ROW_NUMBER() OVER (PARTITION BY tg.TimeGroup ORDER BY tg.TimePoint);
Results...
Id TimePoint Lat Lon
----------- --------------------------- --------------------------------------- ---------------------------------------
1 2017-07-25 13:05:27 12.256 24.123
2 2017-07-25 13:10:27 12.254 24.120
6 2017-07-25 13:14:28 12.254 24.126
8 2017-07-25 13:20:53 12.257 24.125
HTH, Jason

CTE - recursively update quantity until total consumed

I've been researching CTEs trying to determine if it's possible to recursively update inventory quantity records with an order quantity until the order quantity is consumed.
Here are the tables and records:
CREATE TABLE [dbo].[myOrder](
[Account] [float] NOT NULL,
[Item] [float] NOT NULL,
[Quantity] [float] NOT NULL
) ON [PRIMARY]
insert into dbo.myOrder values (12345, 1, 50)
CREATE TABLE [dbo].[myInventory](
[ID] [int] IDENTITY(1,1) NOT NULL,
[Account] [float] NOT NULL,
[InvDate] [numeric](18, 0) NOT NULL,
[Item] [float] NOT NULL,
[Quantity] [float] NOT NULL,
[QuantitySold] [float] NOT NULL
) ON [PRIMARY]
insert into dbo.myInventory values (12345, 111287, 1, 45, 40)
insert into dbo.myInventory values (12345, 111290, 1, 40, 0)
insert into dbo.myInventory values (12345, 111290, 1, 12, 0)
insert into dbo.myInventory values (12345, 111291, 1, 25, 0)
The record in the myOrder table indicates that an order is to be created for account 12345 for item #1, quantity 50:
Account Item Quantity
------- ---- --------
12345 1 50
The inventory table shows that we have plenty of item #1 on hand for account 12345:
ID Account InvDate Item Quantity QuantitySold
-- ------- ------- ---- -------- ------------
1 12345 111287 1 45 40
2 12345 111290 1 40 0
3 12345 111290 1 12 0
4 12345 111291 1 25 0
The goal is to start plugging in the order quantity of 50 into the inventory records until all 50 are consumed. Inventory records are ordered by the value in the InvDate column. Record 1 has 5 remaining quantity (45 - 40 = 5), which would leave us with 45 more to consume for the order. Record 2 can consume 40. Record 3 can consume the last 5. When the query completes the inventory records would look like this:
ID Account InvDate Item Quantity QuantitySold
-- ------- ------- ---- -------- ------------
1 12345 111287 1 45 45
2 12345 111290 1 40 40
3 12345 111290 1 12 5
4 12345 111291 1 25 0
Note: The inventory table stores QuantitySold, not QuantityRemaining, so you have to do the math (Quantity - QuantitySold) to determine how much quantity remains per inventory record.
I've gotten almost nowhere with the CTE. I've found plenty of examples for doing selects where you have 2 parts to your CTE - an initialization part and the recursive part UNIONed together. I could write this with a cursor, but I think it's possible to do with a CTE and I'd like to learn how.
If anyone can confirm this is possible with a CTE or explain how to set up the CTE, I'd appreciate it. Thanks!
--#inserted table mimics inserted virtual table from AFTER INSERT triggers on [dbo].[myOrder] table
DECLARE #inserted TABLE
(
[Account] [float] NOT NULL,
[Item] [float] NOT NULL,
[Quantity] [float] NOT NULL
);
INSERT #inserted
VALUES (12345, 1, 50);
WITH CteRowNumber
AS
(
SELECT inv.ID
,inv.Account
,inv.Item
,inv.Quantity
,inv.QuantitySold
,i.Quantity QuantityOrdered
,ROW_NUMBER() OVER(PARTITION BY inv.Account,inv.Item ORDER BY inv.ID ASC) RowNumber
FROM myInventory inv
INNER JOIN #inserted i ON inv.Account = i.Account
AND inv.Item = i.Item
WHERE inv.Quantity > inv.QuantitySold
), CteRecursive
AS
(
SELECT a.ID
,a.Account
,a.Item
,a.RowNumber
,CASE
WHEN a.Quantity - a.QuantitySold < a.QuantityOrdered THEN a.Quantity - a.QuantitySold
ELSE a.QuantityOrdered
END QuantitySoldNew
,CASE
WHEN a.Quantity - a.QuantitySold < a.QuantityOrdered THEN a.Quantity - a.QuantitySold
ELSE a.QuantityOrdered
END RunningTotal
FROM CteRowNumber a
WHERE a.RowNumber = 1
UNION ALL
SELECT crt.ID
,crt.Account
,crt.Item
,crt.RowNumber
,CASE
WHEN prev.RunningTotal + (crt.Quantity - crt.QuantitySold) < crt.QuantityOrdered THEN crt.Quantity - crt.QuantitySold
ELSE crt.QuantityOrdered - prev.RunningTotal
END QuantitySoldNew
,CASE
WHEN prev.RunningTotal + (crt.Quantity - crt.QuantitySold) < crt.QuantityOrdered THEN prev.RunningTotal + (crt.Quantity - crt.QuantitySold)
ELSE crt.QuantityOrdered
END RunningTotal
FROM CteRecursive prev
INNER JOIN CteRowNumber crt ON prev.Account = crt.Account
AND prev.Item = crt.Item
AND prev.RowNumber + 1 = crt.RowNumber
WHERE prev.RunningTotal < crt.QuantityOrdered
)
SELECT cte.ID
,cte.Account
,cte.Item
,cte.QuantitySoldNew
FROM CteRecursive cte;
--or CteRecursive can be used to update QuantitySold column from [dbo].[myInventory] table
--UPDATE myInventory
--SET QuantitySold = inv.QuantitySold + cte.QuantitySoldNew
--FROM myInventory inv
--INNER JOIN CteRecursive cte ON inv.ID = cte.ID;

Resources