SQL get values for only a few hours in given time period? - sql

My first question here..!
I'm not an expert in SQL, so bear over with me please! :)
I have a web page (not created by me) which gets report data from a MSSQL database, on the web page you enter start date and end date and data are fetched in this time interval from 00:00 on start date until 23:59 on end date.
I have managed to add more queries to the SQL, but now I would like to, for certain values only to return values which were logged in the time range 00:00:00 until 04:00:00 every day in the selected time interval.
Currently values are logged once an hour, but not always consistently. So far I have made a workaround in my web page which shows the first 4 values and skips the next 20, this loops for the selected interval. This method works 98% of the time, but occasionally there are more or fewer than 24 logged values per day which can cause the shown values will be skewed one way or another.
What I would like to do is change my SQL query so that it only returns values in the time range I want (between midnight and 04:00) for every day in the selected period. I hope someone can help me achieve this or give me some hints! :)
This is the existing SQL query running with the variables which I do want all values for. There are more variables than this but I edited them out, all the Ren*Time variables is the ones I want to make a 4-hour-every-day version of.
SET NOCOUNT ON;
IF OBJECT_ID('tempdb..#tmpValues') IS NOT NULL BEGIN DROP TABLE #tmpValues END;
CREATE TABLE #tmpValues(Id INT PRIMARY KEY IDENTITY(1,1),BatchId INT, TimePoint DATETIME, Ren1Time DECIMAL(10,2), Ren2Time DECIMAL(10,2), Ren3Time DECIMAL(10,2), RenTotTime DECIMAL(10,2));
INSERT INTO #tmpValues(BatchId)
SELECT BatchId
FROM Batch
WHERE Batch.LogTime BETWEEN <StartTime> AND <StopTime>;
CREATE UNIQUE INDEX I_BatcId ON #tmpValues(BatchId);
UPDATE #tmpValues SET
TimePoint = (SELECT LogTime FROM Batch WHERE Batch.BatchId = #tmpValues.BatchId),
Ren1Time = (SELECT SUM(_Float) FROM LogData WHERE LogData.BatchId = #tmpValues.BatchId AND LogData.TagId = 21),
Ren2Time = (SELECT SUM(_Float) FROM LogData WHERE LogData.BatchId = #tmpValues.BatchId AND LogData.TagId = 25),
Ren3Time = (SELECT SUM(_Float) FROM LogData WHERE LogData.BatchId = #tmpValues.BatchId AND LogData.TagId = 29),
RenTotTime = (SELECT SUM(_Float) FROM LogData WHERE LogData.BatchId = #tmpValues.BatchId AND (LogData.TagId = 25 OR LogData.TagId = 29 OR LogData.TagId = 33));
DECLARE
#TimePoint DATETIME,
#Ren1Time FLOAT,
#Ren2Time FLOAT,
#Ren3Time FLOAT,
#RenTotTime FLOAT;
INSERT INTO #tmpValues(TimePoint, Ren1Time, Ren2Time, Ren3Time, RenTotTime)
VALUES(#TimePoint, #Ren1Time, #Ren2Time,#Ren3Time, #RenTotTime);
SET NOCOUNT OFF;
SELECT * FROM #tmpValues;
IF OBJECT_ID('tempdb..#tmpValues') IS NOT NULL BEGIN DROP TABLE #tmpValues END;

Don't mess around with temp tables and processing every column separately. I also have no idea what you're trying to do with those variables. You declare them, never set them, then do an INSERT with them, which will just insert a row of NULL values.
Assuming that you're using SQL Server, the DATEPART function will let you get the hour of the day.
SELECT
B.BatchID,
B.LogTime AS TimePoint,
SUM(CASE WHEN B.TagId = 21 THEN _Float ELSE 0 END) AS Ren1Time,
SUM(CASE WHEN B.TagId = 25 THEN _Float ELSE 0 END) AS Ren2Time,
SUM(CASE WHEN B.TagId = 29 THEN _Float ELSE 0 END) AS Ren3Time,
SUM(CASE WHEN B.TagId IN (21, 25, 29) THEN _Float ELSE 0 END) AS RenTotTime
FROM
dbo.Batch B
INNER JOIN LogData LD ON LD.BatchId = B.BatchId
WHERE
B.LogTime BETWEEN <StartTime> AND <StopTime> AND
DATEPART(HOUR, B.LogTime) BETWEEN 0 AND 4
GROUP BY
B.BatchID,
B.TimePoint

Thank you very much for your swift reply!
I couldn't exactly get your suggestion to work, but I did solve my problem thanks to your suggestion!
Since the SQL query which should be limited to the 4 hours is used only from one webpage and there are no values in this web page which should be shown for all 24 hours, I thought I could copy the existing .SQL file to a new file and simply change the following:
WHERE Batch.LogTime BETWEEN <StartTime> AND <StopTime>;
To
WHERE Batch.LogTime BETWEEN <StartTime> AND <StopTime> AND DATEPART(HOUR, Batch.LogTime) BETWEEN 0 AND 3;
I changed the call to the .SQL file in the web page from the old to the new file and it works! Simple as that! Also changed 0 AND 4 to 0 AND 3 since I found 03:59:59 will be included, which is exactly what I want :)

Related

how to fix my control number problem im using trigger

Good day every one this is my trigger code that after the user will insert a data and select a pr date it will generate a control number in the database but my problem here is when the user pick a pr_date that is 01-28-2021 and after adding record it display a value in the datatabase 202103-1 it must display a value of 202101-1
BEGIN
SET #v1 = (SELECT control_number FROM tbl_worklog where DATE_FORMAT(pr_date,'%Y%m') =DATE_FORMAT(NOW(),'%Y%m') ORDER BY id DESC LIMIT 1);
IF (#v1 is null) THEN
SET new.control_number = (CONCAT(CAST(DATE_FORMAT(NOW(),'%Y%m') as CHAR),"-1"));
ELSE
SET #v2 = (CAST(SUBSTRING_INDEX(#v1,"-",-1) as int)+1);
SET new.control_number = (CONCAT(CAST(DATE_FORMAT(NOW(),'%Y%m') as CHAR),"-",CAST(#v2 as char)));
END IF;
END
To determine the control number, you're using the formula
DATE_FORMAT(NOW(), '%Y%m')
which will look at the current date, which is why you're getting 202103 (it's currently March, the third month). You should instead use
DATE_FORMAT(pr_date, '%Y%m')

SQL update or insert based on last record found

Quick background: Process equipment vendor wrote code to update DB with results of process. Data essentially is: (Order #, Result, Qty) except that the Qty is always 1, and when the batch is large, I get a lot of identical records. Vendor software update is not an option. DB update by vendor is done by calling a stored procedure.
What I want to do in the stored procedure, is if the last record for a specific Order # has identical result as the record currently being inserted, then +1 to Qty of last record (and no new record).
In my research, all examples have included the Result as a part of the Where, which would always combine like results, but my order of results is important. If 3 have result X1, 1 has result X2, and another 2 have result X1, then I need 3 records when finished:
Order Result Qty
101 X1 3
101 X2 1
101 X1 2
I also looked at using a cursor, but that seems to expect one to look through several records, and I will only be looking at one. CTE or a nested query seem possible, but I'm not clear how to achive.
In this case, I can assume that only one machine is processing 1 order, and updates for that order will have pleanty of time to complete before the next update. But ideally the solution would be atomic and able to deal with overlapping update requests.
What I am working towards, but don't know if it is the best path (#p variables are passed in, #l variables are used to store the last record):
select top(1) #lOrder = OrderCol,
#lResult = ResultCol,
#lCreateDate = CREATEDATE
from dbo.LOG
where OrderCol = #pOrder
order by CREATEDATE desc
-- make sure the record is no more than x min old, and every other passed value matches
if ( #lOrder = #pOrder
and #lResult = #pResult
and #lCreateDate > DATEADD(minute, -7, GETDATE())
)
begin
Update dbo.LOG
set [QtyCol] += #pQty
where #lOrder = OrderCol
and #lCreateDate = CREATEDATE
end
else
begin
INSERT INTO dbo.LOG
([OrderCol], [QtyCol], [ResultCol], CREATEDATE )
VALUES(#pOrder, #pQty, #pResult, GETDATE());
end
It seems you need to check to see if the update modified rows. If not then insert. In plain language the code says: "Try to update the LOG table if the order matches an order placed within the last 7 minutes. If it doesn't find a match then insert a new row with the CREATEDATE set to the current date."
select top(1) #lOrder = OrderCol,
#lResult = ResultCol,
#lCreateDate = CREATEDATE
from dbo.LOG
where OrderCol = #pOrder
order by CREATEDATE desc;
Update dbo.LOG
set [QtyCol] += #pQty
where OrderCol = #lOrder
and ResultCol = #lResult
and ResultCol = #pResult
and CREATEDATE >= dateadd(minute, -7, getdate());
if ##rowcount=0
insert INTO dbo.LOG([OrderCol], [QtyCol], [ResultCol], CREATEDATE )
VALUES(#pOrder, #pQty, #pResult, GETDATE());

SQL Query - combine 2 rows into 1 row

I have the following query below (view) in SQL Server. The query produces a result set that is needed to populate a grid. However, a new requirement has come up where the users would like to see data on one row in our app. The tblTasks table can produce 1 or 2 rows. The issue becomes when they're is two rows that have the same job_number but different fldProjectContextId (1 or 31). I need to get the MechApprovalOut and ElecApprovalOut columns on one row instead of two.
I've tried restructuring the query using CTE and over partition and haven't been able to get the necessary results I need.
SELECT TOP (100) PERCENT
CAST(dbo.Job_Control.job_number AS int) AS Job_Number,
dbo.tblTasks.fldSalesOrder, dbo.tblTaskCategories.fldTaskCategoryName,
dbo.Job_Control.Dwg_Sent, dbo.Job_Control.Approval_done,
dbo.Job_Control.fldElecDwgSent, dbo.Job_Control.fldElecApprovalDone,
CASE WHEN DATEDIFF(day, dbo.Job_Control.Dwg_Sent, GETDATE()) > 14
AND dbo.Job_Control.Approval_done IS NULL
AND dbo.tblProjectContext.fldProjectContextID = 1
THEN 1 ELSE 0
END AS MechApprovalOut,
CASE WHEN DATEDIFF(day, dbo.Job_Control.fldElecDwgSent, GETDATE()) > 14
AND dbo.Job_Control.fldElecApprovalDone IS NULL
AND dbo.tblProjectContext.fldProjectContextID = 31
THEN 1 ELSE 0
END AS ElecApprovalOut,
dbo.tblProjectContext.fldProjectContextName,
dbo.tblProjectContext.fldProjectContextId, dbo.Job_Control.Drawing_Info,
dbo.Job_Control.fldElectricalAppDwg
FROM dbo.tblTaskCategories
INNER JOIN dbo.tblTasks
ON dbo.tblTaskCategories.fldTaskCategoryId = dbo.tblTasks.fldTaskCategoryId
INNER JOIN dbo.Job_Control
ON dbo.tblTasks.fldSalesOrder = dbo.Job_Control.job_number
INNER JOIN dbo.tblProjectContext
ON dbo.tblTaskCategories.fldProjectContextId = dbo.tblProjectContext.fldProjectContextId
WHERE (dbo.tblTaskCategories.fldTaskCategoryName = N'Approval'
OR dbo.tblTaskCategories.fldTaskCategoryName = N'Re-Approval')
AND (CASE WHEN DATEDIFF(day, dbo.Job_Control.Dwg_Sent, GETDATE()) > 14
AND dbo.Job_Control.Approval_done IS NULL
AND dbo.tblProjectContext.fldProjectContextID = 1
THEN 1 ELSE 0
END = 1)
OR (dbo.tblTaskCategories.fldTaskCategoryName = N'Approval'
OR dbo.tblTaskCategories.fldTaskCategoryName = N'Re-Approval')
AND (CASE WHEN DATEDIFF(day, dbo.Job_Control.fldElecDwgSent, GETDATE()) > 14
AND dbo.Job_Control.fldElecApprovalDone IS NULL
AND dbo.tblProjectContext.fldProjectContextID = 31
THEN 1 ELSE 0
END = 1)
ORDER BY dbo.Job_Control.job_number, dbo.tblTaskCategories.fldProjectContextId
The above query gives me the following result set:
I've created a work around via code (which I don't like but it works for now) where i've used code to populate a "temp" table the way i need it to display the data, that is, one record if duplicate job numbers to get the MechApprovalOut and ElecApprovalOut columns on one row (see first record in following screen shot).
Example:
With the desired result set and one row per job_number, this is how the form looks with the data and how I am using the result set.
Any help restructuring my query to combine duplicate rows with the same job number where MechApprovalOut and ElecApproval out columns are on one row is greatly appreciated! I'd much prefer to use a view on SQL then code in the app to populate a temp table.
Thanks,
Jimmy
What I would do is LEFT JOIN the main table to itself at the beginning of the query, matching on Job Number and Sales Order, such that the left side of the join is only looking at Approval task categories and the right side of the join is only looking at Re-Approval task categories. Then I would make extensive use of the COALESCE() function to select data from the correct side of the join for use later on and in the select clause. This may also be the piece you were missing to make a CTE work.
There is probably also a solution that uses a ranking/windowing function (maybe not RANK itself, but something that category) along with the PARTITION BY clause. However, as those are fairly new to Sql Server I haven't used them enough personally to be comfortable writing an example solution for you without direct access to the data to play with, and it would still take me a little more time to get right than I can devote to this right now. Maybe this paragraph will motivate someone else to do that work.

I want to update values in column, based on condition that need to compare data from another table

I need help on one case in SQL, so I have to fill one column DIFFERENCE with 'Above' or 'Below' in table CLIENTS, if the date in other column in table - DOCUMENT is above or below 4 months from now. I tried with this
UPDATE CLIENTS
SET DIFFERENCE = CASE WHEN MONTHS_BETWEEN(TO_DATE((SELECT DATA FROM DOCUMENT, CLIENTS WHERE DOCUMENT.ID_CLIENT=CLIENTS.ID_CLIENT ),'DD.MM.YYYY'),TO_DATE(SYSDATE,'DD.MM.YYYY')) < 4 THEN 'Below' ELSE 'Above' END
but it returns lot of values, so I tried to JOIN the tables and
UPDATE CLIENTS
SET DIFFERENCE = CASE WHEN MONTHS_BETWEEN(TO_DATE(DATA,'DD.MM.YYYY'),TO_DATE(SYSDATE,'DD.MM.YYYY')) < 4 THEN 'Below' ELSE 'Above' END
FROM CLIENTS JOIN DOCUMENT
ON DOCUMENT.ID_CLIENT=CLIENTS.ID_CLIENT
but this time says Not properly ended.
I'm working with Oracle db.
Please if you see the answer, write me!
Thank you in advance!
SELECT CLIENTS.ID_CLIENT,MIN(DOCUMENT.DATA) AS "DATA"
FROM DOCUMENT,CLIENTS
WHERE CLIENTS.ID_CLIENT=DOCUMENT.ID_CLIENT
GROUP BY CLIENTS.ID_CLIENT
and some of the results:
ID_CLIENT DATA
54 01/23/2014
57 01/23/2014
78 01/23/2014
87 01/24/2014
91 01/24/2014
I found the solution,
UPDATE CLIENTS
SET DIFFERENCE = CASE WHEN MONTHS_BETWEEN(TO_DATE((SELECT MIN(DATA) FROM DOCUMENT, CLIENTS WHERE DOCUMENT.ID_CLIENT=CLIENTS.ID_CLIENT),'MM.DD.YYYY'),TO_DATE(SYSDATE,'MM.DD.YYYY')) < 4 THEN 'Below' ELSE 'Above' END
The mistake was 'MM.DD.YYYY' ... first I used 'DD.MM.YYYY' - very stupid mistake!
Thanks for all the answers! ekad YOU really helped me!!!
Instead of joining the tables, you need to check whether there's any related documents with DATA more than 4 months from now using EXISTS. It also seems that DOCUMENT.DATA is a varchar and the value is set using mm/dd/yyyy format, so you need to change the second parameter of TO_DATE function to MM/DD/YYYY
UPDATE CLIENTS
SET DIFFERENCE = CASE WHEN EXISTS
(SELECT 1 FROM DOCUMENT
WHERE ID_CLIENT = CLIENTS.ID_CLIENT
AND MONTHS_BETWEEN(TO_DATE(DATA,'MM/DD/YYYY'),SYSDATE) > 4)
THEN 'Above'
ELSE 'Below' END

SQL - 2 table values to be grouped by third unconnected value

I want to create a graph that pulls data from 2 user questions generated from within an SQL database.
The issue is that the user questions are stored in the same table, as are the answers. The only connection is that the question string includes a year value, which I extract using the LEFT command so that I output a column called 'YEAR' with a list of integer values running from 2013 to 2038 (25 year period).
I then want to pull the corresponding answers ('forecast' and 'actual') from each 'YEAR' so that I can plot a graph with a couple of values from each year (sorry if this isn't making any sense). The graph should show a forecast line covering the 25 year period with a second line (or column) showing the actual value as it gets populated over the years. I'll then be able to visualise if our actual value is close to our original forecast figures (long term goal!)
CODE BELOW
SELECT CAST((LEFT(F_TASK_ANS.TA_ANS_QUESTION,4)) AS INTEGER) AS YEAR,
-- first select takes left 4 characters of question and outputs value as string then coverts value to whole number.
CAST((CASE WHEN F_TASK_ANS.TA_ANS_QUESTION LIKE '%forecast' THEN F_TASK_ANS.TA_ANS_ANSWER END) AS NUMERIC(9,2)) AS 'FORECAST',
CAST((CASE WHEN F_TASK_ANS.TA_ANS_QUESTION LIKE '%actual' THEN ISNULL(F_TASK_ANS.TA_ANS_ANSWER,0) END) AS NUMERIC(9,2)) AS 'ACTUAL'
-- actual value will be null until filled in each year therefore ISNULL added to replace null with 0.00.
FROM F_TASK_ANS INNER JOIN F_TASKS ON F_TASK_ANS.TA_ANS_FKEY_TA_SEQ = F_TASKS.TA_SEQ
WHERE TA_ANS_ANSWER <> ''
AND (TA_TASK_ID LIKE '%6051' OR TA_TASK_ID LIKE '%6052')
-- The two numbers above refer to separate PPM questions that the user enters a value into
I tried GROUP BY 'YEAR' but I get an
Error: Each GROUP BY expression must contain at least one column that
is not an outer reference - which I assume is because I haven't linked
the 2 tables in any way...
Should I be adding a UNION so the tables are joined?
What I want to see is something like the following output (which I'll graph up later)
YEAR FORECAST ACTUAL
2013 135000 127331
2014 143000 145102
2015 149000 0
2016 158000 0
2017 161000 0
2018... etc
Any help or guidance would be hugely appreciated.
Thanks
Although the syntax is pretty hairy, this seems like a fairly simple query. You are in fact linking your two tables (with the JOIN statement) and you don't need a UNION.
Try something like this (using a common table expression, or CTE, to make the grouping clearer, and changing the syntax for slightly greater clarity):
WITH data
AS (
SELECT YEAR = CAST((LEFT(A.TA_ANS_QUESTION,4)) AS INTEGER)
, FORECAST = CASE WHEN A.TA_ANS_QUESTION LIKE '%forecast'
THEN CONVERT(NUMERIC(9,2), A.TA_ANS_ANSWER)
ELSE CONVERT(NUMERIC(9,2), 0)
END
, ACTUAL = CASE WHEN A.TA_ANS_QUESTION LIKE '%actual'
THEN CONVERT(NUMERIC(9,2), ISNULL(A.TA_ANS_ANSWER,0) )
ELSE CONVERT(NUMERIC(9,2), 0)
END
FROM F_TASK_ANS A
INNER JOIN F_TASKS T
ON A.TA_ANS_FKEY_TA_SEQ = T.TA_SEQ
-- It sounded like you wanted to include the ones where the answer was null. If
-- that's wrong, get rid of the test for NULL.
WHERE (A.TA_ANS_ANSWER <> '' OR A.TA_ANS_ANSWER IS NULL)
AND (TA_TASK_ID LIKE '%6051' OR TA_TASK_ID LIKE '%6052')
)
SELECT YEAR
, FORECAST = SUM(data.Forecast)
, ACTUAL = SUM(data.Actual)
FROM data
GROUP BY YEAR
ORDER BY YEAR
Try something like this ...
SELECT CAST((LEFT(F_TASK_ANS.TA_ANS_QUESTION,4)) AS INT) AS [YEAR]
,SUM(CAST((CASE WHEN F_TASK_ANS.TA_ANS_QUESTION LIKE '%forecast'
THEN F_TASK_ANS.TA_ANS_ANSWER ELSE 0 END) AS NUMERIC(9,2))) AS [FORECAST]
,SUM(CAST((CASE WHEN F_TASK_ANS.TA_ANS_QUESTION LIKE '%actual'
THEN F_TASK_ANS.TA_ANS_ANSWER ELSE 0 END) AS NUMERIC(9,2))) AS [ACTUAL]
FROM F_TASK_ANS INNER JOIN F_TASKS
ON F_TASK_ANS.TA_ANS_FKEY_TA_SEQ = F_TASKS.TA_SEQ
WHERE TA_ANS_ANSWER <> ''
AND (TA_TASK_ID LIKE '%6051' OR TA_TASK_ID LIKE '%6052')
GROUP BY CAST((LEFT(F_TASK_ANS.TA_ANS_QUESTION,4)) AS INT)