Selecting rows with the nearest date using SQL - sql-server

I have a SQL statement.
SELECT
ID, LOCATION, CODE,MAX(DATE),FLAG
FROM
TABLE1
WHERE
DATE <= CONVERT(DATETIME,'11-11-2012')
AND EXISTS (SELECT * FROM #TEMP_CODE WHERE TABLE1.CODE = #TEMP_CODE.CODE)
AND ID IN (14, 279)
GROUP BY
ID, LOCATION, CODE
I need rows with the nearest date to the 11-11-2012, but the table returns all the values. What am I doing wrong. Thanks
ID LOCATION CODE DATE FLAG
-------------------------------------------------------------------
14 CAR STREET,UDUPI 234 2012-08-08 00:00:00.000 0
14 CAR STREET,UDUPI 234 2012-08-10 00:00:00.000 1
14 CAR STREET,UDUPI 234 2012-08-14 00:00:00.000 0
279 MADHUGIRI 234 2012-08-08 00:00:00.000 1
279 MADHUGIRI 234 2012-08-11 00:00:00.000 0
I want to show only the rows with dates less than or equal to the given date. The required result is
ID LOCATION CODE DATE FLAG
-------------------------------------------------------------------
14 CAR STREET,UDUPI 234 2012-08-10 00:00:00.000 1
279 MADHUGIRI 234 2012-08-11 00:00:00.000 0

;WITH x AS
(
SELECT ID, Location, Code, Date, Flag,
rn = ROW_NUMBER() OVER
(PARTITION BY ID, Location, Code ORDER BY [Date] DESC)
FROM dbo.TABLE1 AS t1
WHERE [Date] <= '20121111'
AND ID IN (14, 279) -- sorry, missed this
AND EXISTS (SELECT 1 FROM #TEMP_CODE WHERE CODE = t1.CODE)
)
SELECT ID, Location, Code, Date, Flag
FROM x WHERE rn = 1;
This yields:
ID LOCATION CODE [Date] FLAG
--- ---------------- ---- ---------- ----
14 CAR STREET,UDUPI 234 2012-08-14 0
279 MADHUGIRI 234 2012-08-11 0
This disagrees with your required results, but I think those are wrong and I think you should check them.

Use a subquery to get the max date for each ID, and then join that to your table:
SELECT
ID, LOCATION, CODE, DATE, FLAG
FROM
TABLE1
JOIN (
SELECT ID AS SubID, MAX(DATE) AS SubDATE
FROM TABLE1
WHERE DATE < '11/11/2012'
AND EXISTS (SELECT * FROM #TEMP_CODE WHERE TABLE1.CODE = #TEMP_CODE.CODE)
AND ID IN (14, 279)
GROUP BY ID
) AS SUB ON ID = SubID AND DATE = SubDATE

add a Order BY DATE LIMIT 0,2
With the order by you will make the date order by the closest to your condition in where and with the limit will return only the top 2 values!
SET ROWCOUNT 2
SELECT
ID, LOCATION, CODE,MAX(DATE),FLAG
FROM
TABLE1
WHERE
DATE <= CONVERT(DATETIME,'11-11-2012')
AND EXISTS (SELECT * FROM #TEMP_CODE WHERE TABLE1.CODE = #TEMP_CODE.CODE)
AND ID IN (14, 279)
GROUP BY
ID, LOCATION, CODE
ORDER BY DATE

Related

SQL Pivot / Case Query based on Row Value

Problem
Using SQL Server, I'm trying to pivot data based on values in a column. I want to move Bob and John's value column over if Salary is in the metric column.
Sample data:
Person table
Person ID
-------------
Bob 1
Bob 1
John 2
John 2
Value table
Metric Value ID
---------------------
Age 52 1
Salary 60000 1
Age 45 2
Salary 55000 2
Expected output
My goal is to pivot the table if salary is present in the Metric column.
Person Metric Value Salary ID
---------------------------------------
Bob Age 52 60000 1
John Age 45 55000 2
Current code:
SELECT *
FROM person_table pt, value_table vb
WHERE pt.id = vb.id
AND vb.metric IN ('Age', 'Salary')
Use the following pivot query:
SELECT
pt.Person,
'Age' AS Metric,
MAX(CASE WHEN vb.Metric = 'Age' THEN vb.Value END) AS Value,
MAX(CASE WHEN vb.Metric = 'Salary' THEN vb.Value END) AS Salary,
pt.ID
FROM person_table pt
INNER JOIN value_table vb
ON pt.id = vb.id
GROUP BY
pt.Person,
pt.ID
ORDER BY
pt.ID;

create date range report based on history table

We have been keeping track of some changes in a History Table like this:
ChangeID EmployeeID PropertyName OldValue NewValue ModifiedDate
100 10 EmploymentStart Not Set 1 2013-01-01
101 10 SalaryValue Not Set 55000 2013-01-01
102 10 SalaryValue 55000 61500 2013-03-20
103 10 SalaryEffectiveDate 2013-01-01 2013-04-01 2013-03-20
104 11 EmploymentStart Not Set 1 2013-01-21
105 11 SalaryValue Not Set 43000 2013-01-21
106 10 SalaryValue 61500 72500 2013-09-20
107 10 SalaryEffectiveDate 2013-04-01 2013-10-01 2013-09-20
Basically if an Employee's Salary changes, we log two rows in the history table. One row for the Salary value itself and the other row for the salary effective date. So these two have identical Modification Date/Time and are kind safe to assume that are always after each other in the database. We can also assume that Salary Value is always logged first (so it is one record before the corresponding effective date
Now we are looking into creating reports based on a given date range into a table like this:
Annual Salary Change Report (2013)
EmployeeID Date1 Date2 Salary
10 2013-01-01 2013-04-01 55000
10 2013-04-01 2013-10-01 61500
10 2013-10-01 2013-12-31 72500
11 2013-03-21 2013-12-31 43000
I have done something similar in the past by joining the table to itself but in those cases the effective date and the new value where in the same row. Now I have to create each row of the output table by looking into a few rows of the existing history table. Is there an straightforward way of doing this whitout using cursors?
Edit #1:
Im reading on this and apparently its doable using PIVOTs
Thank you very much in advance.
You can use self join to get the result you want. The trick is to create a cte and add two rows for each EmployeeID as follows (I call the history table ht):
with cte1 as
(
select EmployeeID, PropertyName, OldValue, NewValue, ModifiedDate
from ht
union all
select t1.EmployeeID,
(case when t1.PropertyName = "EmploymentStart" then "SalaryEffectiveDate" else t1.PropertyName end),
(case when t1.PropertyName = "EmploymentStart" then t1.ModifiedDate else t1.NewValue end),
(case when t1.PropertyName = "SalaryValue" then t1.NewValue
when t1.PropertyName = "SalaryEffectiveDate" then "2013-12-31"
when t1.PropertyName = "EmploymentStart" then "2013-12-31" end),
"2013-12-31"
from ht t1
where t1.ModifiedDate = (select max(t2.ModifiedDate) from ht t2 where t1.EmployeeID = t2.EmployeeID)
)
select t3.EmployeeID, t4.OldValue Date1, t4.NewValue Date2, t3.OldValue Salary
from cte1 t3
inner join cte1 t4 on t3.EmployeeID = t4.EmployeeID
and t3.ModifiedDate = t4.ModifiedDate
where t3.PropertyName = "SalaryValue"
and t4.PropertyName = "SalaryEffectiveDate"
order by t3.EmployeeID, Date1
I hope this helps.
It is a little over kill to use pivot since you only need two properties. Use GROUP BY can also achieve this:
;WITH cte_salary_history(EmployeeID,SalaryEffectiveDate,SalaryValue)
AS
(
SELECT EmployeeID,
MAX(CASE WHEN PropertyName='SalaryEffectiveDate' THEN NewValue ELSE NULL END) AS SalaryEffectiveDate,
MAX(CASE WHEN PropertyName='SalaryValue' THEN NewValue ELSE NULL END) AS SalaryValue
FROM yourtable
GROUP BY EmployeeID,ModifiedDate
)
SELECT EmployeeID,SalaryEffectiveDate,
LEAD(SalaryEffectiveDate,1,'9999-12-31') OVER(PARTITION BY EmployeeID ORDER BY SalaryEffectiveDate) AS SalaryEndDate,
SalaryValue
FROM cte_salary_history

Using UPDATE + SET to change a value to another date's value

Forgive me if the answer is obvious here, however I have been stuck for days; my unsuccessful query below.
If a 'Retailer' reports sales figures, but not inventory values for a certain day, I want to update that missing value using the value for the day prior.
Here's a sample table:
Retailer Date ItemID Sold Inventory
Joe's 2017-10-30 00:00:00.000 111111 10 0
Joe's 2017-10-29 00:00:00.000 111111 10 999999
Mary's 2017-10-30 00:00:00.000 123123 10 0
Mary's 2017-10-29 00:00:00.000 123123 10 888888
Betty's 2017-10-30 00:00:00.000 111111 10 499990
Betty's 2017-10-29 00:00:00.000 111111 10 500000
And here is the query I'm trying to use:
SET T1.Inventory = (SELECT T2.Inventory
FROM [dbo].[TEST] T2
WHERE CAST(T2.Date AS DATE) = CONVERT(date,getDate()-2))
FROM [dbo].[TEST] T1
WHERE Inventory = '0'
Use the DATEADD function instead of getDate()-2
And if you want the day before today, you should use GetDate and subtract 1, rather than 2.
If you want the day before the record you are looking at with the same retailer, then you should use t1.Date and make sure you have correlated the subquery:
SET T1.Inventory = (SELECT T2.Inventory
FROM [dbo].[TEST] T2
WHERE CAST(T2.Date AS DATE) = DATEADD(day,-1,CONVERT(date,T1.Date))
AND t1.Retailer=t2.Retailer
)
...
Undoubtedly the reason for the difficulty with updating the inventory column is that the table lacks a unique column which is considered by most to be absolutely essential in any database table. So I have added an identity column RID as a Row ID which is unique.
ALTER TABLE T1 ADD RID INT IDENTITY(1,1)
DECLARE #RID INT = (SELECT MIN(RID) FROM T1 WHERE Inventory = 0)
DECLARE #INVZERO INT = (SELECT COUNT(*) FROM T1 WHERE Inventory = 0)
WHILE #INVZERO > 0
BEGIN
UPDATE T1 SET INVENTORY =
(
SELECT INVENTORY FROM T1
WHERE RETAILER = (SELECT RETAILER FROM T1 WHERE RID = #RID)
AND [DATE] = DATEADD(DAY,-1,(SELECT [DATE] FROM T1 WHERE RID = #RID))
)
WHERE RID = #RID
SET #RID = (SELECT MIN(RID) FROM T1 WHERE Inventory = 0 AND RID > #RID)
SET #INVZERO = (SELECT COUNT(*) FROM T1 WHERE Inventory = 0)
END
SELECT * FROM T1

SQL Server: update table with value from previous record

I have tried several ways using LAG(), ROW_NUMBER() and so on, but I cannot get it working... Please help.
Assume we have this table:
Date Time Amount Balance
---------------------------------------------
20171001 12:44:00 102.00 102.00
20171002 09:32:12 10.00 null
20171002 20:00:00 123.00 null
20171003 07:43:12 5.29 null
My goal is to update the Balance but these records are not ordered in this table.
I have tried to use this code:
with t1 as
(
select
Date, Time, Amount, Balance,
lag(Balance) over (order by Date, Time) Balance_old
from
table1
)
update table1
set Balance = Amount + Balance_old
where Balance_old is not null
However, this seems to only update 1 record instead of 3 in the above example. Even when I try to do something similar with ROW_NUMBER() then I do not get the results I require.
The results I would like to have are as follows:
Date Time Amount Balance
---------------------------------------------
20171001 12:44:00 102.00 102.00
20171002 09:32:12 10.00 112.00
20171002 20:00:00 123.00 235.00
20171003 07:43:12 5.29 240.29
Please notice: in my situation there is always a record which has a value in Balance. This is the starting point which can be 0 or <>0 (but not null).
As one of the approaches is to simply use sum() over() window function.
-- set up
select *
into t1
from (
select cast('20171001' as date) Date1, cast('12:44:00' as time) Time1, 102.00 Amount, 102.00 Balance union all
select cast('20171002' as date), cast('09:32:12' as time), 10.00, null union all
select cast('20171002' as date), cast('20:00:00' as time), 123.00, null union all
select cast('20171003' as date), cast('07:43:12' as time), 5.29, null
) q
-- UPDATE statement
;with t2 as(
select date1
, time1
, amount
, balance
, sum(isnull(balance, amount)) over(order by date1, time1) as balance1
from t1
)
update t2
set balance = balance1
The result:
Date1 Time1 Amount Balance
---------- ---------------- ---------- -------------
2017-10-01 12:44:00.0000000 102.00 102.00
2017-10-02 09:32:12.0000000 10.00 112.00
2017-10-02 20:00:00.0000000 123.00 235.00
2017-10-03 07:43:12.0000000 5.29 240.29

Select rowset with null value in first row of group by result set

I am stuck with a problem.
I have some data likes these :
Id Creation date Creation date hour range Id vehicule Id variable Value
1 2017-03-01 9:10 2017-03-01 9:00 1 6 0.18
2 2017-03-01 9:50 2017-03-01 9:00 1 3 0.50
3 2017-03-01 9:27 2017-03-01 9:00 1 3 null
4 2017-03-01 10:05 2017-03-01 10:00 1 3 0.35
5 2017-03-01 10:17 2017-03-01 10:00 1 3 0.12
6 2017-03-01 9:05 2017-03-01 9:00 1 5 0.04
7 2017-03-01 9:57 2017-03-01 9:00 1 5 null
I need to select rowset group by Id vehicule, Id variable, Creation date hour range and order by group by Id vehicule, Id variable, Creation date where the first Value is null but second value, third value, ... is not null. So, in the sample above, the following rowset :
Id Creation date Creation date hour range Id vehicule Id variable Value
3 2017-03-01 9:27 2017-03-01 9:00 1 3 null
2 2017-03-01 9:50 2017-03-01 9:00 1 3 0.50
Could you help me please ?
Thank you
You will have no luck with a group by in this case. I would give 2 "if exists" into the where clause to filter all IDs that fit your criteria:
(for example/not tested/probably takes forever)
select *
from yourTable y1
where id in
--the id must be in all IDs, where the first value of the set is null
--same ID instead of group by
(select 1 from yourTable y2 where y1.IDs = y2.IDs and
--the first in the set
y2.createdate = (select min(createdate) from yourtable y3 with sameid) and
y2.value is null)
AND
--the id must also be in the IDs, where there are values besides the first that are not null
id in (same select but with "not min" and "not null" obviously
hope that helped :)
Include the Value field in the ORDER BY clause and it will be sorted to the top because NULL has a lower practical value than a non-NULL value.
Assuming (because your middle paragraph is hard to understand) you want all the fields output but you want the 4th and 5th columns to produce some grouping of the output, with Value = NULL at the top of each group:
SELECT Id, CreatedDate, CreatedDateHourRange, IdVehicule, IdVariable, Value
ORDER BY IdVehicule, IdVariable, Value
I don't see any need for an actual GROUP BY clause.
I think it is unclear as to whether you want to limit the NULL Value rows in each block to just one row of NULL, but if you do you would need to state the order for which the datetime columns are sorted.
indeed group by was no use here. Also I wasn't sure where your 10:00 records were going to. Does this help?
;WITH CTE_ADD_SOME_LOGIC
AS
(
SELECT Id, CreationDate ,CreationDateHourRange ,IdVehicle ,IdVariable ,Value
, CASE WHEN Value IS NULL THEN 1 ELSE 0 END AS VALUE_IS_NULL FROM tbl
),
CTE_MORE_LOGIC
AS
(
SELECT Id, CreationDate ,CreationDateHourRange ,IdVehicle ,IdVariable ,Value,VALUE_IS_NULL
, RANK() OVER (ORDER BY CreationDateHourRange,VALUE_IS_NULL) AS RN FROM CTE_ADD_SOME_LOGIC),
CTE_ORDER
AS
(
SELECT Id, CreationDate ,CreationDateHourRange ,IdVehicle ,IdVariable ,Value,VALUE_IS_NULL, RN
, ROW_NUMBER() OVER(PARTITION BY RN ORDER BY RN,IdVehicle,IdVariable,CreationDate, VALUE_IS_NULL DESC) AS HIERARCHY FROM CTE_MORE_LOGIC
)
SELECT Id, CreationDate ,CreationDateHourRange ,IdVehicle ,IdVariable ,Value FROM CTE_ORDER WHERE HIERARCHY = 1
ORDER BY Id
Try this Query
DECLARE #Nulloccurrence INT=1 -- Give like 1,2,3 value to get first null occurrence 2 for 2nd null occurrence
SELECT TOP 2 *
FROM cte
WHERE Id <= (
SELECT ID FROM
(
SELECT Id, ROW_NUMBER()OVER( Order by id) AS Seq
FROM cte
WHERE (
CASE
WHEN CAST(variableValue AS VARCHAR) IS NULL
THEN 'P'
ELSE CAST(variableValue AS VARCHAR)
END
) = 'P'
)Dt
WHERE Dt.Seq=#Nulloccurrence
)
ORDER BY 1 DESC
Expected Result
Id Creationdate Creationdatehourrange Ids vehicleId variableValue
------------------------------------------------------------------------
3 2017-03-01 9:27 2017-03-01 9:00 1 3 NULL
2 2017-03-01 9:50 2017-03-01 9:00 1 3 0.50
For 'where the first Value is null but second value, third value, ... is not null' i suppose you want to filter cases where there is a null and a not null value at [Value] within the set you group by, to decide to filter or not that grouped row. This cannot be filtered on standard WHERE clause because at WHERE clause each row is filtered with conditions relevant to that row scope only. Simply put, each row filtered cannot 'see' other rows unless you use sub-query. You need to use HAVING clause (the comment out is for 2+ null records)
This will work:
> DECLARE #mytbl TABLE(Id INT, [Creation date] DATETIME, [Creation date
> hour range] DATETIME, [Id veh] INT, [Id var] INT, Value INT )
>
> INSERT INTO #mytbl VALUES (1,'2017-03-01 9:10 ','2017-03-01 9:00 ',1,
> 6, 0.18) INSERT INTO #mytbl VALUES (2,'2017-03-01 9:50 ','2017-03-01
> 9:00 ',1, 3, 0.50) INSERT INTO #mytbl VALUES (3,'2017-03-01 9:27
> ','2017-03-01 9:00 ',1, 3, NULL) INSERT INTO #mytbl VALUES
> (4,'2017-03-01 10:05','2017-03-01 10:00',1, 3, 0.35) INSERT INTO
> #mytbl VALUES (5,'2017-03-01 10:17','2017-03-01 10:00',1, 3, 0.12)
> INSERT INTO #mytbl VALUES (6,'2017-03-01 9:05 ','2017-03-01 9:00 ',1,
> 5, 0.04) INSERT INTO #mytbl VALUES (7,'2017-03-01 9:57 ','2017-03-01
> 9:00 ',1, 5, NULL)
>
> SELECT [Id veh], [Id var],[Creation date hour range] FROM #mytbl GROUP
> BY [Id veh], [Id var],[Creation date hour range] HAVING COUNT([Id
> veh]) - COUNT(Value) = 1
> --HAVING COUNT([Id veh]) - COUNT(Value) >= 1 ORDER BY [Id veh], [Id var],[Creation date hour range]

Resources