Using Datediff() on Joined Tables with CASES - sql

I'm trying to determine the best way to calculate a datediff (in minutes). I'm trying to determine the time lapsed between two stages in a registration process (stage 8 and stage 10), but I only need this for anyone who's ever been in stage 10. There are two tables: Registration and Registrationstagehistory which are linked by the registration id number (rid)
I get the following error:
Incorrect syntax near the keyword 'CASE'.
from below.
Select datediff(Minute,startdate,enddate)
from Registration r with(nolock)
inner join registrationstagehistory rh with(nolock) on r.rid=rh.rhrid
It may be something like this:
CASE WHEN R.RsID=10 THEN
CASE rh.rhrsid
WHEN 8 then 'startdate'
WHEN 10 THEN 'enddate'
ELSE NULL
END
END

You're missing an END to one of your CASE statements (the inner one it appears but i can't be sure from the context I have):
CASE WHEN R.RsID=10 THEN
CASE rh.rhrsid
WHEN 8 then 'startdate'
WHEN 10 THEN 'enddate'
ELSE NULL
END
END

Check it here: http://rextester.com/BHBZ30100
I've used this schema for this answer:
create table #reg (id int, other text);
create table #his (reg_id int, rs_id int, rdate datetime);
insert into #reg values (1, 'registration 1'), (2, 'registration 2');
insert into #his values (1, 1, '2016-11-01')
,(1, 5, '2016-11-05')
,(1, 8, '2016-11-08')
,(1, 9, '2016-11-09')
,(1,10, '2016-11-10')
,(2, 1, '2016-11-01')
,(2, 5, '2016-11-05');
If you only want the datediff in RegistrationHistory table of Registration records that has reached Stage 10, you can directly filter the records where Stage=10. Then use another query to search the date of Stage 8, and find the difference.
select
#reg.*, #his.*,
datediff( minute
,isnull((select h2.rdate
from #his h2
where h2.rs_id = 8 and h2.reg_id = #his.reg_id), #his.rdate)
,#his.rdate) as minutes
from #his
inner join #reg on #his.reg_id = #reg.id
where
#his.rs_id = 10
And this is the result: (I've added all fields, due I don't know exactly what you need),
+----+----+----------------+--------+-------+---------------------+---------+
| | id | other | reg_id | rs_id | rdate | minutes |
+----+----+----------------+--------+-------+---------------------+---------+
| 1 | 1 | registration 1 | 1 | 10 | 10.11.2016 00:00:00 | 2880 |
+----+----+----------------+--------+-------+---------------------+---------+

Related

Get Ids from constant list for which there are no rows in corresponding table

Let say I have a table Vehicles(Id, Name) with below values:
1 Car
2 Bike
3 Bus
and a constant list of Ids:
1, 2, 3, 4, 5
I want to write a query returning Ids from above list for which there are no rows in Vehicles table. In the above example it should return:
4, 5
But when I add new row to Vehicles table:
4 Plane
It should return only:
5
And similarly, when from the first version of Vehicle table I remove the third row (3, Bus) my query should return:
3, 4, 5
I tried with exist operator but it doesn't provide me correct results:
select top v.Id from Vehicle v where Not Exists ( select v2.Id from Vehicle v2 where v.id = v2.id and v2.id in ( 1, 2, 3, 4, 5 ))
You need to treat your "list" as a dataset, and then use the EXISTS:
SELECT V.I
FROM (VALUES(1),(2),(3),(4),(5))V(I) --Presumably this would be a table (type parameter),
--or a delimited string split into rows
WHERE NOT EXISTS (SELECT 1
FROM dbo.YourTable YT
WHERE YT.YourColumn = V.I);
Please try the following solution.
It is using EXCEPT set operator.
Set Operators - EXCEPT and INTERSECT (Transact-SQL)
SQL
-- DDL and sample data population, start
DECLARE #Vehicles TABLE (ID INT PRIMARY KEY, vehicleType VARCHAR(30));
INSERT INTO #Vehicles (ID, vehicleType) VALUES
(1, 'Car'),
(2, 'Bike'),
(3, 'Bus');
-- DDL and sample data population, end
DECLARE #vehicleList VARCHAR(20) = '1, 2, 3, 4, 5'
, #separator CHAR(1) = ',';
SELECT TRIM(value) AS missingID
FROM STRING_SPLIT(#vehicleList, #separator)
EXCEPT
SELECT ID FROM #Vehicles;
Output
+-----------+
| missingID |
+-----------+
| 4 |
| 5 |
+-----------+
In SQL we store our values in tables. We therefore store your list in a table.
It is then simple to work with it and we can easily find the information wanted.
I fully agree that it is possible to use other functions to solve the problem. It is more intelligent to implement database design to use basic SQL. It will run faster, be easier to maintain and will scale for a table of a million rows without any problems. When we add the 4th mode of transport we don't have to modify anything else.
CREATE TABLE vehicules(
id int, name varchar(25));
INSERT INTO vehicules VALUES
(1 ,'Car'),
(2 ,'Bike'),
(3 ,'Bus');
CREATE TABLE ids (iid int)
INSERT INTO ids VALUES
(1),(2),(3),(4),(5);
CREATE VIEW unknownIds AS
SELECT iid unknown_id FROM ids
LEFT JOIN vehicules
ON iid = id
WHERE id IS NULL;
SELECT * FROM unknownIds;
| unknown_id |
| ---------: |
| 4 |
| 5 |
INSERT INTO vehicules VALUES (4,'Plane')
SELECT * FROM unknownIds;
| unknown_id |
| ---------: |
| 5 |
db<>fiddle here

Custom table join

Suppose I have 4 tables in Postgresql DB:
users {
id: int
}
cars {
id: int
}
usage_items {
id: int,
user_id: int,
car_id: int,
start: date,
end: date
}
prices {
id: int,
car_id: int,
price: int
}
When a user is renting a car I am creating a usage_item record to track the rental time. At the end of the month, I am sending him an invoice with calculated costs. The SQL is pretty simple here:
SELECT usage_items.start, usage_items.end, prices.price
FROM usage_items
JOIN prices ON prices.car_id = usage_items.car_id
(I omitted here WHERE clause with dates comparison, the rest of calculations I do in my Ruby code)
The problem I struggle with now is that some of my users have custom contracts with me ensuring lower prices for them. I am looking for a way to express this logic in my DB.
I came up with an idea to add user_id column to the prices table but this way I would need to create prices for every single user. So I decided to implement the following logic: if car_id in prices row is null it means that it is the default price for all the users. Otherwise, it is specific to a user. But I have no idea how to write SQL for this case, because:
SELECT usage_items.start, usage_items.end, prices.price
FROM usage_items
JOIN prices ON prices.car_id = usage_items.car_id
WHERE prices.user_id IS NULL OR prices.user_id = usage_items.user_id
returns rows for both prices. And I need only the one with an associated group or if it does not exist the one with null group_id.
Can you help me fix this SQL? Or maybe my design is bad and I should change it somehow?
Given your existing schema, this is one way to accomplish what you want:
Setup:
CREATE TABLE usage_items (user_id INTEGER, car_id INTEGER);
CREATE TABLE prices (user_id INTEGER, car_id INTEGER, price INTEGER);
INSERT INTO usage_items VALUES (1, 10), (2, 11), (3, 12);
INSERT INTO prices VALUES
(1, 10, 101),
(2, 11, 102),
(4, 12, 104),
(NULL, 10, 201),
(NULL, 11, 202),
(NULL, 12, 304);
Query (I'm not using start/end but it's the same thing):
SELECT DISTINCT ON (u.user_id, u.car_id) u.user_id, u.car_id, p.price
FROM usage_items u
LEFT JOIN prices p
ON u.car_id = p.car_id
AND (u.user_id = p.user_id OR p.user_id IS NULL)
ORDER BY u.user_id, u.car_id, CASE WHEN p.user_id IS NOT NULL THEN 1 ELSE 2 END
Result:
| user_id | car_id | price |
| ------- | ------ | ----- |
| 1 | 10 | 101 |
| 2 | 11 | 102 |
| 3 | 12 | 304 |
As you can see, the records in usage_items with a corresponding record for their car AND user_id in prices get their custom price rather than the NULL version; user 3 who does not have a custom price gets the NULL version (and not the custom price for a different customer).
Test here https://www.db-fiddle.com/f/wPVWEY3r22n22iKpDMrcMC/0

SQL list account managers with predecessor

I have the following that I can't seem to figure out. I'm trying to get a list of account managers on an account, their start/end date and the new account manager that took over the account on a single row.
Example:
DECLARE #accountManagerListing TABLE
(
accountNumber INT,
accountManager VARCHAR(8),
accountManagerStartDate DATE,
accountManagerEndDate DATE
)
INSERT INTO #accountManagerListing (accountNumber, accountManager, accountManagerStartDate, accountManagerEndDate)
VALUES (1, 'asmith', '01/01/2001', '01/31/2001'),
(1, 'bsmith', '02/01/2001', '03/01/2002'),
(1, 'csmith', '03/02/2002', '03/10/2002'),
(1, 'dsmith', '03/11/2002', '06/01/2017'),
(1, 'esmith', '06/02/2017', '08/17/2018'),
(2, 'fsmith', '02/11/2018', '06/01/2018'),
(2, 'gsmith', '06/02/2018', null)
Expected results:
Account Number Old Account Manager New Account Manager Start Date End Date
1 asmith 01/01/2001 01/31/2001
1 asmith bsmith 02/01/2001 03/01/2002
1 bsmith csmith 03/02/2002 03/10/2002
1 csmith dsmith 03/11/2002 06/01/2017
1 dsmith esmith 06/02/2017 08/17/2018
2 fsmith 02/11/2018 06/01/2018
2 fsmith gsmith 06/02/2018 NULL
Use lag() :
select a.*,
lag(accountManager) over (partition by accountnumber order by accountManagerStartDate) as OldAccountManager
from #accountManagerListing a;
You can use a left join:
select aml.*, amlprev.accountmanager as old_accountmanager
from #accountManagerListing aml left join
#accountManagerListing amlprev
on amlprev.accountnumber = aml.accountnumber and
amlprev.enddate = dateadd(day, -1, aml.startdate);
This finds the immediately preceding manager (if any). If there is a gap, then this returns no manager. This logic seems more aligned with your description of "took over".

Recursive view that sum value from double tree structure SQL Server

First sorry for numerous repost of my question, I'm new around and getting used to properly and clearly asking questions.
I'm working on a recursive view that sum up values from a double tree structure.
I have researched around and found many questions about recursive sums but none of their solutions seemed to work for my issue specifically.
As of now I have issues aggregating the values in the right cells, the logic being i need the sum of each element per year in it's parent and also the sum of all the years for a given element.
Here is a fiddle of my tables and actual script:
SQL Fiddle
And here is a screenshot of the output I'm looking for:
My question is:
How can I get my view to aggregate the value from child to parent in this double tree structure?
If I understand your question correctly, you are trying to get an aggregation at 2 different levels to show in a single result set.
Clarification Scenario:
Below is an over-simplified sample data set for what I believe you are trying to achieve.
create table #agg_table
(
group_one int
, group_two int
, group_val int
)
insert into #agg_table
values (1, 1, 6)
, (1, 1, 7)
, (1, 2, 8)
, (1, 2, 9)
, (2, 3, 10)
, (2, 3, 11)
, (2, 4, 12)
, (2, 4, 13)
Given the sample data above, you want want to see the following output:
+-----------+-----------+-----------+
| group_one | group_two | group_val |
+-----------+-----------+-----------+
| 1 | NULL | 30 |
| 1 | 1 | 13 |
| 1 | 2 | 17 |
| 2 | NULL | 46 |
| 2 | 3 | 21 |
| 2 | 4 | 25 |
+-----------+-----------+-----------+
This output can be achieved by making use of the group by grouping sets
(example G. in the link) syntax in SQL Server as shown in the query below:
select a.group_one
, a.group_two
, sum(a.group_val) as group_val
from #agg_table as a
group by grouping sets
(
(
a.group_one
, a.group_two
)
,
(
a.group_one
)
)
order by a.group_one
, a.group_two
What that means for your scenario, is that I believe your Recursive-CTE is not the issue. The only thing that needs to change is in the final select query from the entire CTE.
Answer:
with Temp (EntityOneId, EntityOneParentId, EntityTwoId, EntityTwoParentId, Year, Value)
as
(
SELECT E1.Id, E1.ParentId, E2.Id, E2.ParentId, VY.Year, VY.Value
FROM ValueYear AS VY
FULL OUTER JOIN EntityOne AS E1
ON VY.EntityOneId = E1.Id
FULL OUTER JOIN EntityTwo AS E2
ON VY.EntityTwoId = E2.Id
),
T (EntityOneId, EntityOneParentId, EntityTwoId, EntityTwoParentId, Year, Value, Levels)
as
(
Select
T1.EntityOneId,
T1.EntityOneParentId,
T1.EntityTwoId,
T1.EntityTwoParentId,
T1.Year,
T1.Value,
0 as Levels
From
Temp
As T1
Where
T1.EntityOneParentId is null
union all
Select
T1.EntityOneId,
T1.EntityOneParentId,
T1.EntityTwoId,
T1.EntityTwoParentId,
T1.Year,
T1.Value,
T.Levels +1
From
Temp
AS T1
join
T
On T.EntityOneId = T1.EntityOneParentId
)
Select
T.EntityOneId,
T.EntityOneParentId,
T.EntityTwoId,
T.EntityTwoParentId,
T.Year,
sum(T.Value) as Value
from T
group by grouping sets
(
(
T.EntityOneId,
T.EntityOneParentId,
T.EntityTwoId,
T.EntityTwoParentId,
T.Year
)
,
(
T.EntityOneId,
T.EntityOneParentId,
T.EntityTwoId,
T.EntityTwoParentId
)
)
order by T.EntityOneID
, T.EntityOneParentID
, T.EntityTwoID
, T.EntityTwoParentID
, T.Year
FYI - I believe the sample data did not have the records necessary to match the expected output completely, but the last 20 records in the SQL Fiddle match the expected output perfectly.

Left join with complex join clause

I have two tables and want to left join them.
I want all entries from the account table, but only rows matching a criteria from the right table. If no criteria is matching, I only want the account.
The following does not work as expected:
SELECT * FROM Account a
LEFT JOIN
Entries ef ON ef.account_id = a.account_id AND
(ef.entry_period_end_date BETWEEN $periodStartDate_escaped AND LAST_DAY(date_add( $periodStartDate_escaped, INTERVAL $periodLengthInMonths_escaped MONTH))
OR
ef.forecast_period_end BETWEEN $periodStartDate_escaped AND LAST_DAY(date_add( $periodStartDate_escaped, INTERVAL $periodLengthInMonths_escaped MONTH))
OR
ef.entry_period_end_date IS NULL
OR
ef.forecast_period_end IS NULL
)
cause it also gives me the rows from the entries table, which are outside the requested period.
Example Data:
Account Table
AccountID | AccountName
1 Test
2 Foobar
3 Test1
4 Foobar2
Entries Table
id | AccountID | entry_period_end_date | forecast_period_end | amount
1 1 12/31/2009 12/31/2009 100
2 1 NULL 10/31/2009 150
3 2 NULL NULL 200
4 3 10/31/2009 NULL 250
5 4 10/31/2009 10/31/2009 300
So the query should return (when i set startDate = 12/01/2009, endDate 12/31/2009)
AccountID | id
1 1
2 NULL
3 NULL
4 NULL
Thx,
Martin
If either entry_period_end_date or forecast_period_end is NULL, the row will be returned, even if your other, non-NULL column is not within the period.
Probably you meant this:
SELECT *
FROM Account a
LEFT JOIN
Entries ef
ON ef.account_id = a.account_id
AND
(
entry_period_end_date BETWEEN …
OR forecast_period_end BETWEEN …
)
, which will return you all rows with either entry_period_end or forecast_period_end within the given period.
Update:
A test script:
CREATE TABLE account (AccountID INT NOT NULL, AccountName VARCHAR(100) NOT NULL);
INSERT
INTO account
VALUES
(1, 'Test'),
(2, 'Foobar'),
(3, 'Test1'),
(4, 'Foobar1');
CREATE TABLE Entries (id INT NOT NULL, AccountID INT NOT NULL, entry_period_end_date DATETIME, forecast_period_end DATETIME, amount FLOAT NOT NULL);
INSERT
INTO Entries
VALUES
(1, 1, '2009-12-31', '2009-12-31', 100),
(2, 1, NULL, '2009-10-31', 100),
(3, 2, NULL, NULL, 100),
(4, 3, '2009-10-31', NULL, 100),
(5, 4, '2009-10-31', '2009-10-31', 100);
SELECT a.*, ef.id
FROM Account a
LEFT JOIN
Entries ef
ON ef.accountID = a.accountID
AND
(
entry_period_end_date BETWEEN '2009-12-01' AND '2009-12-31'
OR forecast_period_end BETWEEN '2009-12-01' AND '2009-12-31'
);
returns following:
1, 'Test', 1
2, 'Foobar', NULL
3, 'Test1', NULL
4, 'Foobar1' NULL
Edited to fix logic so end date logic is grouped together, then forecast period logic...
Now it should check for a "good" end date (null or within range), then check for a "good" forecast date (null or within range)
Since all the logic is on the Entries table, narrow it down first, then join
SELECT a.*,temp.id FROM Account a
LEFT JOIN
(
SELECT id, account_id
FROM Entries ef
WHERE
((ef.entry_period_end_date BETWEEN $periodStartDate_escaped AND LAST_DAY(date_add( $periodStartDate_escaped, INTERVAL $periodLengthInMonths_escaped MONTH))
OR
ef.entry_period_end_date IS NULL
)
AND
(ef.forecast_period_end BETWEEN $periodStartDate_escaped AND LAST_DAY(date_add( $periodStartDate_escaped, INTERVAL $periodLengthInMonths_escaped MONTH))
OR
ef.forecast_period_end IS NULL
)
) temp
ON a.account_id = temp.account_id