SQL conditional join with subselect - sql

I do not understand why this code isn't working. I have a join which needs a different join condition depending on the result of a subselect.
When the term date is null, I want this to come after the and operator: and vs.term_date is null
When the term date is not null, I want the subquery below to come after the and operator. I need to join on the max effective date: vs.eff_date = (subquery)
I realize this code is messy, but I have to aggregate with a subselect since there are multiple rows coming back and I only want one. (but open to other solutions)
select *
From loc
inner join VendorSite vs
on loc.Record_Number = vs.SITE_NO
and --conditional join
case
when
(
case
when --if term date is null i want this after "and"
(select top 1 TERM_DATE
from VendorSite vs2
where vs2.SITE_NO = vs.SITE_NO
order by TERM_DATE asc) is null
then 1
else 0
end
) = 1
then vs.term_date is null
else --when term_date isn't null use max eff date
vs.eff_date =
(select max(eff_date)
from VendorSite vs2
where vs2.SITE_NO = loc.Record_Number)
end
Example: From the below dataset, I would only want to see the row where term_date is null come back.
site_no eff_date term_date
13588 2007-01-01 00:00:00.000 NULL
13588 2007-03-01 00:00:00.000 2007-11-09 00:00:00.000

Related

JOIN when no data exists

Disclaimer: I don't have a lot of tech background and just learning SQL so apologies.
I have 2 table for acct information - one ref data (ACCT_RD) and one txn data (ACCT_TD).
ACCT_RD is like this
ACCT_ID
ACCT_NAME
1
abc
2
xyz
ACCT_TD is like this
ACCT_ID
DATE
VALUE
1
01-31-2020
4000.33
1
01-31-2021
2000.11
2
01-31-2020
5666.23
I want a query where I will pass the account id and date and it will return me data in format
ACCT_ID
NAME
DATE
VALUE
1
abc
01-31-2020
4000.33
1
abc
null
null
it could be that the ACCT_TD may not contain data (no rows) for all dates but ACCT_RD will always have the info.
I am trying a LEFT Join like
SELECT R.ACCT_ID, R.NAME, T.VALUE, T.DATE
FROM ACCT_RD R
LEFT JOIN ACCT_TD T ON R.ACCT_ID = T.ACCT_ID
WHERE R.ACCT_ID = 1
AND T.DATE IN ('01-31-2000','01-31-2020')
I am getting a row where I have data in both and no row where I don't have data in ACCT_TD.
Is it because in ACCT_TD no row exists for date '01-31-2000' and it is not a column for ACCT_RD?
How can I achieve what I am looking for?
It looks like you have the following requirements for the result set.
For any given ACCT_ID It must contain all the corresponding rows
(if any) from ACCT_TD
VALUE and DATE result set columns are equal to ACCT_TD.VALUE and ACCT_TD.DATE, if ACCT_TD.DATE is in a list of date parameters passed, and both NULL otherwise
If it's the correct understanding of requirements, then try this:
WITH
ACCT_RD (ACCT_ID, ACCT_NAME) AS
(
VALUES
(1, 'abc')
, (2, 'xyz')
)
, ACCT_TD (ACCT_ID, DATE, VALUE) AS
(
VALUES
(1, '01-31-2020', 4000.33)
, (1, '01-31-2021', 2000.11)
, (2, '01-31-2020', 5666.23)
)
SELECT
R.ACCT_ID, R.ACCT_NAME
, CASE WHEN T.DATE IN ('01-31-2000','01-31-2020') THEN T.DATE END AS DATE
, CASE WHEN T.DATE IN ('01-31-2000','01-31-2020') THEN T.VALUE END AS VALUE
FROM ACCT_RD R
LEFT JOIN ACCT_TD T ON R.ACCT_ID = T.ACCT_ID
WHERE R.ACCT_ID = 1;
The result is:
|ACCT_ID|ACCT_NAME|DATE |VALUE |
|-------|---------|----------|-------|
|1 |abc |01-31-2020|4000.33|
|1 |abc | | |
If you want to guarantee that one row is returned even when there is no match in the second table, just move the date filters to the on clause:
SELECT R.ACCT_ID, R.NAME, T.VALUE, T.DATE
FROM ACCT_RD R LEFT JOIN
ACCT_TD T
ON R.ACCT_ID = T.ACCT_ID AND
T.DATE IN ('01-31-2000', '01-31-2020')
WHERE R.ACCT_ID = 1;
The values will be NULL if there is no match.
Also, if your columns are really dates, then you should use proper date formats:
T.DATE IN ('2000-01-31', '2000-01-31')

Is there an better way to query whether a condition is fulfilled before a row within a time duration?

I wrote a query to count the amount of item with some conditions. But the query looks complex and take a long time to run. Is there a better way to get the same result?
My table looks like this.
timestamp uid action item state
------------------------------------------------
2010 1 switch null on
2100 1 move A null
2300 1 move A null
2700 1 move B null
2013 2 switch null off
2213 2 move C null
2513 2 move A null
2200 3 switch null off
2350 3 move A null
2513 3 switch null on
2700 3 move A null
Basically, I want to get the number of each item with a condition that state is on before and within a period of time.
My query is
WITH action_move (
SELECT timestamp, uid, item
FROM table
WHERE action=move AND item IS NOT NULL
)
SELECT item, count(*)
FROM action_move
WHERE EXISTS (
SELECT timestamp
FROM table
WHERE
uid=action_move.uid
action=switch
AND state=on
AND (action_move.timestamp - timestamp) < 1000
)
GROUP BY item;
My result
item count
-------------
A 3
B 1
C 0
You can do what you want with window functions. I think the logic is:
select item, count(*)
from (select t.*,
max(timestamp) filter (where state = 'on') over (order by timestamp) as prev_on
from t
) t
where item is not null prev_on >= timestamp - 1000
group by item;
Often times when you could use window functions, in modern postgres you should use LATERAL. A LATERAL subclause allows you to reference columns from the parent clause. So try something like:
SELECT item, sum(counts.count) AS count
FROM table t1,
LATERAL (
SELECT count(*)
FROM table t2
WHERE t1.uid = t2.uid
AND t2.action=switch
AND t2.state=on
AND (t1.timestamp - t2.timestamp) < 1000
) counts
WHERE action=move AND item IS NOT NULL
GROUP BY item;
I'm not sure if I've replicated this exactly. You might not need the GROUP BY in the outer clause if you have a filter for the specific events you want to aggregate. Basically the lateral will just let you subselect on the table while referencing the row you are subselecting for.

Removing these darn NULLS

I have an issue that would seem straight forward but for some reason I cannot get rid of my null values from the blow select. All I need this to do is return one row, that one without the NULL value. Can someone please point out the error in my ways? :)
The result that I get when running:
EffectiveDate Refund
2015-05-18 00:00:00.000 NULL
2015-05-18 00:00:00.000 1
What I expect back:
EffectiveDate Refund
2015-05-18 00:00:00.000 1
My query:
select md.EffectiveDate,
CASE
WHEN
ISNULL(ConfigID,'') = 3 THEN '1'
WHEN
ISNULL(ConfigID,'') = 4 THEN '2'
END AS Refund
from dbo.PartnerBankConfig md
where md.PartnerID= 100000509
and md.EffectiveDate = (select max(EffectiveDate)
from dbo.PartnerBankConfig
where PartnerID = 100000509
and ISNULL(ConfigID,'') IS NOT NULL)
You get this null because the data doesn't match any condition in your case statement. In other words, in that row you have a value for ConfigID that is neither 3 nor 4. The behavior of a case statement when none of the conditions match is to evaluate to null, and thus null is being returned for this row.
In addition, this function: ISNULL(ConfigID,'') replaces any null with an empty string (a non-null value).
Therefore, ISNULL(ConfigID,'') IS NOT NULL doesn't make sense. It is always going to be true, because ISNULL is always returning a non-null value. You should remove every use of ISNULL() from your query, as none of them are necessary.
As Dan explains, your use of ISNULL() is just not appropriate. From your description, you seem to want this simpler query:
select md.EffectiveDate,
(CASE WHEN ConfigID = 3 THEN 1
WHEN ConfigID = 4 THEN 2
END) as Refund
from (select md.*, max(EffectiveDate) over (partition by PartnerId) as maxed
from dbo.PartnerBankConfig md
where md.PartnerID = 100000509 and
configId in (3, 4)
) md
where md.EffectiveDate = maxed;
Or, even more simply:
select top (1) with ties md.EffectiveDate,
(CASE WHEN ConfigID = 3 THEN 1
WHEN ConfigID = 4 THEN 2
END) as Refund
from (select md.*, max(EffectiveDate) over (partition by PartnerId) as maxed
from dbo.PartnerBankConfig md
where md.PartnerID = 100000509 and
ConfigId in (3, 4)
order by EffectiveDate desc;

Select most recent InstanceID base on max end date

I am trying to pull the memberinstance from a table based on the max DateEnd. If it is Null I want to pull that as it would be still ongoing. I am using sql server.
select memberinstanceid
from table
group by memberid
having MAX(ISNULL(date_end, '2099-12-31'))
This query above doesnt work for me. I have tried different ones and have gotten it to return the separate instances, but not just the one with the max date.
Below is what my table looks like.
MemberID MemberInstanceID DateStart DateEnd
2 abc12 2013-01-01 2013-12-31
4 abc21 2010-01-01 2013-12-31
2 abc10 2015-01-01 NULL
4 abc19 2014-01-01 2014-10-31
I would expect my results to look like this
MemberInstanceID
abc10
abc19
I have been trying to figure out how to do this but have not had much luck. Any help would be much appreciated. Thanks
I think you need something like the following:
select MemberID, MemberInstanceID
from table t
where (
-- DateEnd is null...
DateEnd is null
or (
-- ...or pick the latest DateEnd for this member...
DateEnd = (
select max(DateEnd)
from table
where MemberID = t.MemberID
)
-- ... and check there's not a NULL entry for DateEnd for this member
and not exists (
select 1
from table
where MemberID = t.MemberID
and DateEnd is null
)
)
)
The problem with this approach would be if there are multiple rows that match for each member, i.e. multiple NULL rows with the same MemberID, or multiple rows with the same DateEnd for the same MemberID.
SELECT TOP 1 memberinstanceid
from table
ORDER BY (CASE WHEN [DateEnd] IS NULL THEN 1 ELSE 0 END) DESC,
[DateEnd] DESC
The ORDER BY is essentially creating a "column" to sort the NULL values to the top, then doing a secondary sort on the dates that are not null.
You have a good start but you don't need to perform any explicit grouping. What you want is the row where the EndDate is null or is the largest value (latest date) of all the records with the same MemberID. You also realized that the Max couldn't return the latest non-null date because the null, if one exists, must be the latest date.
select m.*
from Members m
where m.DateEnd is null
or m.DateEnd =(
select Max( IsNull( DateEnd, '9999-12-31' ))
from Members
where MemberID = m.MemberID );

SQL select from multiple tables based on datetime

I am working on a script to analyze some data contained in thousands of tables on a SQL Server 2008 database.
For simplicity sakes, the tables can be broken down into groups of 4-8 semi-related tables. By semi-related I mean that they are data collections for the same item but they do not have any actual SQL relationship. Each table consists of a date-time stamp (datetime2 data type), value (can be a bit, int, or float depending on the particular item), and some other columns that are currently not of interest. The date-time stamp is set for every 15 minutes (on the quarter hour) within a few seconds; however, not all of the data is recorded precisely at the same time...
For example:
TABLE1:
TIMESTAMP VALUE
2014-11-27 07:15:00.390 1
2014-11-27 07:30:00.390 0
2014-11-27 07:45:00.373 0
2014-11-27 08:00:00.327 0
TABLE2:
TIMESTAMP VALUE
2014-11-19 08:00:07.880 0
2014-11-19 08:15:06.867 0.0979999974370003
2014-11-19 08:30:08.593 0.0979999974370003
2014-11-19 08:45:07.397 0.0979999974370003
TABLE3
TIMESTAMP VALUE
2014-11-27 07:15:00.390 0
2014-11-27 07:30:00.390 0
2014-11-27 07:45:00.373 1
2014-11-27 08:00:00.327 1
As you can see, not all of the tables will start with the same quarterly TIMESTAMP. Basically, what I am after is a query that will return the VALUE for each of the 3 tables for every 15 minute interval starting with the earliest TIMESTAMP out of the 3 tables. For the example given, I'd want to start at 2014-11-27 07:15 (don't care about seconds... thus, would need to allow for the timestamp to be +- 1 minute or so). Returning NULL for the value when there is no record for the particular TIMESTAMP is ok. So, the query for my listed example would return something like:
TIMESTAMP VALUE1 VALUE2 VALUE3
2014-11-27 07:15 1 NULL 0
2014-11-27 07:30 0 NULL 0
2014-11-27 07:45 0 NULL 1
2014-11-27 08:00 0 NULL 1
...
2014-11-19 08:00 0 0 1
2014-11-19 08:15 0 0.0979999974370003 0
2014-11-19 08:30 0 0.0979999974370003 0
2014-11-19 08:45 0 0.0979999974370003 0
I hope this makes sense. Any help/pointers/guidance will be appreciated.
Use Full Outer Join
SELECT COALESCE(a.[TIMESTAMP], b.[TIMESTAMP], c.[TIMESTAMP]) [TIMESTAMP],
Isnull(Max(a.VALUE), 0) VALUE1,
Max(b.VALUE) VALUE2,
Isnull(Max(c.VALUE), 0) VALUE3
FROM TABLE1 a
FULL OUTER JOIN TABLE2 b
ON CONVERT(SMALLDATETIME, a.[TIMESTAMP]) = CONVERT(SMALLDATETIME, b.[TIMESTAMP])
FULL OUTER JOIN TABLE3 c
ON CONVERT(SMALLDATETIME, a.[TIMESTAMP]) = CONVERT(SMALLDATETIME, c.[TIMESTAMP])
GROUP BY COALESCE(a.[TIMESTAMP], b.[TIMESTAMP], c.[TIMESTAMP])
ORDER BY [TIMESTAMP] DESC
The first thing I would do is normalize the timestamps to the minute. You can do this with an update to the existing column
UPDATE TABLENAME
SET TIMESTAMP = dateadd(minute,datediff(minute,0,TIMESTAMP),0)
or in a new column
ALTER TABLE TABLENAME ADD COLUMN NORMTIME DATETIME;
UPDATE TABLENAME
SET NORMTIME = dateadd(minute,datediff(minute,0,TIMESTAMP),0)
For details on flooring dates this see this post: Floor a date in SQL server
The next step is to make a table that has all of the timestamps (normalized) that you expect to see -- that is every 15 -- one per row. Lets call this table TIME_PERIOD and the column EVENT_TIME for my examples (call it whatever you want).
There are many ways to make such a table recursive CTE, ROW_NUMBER(), even brute force. I leave that part up to you.
Now the problem is simple select with left joins and a filter for valid values like this:
SELECT TP.EVENT_TIME, a.VALUE as VALUE1, b.VALUE as VALUE2, c.VALUE as VALUE3
FROM TIME_PERIOD TP
LEFT JOIN TABLE1 a ON a.[TIMESTAMP] = TP.EVENT_TIME
LEFT JOIN TABLE2 b ON b.[TIMESTAMP] = TP.EVENT_TIME
LEFT JOIN TABLE3 c ON c.[TIMESTAMP] = TP.EVENT_TIME
WHERE COALESCE(a.[TIMESTAMP], b.[TIMESTAMP], c.[TIMESTAMP]) is not null
ORDER BY TP.EVENT_TIME DESC
The where might get a little more complex if they are different types so you can always use this (which is not as good as coalesce but will always work):
WHERE a.[TIMESTAMP] IS NOT NULL OR
b.[TIMESTAMP] IS NOT NULL OR
c.[TIMESTAMP] IS NOT NULL
Here is an updated version of NoDisplayName's answer that does what you want. It works for SQL 2012, but you could replace the DATETIMEFROMPARTS function with a series of other functions to get the same result.
;WITH
NewT1 as (
SELECT DATETimeFROMPARTS( DATEPART(year,Timestamp) , DATEPART(month,timestamp) , datepart(day,timestamp),datepart(hour,timestamp), datepart(minute,timestamp),0,0 ) as TimeStamp, Value
FROM Table1),
NewT2 as (
SELECT DATETimeFROMPARTS( DATEPART(year,Timestamp) , DATEPART(month,timestamp) , datepart(day,timestamp),datepart(hour,timestamp), datepart(minute,timestamp),0,0 ) as TimeStamp, Value
FROM Table2),
NewT3 as (
SELECT DATETimeFROMPARTS( DATEPART(year,Timestamp) , DATEPART(month,timestamp) , datepart(day,timestamp),datepart(hour,timestamp), datepart(minute,timestamp),0,0 ) as TimeStamp, Value
FROM Table3)
SELECT COALESCE(a.[TIMESTAMP], b.[TIMESTAMP], c.[TIMESTAMP]) [TIMESTAMPs],
Isnull(Max(a.VALUE), 0) VALUE1,
Isnull(Max(b.VALUE), 0) VALUE2,
Isnull(Max(c.VALUE), 0) VALUE3
FROM NewT1 a
FULL OUTER JOIN NewT2 b
ON a.[TIMESTAMP] = b.[TIMESTAMP]
FULL OUTER JOIN TABLE3 c
ON a.[TIMESTAMP] = b.[TIMESTAMP]
GROUP BY COALESCE(a.[TIMESTAMP], b.[TIMESTAMP], c.[TIMESTAMP])
ORDER BY [TIMESTAMPs]