SQL Server 2012 join query - sql-server

I have 2 tables: Fields{id, testid, labid, upperlimit, lowerlimit) and LabTest{id, testid, labid, upperlimit, lowerlimit)
Sample data: Fields: 0, testid1, 1, 10, 20
1, testid2, 2, 10, 20
LabTest: 0, testid1, 1, 40, 50
1, testid1, null, 50, 60
2, testid2, null, 55, 67
3, testid2, null, 67, 78
4, testid2, null, 78, 89
join on testid1 should return 1 line
join on testid2 should return 3 lines
If labid in Fields table is not null and testid is not null then I need to use the upperlimit and lowerlimit of the LabTest table where Fields.testid=LabTest.testid and Fields.labid=LabTest.labid
If I use a left join (because I need to take the Fields without testid also):
select * from Fields left join LabTest on Fields.testid=LabTest.testid
I will get back all the fields no matter if their labid is null or not (5 lines using the above sample data), but
what I want to get is only one line the one with LabTest.labid not
null and if this doesnt exist then and only then I want to get the
line(s) with LabTest.labid = null

Use Inner Join or If filter the Left join with a Where Clause
;WITH CTE
AS
(
select
RN = ROW_NUMBER() OVER(PARTITION BY Fields.Id ORDER BY labid),
Fields_Id = Fields.id,
Fields_testid = Fields.testid,
Fields_labid = Fields.labid,
Fields_upperlimit = Fields.upperlimit,
Fields_lowerlimit = Fields.lowerlimit,
LabTest_Id = LabTest.id,
LabTest_testid = LabTest.testid,
LabTest_labid = LabTest.labid,
LabTest_upperlimit = LabTest.upperlimit,
LabTest_lowerlimit = LabTest.lowerlimit
from Fields
left join LabTest
on Fields.testid=LabTest.testid
)
SELECT
*
FROM CTE
WHERE RN = 1

Try this:
select top 1 *
from Fields left join LabTest
on Fields.testid=LabTest.testid
where LabTest.labid is not null

Related

Snowflake lateral subquery fails

If you run the SQL below in Snowflake you'll see that it returns SQL compilation error: Unsupported subquery type cannot be evaluated. For the life of me I cannot figure out how to rewrite it so that it does what I'm trying to do. Any suggestions?
CREATE OR REPLACE TABLE TEST1
(
ROWID INT IDENTITY(1,1),
DATESTAMP TIMESTAMP_NTZ(9),
STATE_ID INT,
IS_TEST BOOLEAN,
USER_ID INT,
GROUP_ID INT,
SECONDARY_USER_ID INT
);
INSERT INTO TEST1 (DATESTAMP, STATE_ID, IS_TEST, USER_ID, GROUP_ID) SELECT '01/01/2020', 31, TRUE, 1, 1;
INSERT INTO TEST1 (DATESTAMP, STATE_ID, IS_TEST, USER_ID, GROUP_ID) SELECT '02/01/2020', 31, TRUE, 1, 1;
INSERT INTO TEST1 (DATESTAMP, STATE_ID, IS_TEST, USER_ID, GROUP_ID) SELECT '05/01/2020', 29, NULL, 5, 1;
INSERT INTO TEST1 (DATESTAMP, STATE_ID, IS_TEST, USER_ID, GROUP_ID) SELECT '06/01/2020', 32, TRUE, 6, 1;
UPDATE TEST1 T1
SET SECONDARY_USER_ID = TSU.USER_ID
FROM TEST1 TS,
LATERAL (SELECT TOP 1 USER_ID
FROM TEST1 X
WHERE USER_ID <> 3
AND X.DATESTAMP < TS.DATESTAMP
AND TS.GROUP_ID = X.GROUP_ID
AND (
(X.STATE_ID = 29 and TS.IS_TEST IS NULL)
OR
(X.STATE_ID = 32 and TS.IS_TEST = TRUE)
)
ORDER BY X.DATESTAMP DESC
) TSU
WHERE TS.STATE_ID = 31
AND TS.ROWID = T1.ROWID;
The query will work if you replace:
SELECT TOP 1 [...] ORDER BY X.DATESTAMP DESC
with
ARRAY_AGG(X.USER_ID) WITHIN GROUP(ORDER BY X.DATESTAMP DESC)[0]
Full query:
UPDATE TEST1 T1
SET SECONDARY_USER_ID = TSU.USER_ID
FROM TEST1 TS,
LATERAL (SELECT ARRAY_AGG(X.USER_ID) WITHIN GROUP(ORDER BY DATESTAMP DESC)[0] USER_ID
FROM TEST1 X
WHERE USER_ID <> 3
AND X.DATESTAMP < TS.DATESTAMP
AND TS.GROUP_ID = X.GROUP_ID
AND (
(X.STATE_ID = 29 and TS.IS_TEST IS NULL)
OR
(X.STATE_ID = 32 and TS.IS_TEST = TRUE)
)
) TSU
WHERE TS.STATE_ID = 31
AND TS.ROWID = T1.ROWID;

Select parent row and its children and grand children

I have a table of comments and I am trying to accomplish being able to select the root comments (`ParentCommentId = 0) and then out of the parent comments I got back, also select me all of the children and those children's children and so on
For example, I would get back the row for CommentId = 1038. I would also want its child (CommentId = 1039) because the ParentCommentId = 1038, and then also CommentId = 1040 because it's ParentCommentId = 1039 and etc..
I tried the below query as I think I am on the right direction.
SELECT *
FROM
(SELECT
c.CommentId,
c.PostId,
c.Comment,
c.ParentCommentId,
c.CommentDateTime
FROM
[gallery].[Comments] c
INNER JOIN
[player].[Players] p ON p.UserId = c.UserId
WHERE
c.PostId = 32
AND ParentCommentId = 0) AS ParentComments
JOIN
(SELECT
c.CommentId,
c.PostId,
c.Comment,
c.ParentCommentId,
c.CommentDateTime
FROM
[gallery].[Comments] c
INNER JOIN
[player].[Players] p ON p.UserId = c.UserId
WHERE
c.PostId = 32
AND ParentCommentId != 0) AS ChildComments ON ParentComments.CommentId = ChildComments.ParentCommentId
But I am getting back wrong data for sure, like the child on same row as parent, ideally I would like the children to be separate rows. It also only goes 1 child deep and am definitely missing a lot of comments (there are way more than the 5 pictured below). I seem to only be getting a root comment and it's first child and not any that don't have children or the root comments grand children.
You're likely missing data because you used an inner join. This is problematic for cases where the grand-parent isn't in the table, e.g.:
for CommentID = 1036 we are not given the grand-parent/parent in the data extract
for any comment at level 1 of the hierarchy (i.e. Parent = 0)
Using a left join should fix this.
Data used
declare #target table (
CommentID int
,PostID int
,Comment varchar(200)
,ParentCommentId int
);
insert into #target
values
(1036, 32, 'Que?', 1033)
,(1037, 32, 'What up mane', 1035)
,(1038, 32, 'Hi', 0)
,(1039, 32, 'Can you see me?', 1038)
,(1040, 32, 'Test', 1039)
,(1041, 32, 'T', 1038)
,(1042, 32, 'Yoooo', 0)
,(1043, 32, 'Test?', 1042)
,(1044, 32, 'Test 1', 1039)
,(1045, 32, 'Test 2', 1039)
;
Getting the Hierarchy
A simple hierarchy table can be built showing the three levels of
select
GrandParentCommentID = isnull(b.ParentCommentID, 0)
,a.ParentCommentID
,a.CommentID
from #target as a
left join #target as b on a.ParentCommentId = b.CommentID
Full Answer
with Hierarchy as (
select
GrandParentCommentID = b.CommentID
,a.ParentCommentID
,a.CommentID
from #target as a
inner join #target as b on a.ParentCommentId = b.CommentID
)
select
hier.GrandParentCommentID
,hier.ParentCommentID
,hier.CommentID
,details.Comment
from Hierarchy as hier
inner join #target as details on details.CommentID = hier.CommentID
order by
hier.GrandParentCommentID
,hier.ParentCommentID
Results
Bonus
If you want to get the level as a column, you can use a recursive CTE as below (I'm sure there's better ways):
;with Hierarchy as (
select
GrandParentCommentID = 0
,ParentCommentID = 0
,CommentID
,hier_level = 1
from #target where ParentCommentID = 0
union all
select
GrandParentCommentID = h.ParentCommentID
,t.ParentCommentID
,t.CommentID
,hierarchy_level = h.hier_level + 1
from Hierarchy as h
inner join #target as t on t.ParentCommentId = h.CommentID
)
select * from Hierarchy
Add a grand-grand-child for demonstration:
insert into #target values (1045, 32, 'Test 2', 1039);
Results:

alias in case when sqlserver

I wrote a trigger to update status to Order from OrderDetails status:
BEGIN
/*
Order Status:
Pending = 0,
Processing = 1,
Proceeded = 2,
Completed = 3,
Cancelled = 4,
Order Detail Status:
Pending = 0,
Processing = 1,
Proceeded = 2,
Emailed = 3,
Ordered = 4,
Cancelled = 5
*/
IF (UPDATE([Status]))
BEGIN
UPDATE [Order]
SET [Status] =
CASE (SELECT MIN(od.[Status]) FROM OrderDetail od WHERE od.OrderId = i.OrderId)
WHEN 5 THEN 4
WHEN 4 THEN 3
WHEN 3 THEN 2
WHEN 2 THEN 2
WHEN 1 THEN 1
WHEN 0 THEN 0
END
FROM INSERTED i
WHERE [Order].Id = i.OrderId
END
END
You can see, WHEN OrderDetailStatus = 2 OR 3 THEN OrderStatus = 2, otherwise OrderStatus = OrderDetailStatus. For now, I have to list the values of status.
So, is it possible to create a alias for value statement, like this:
SET [Status] =
CASE (SELECT MIN(od.[Status]) FROM OrderDetail od WHERE od.OrderId = i.OrderId) AS val
WHEN 3 THEN 2
ELSE val
END
FROM INSERTED i
WHERE [Order].Id = i.OrderId
The only way I can think to "shorten" this would be:
UPDATE O
SET [Status] = CASE WHEN od.[Status] BETWEEN 3 AND 5 THEN od.[Status] - 1 ELSE od.[Status] END
FROM [Order] O
JOIN INSERTED i ON O.Id = i.OrderId
CROSS APPLY (SELECT MIN(od.[Status]) AS [Status]
FROM OrderDetail ca
WHERE ca.OrderId = i.OrderId) od;
On a different note, you should really avoid using reserved (or even key) words for object names. ORDER is a reserved word in SQL Server, so should really not be used. Status is a key word, so can be, but should also be avoided.

SQL join to an alias column name in SQL Server

Not sure if this is possible but I'm trying to join to an alias column in SQL Server.
Is it possible to change the JOIN below?
JOIN
#LOGFILE ON #CSIQUESTIONLOG.LogSeqNo = #LOGFILE.Seq
AND #LOGFILE.Contcode = 'WCM'
to
JOIN
#LOGFILE ON #CSIQUESTIONLOG.LogSeqNo = #LOGFILE.[SEQ2]
AND #LOGFILE.Contcode = 'WCM'
So it joins to the SEQ2 column - not the SEQ column, and so I only have to run one query
Sample data below:
IF OBJECT_ID('tempdb..#CSIQUESTIONLOG') IS NOT NULL
DROP TABLE #CSIQUESTIONLOG
SELECT *
INTO #CSIQUESTIONLOG
FROM (VALUES ('BA', '2017-01-01','123451', '185', 2),
('BA', '2017-01-01','123452', '185', 4),
('BA', '2017-01-01','123453', '184', 1),
('BA', '2017-01-01','123454', '183', 3),
('BA', '2017-01-01','123455', '182', 5),
('BA', '2017-01-01','123456', '181', 0),
('BA', '2017-01-01','123457', '182', 1),
('BA', '2017-01-01','7684417', '180', 2)) d (Dealer, Created, Logseqno, CSIseqno, Answer)
IF OBJECT_ID('tempdb..#LOGFILE') IS NOT NULL
DROP TABLE #LOGFILE
SELECT *
INTO #LOGFILE
FROM (VALUES (7684417, 'BA', 498, 'WCM', 1261723),
(7669984, 'BA', 38, 'CSI', 1261723),
(7685141, 'BA', 400, 'WCM', 1261750),
(7686369, 'BA', 193, 'CSI', 1261750),
(7692571, 'BA', 401, 'WCM', 1262289),
(7700336, 'BA', 38, 'CSI', 1262289)) d (Seq, Dealer, OpNum, Contcode, ContSeqNo)
SELECT
a.*, x.Seq AS [SEQ2]
FROM
#LOGFILE a
OUTER APPLY
(SELECT Seq
FROM #LOGFILE b
WHERE b.ContSeqNo = a.ContSeqNo AND b.ContCode = 'CSI') x
Final query:
SELECT
#CSIQUESTIONLOG.Created, #CSIQUESTIONLOG.CSIseqno,
#LOGFILE.OpNum,
COUNT (*) AS TOTAL
FROM
#CSIQUESTIONLOG
JOIN
#LOGFILE ON #CSIQUESTIONLOG.LogSeqNo = #LOGFILE.Seq
AND #LOGFILE.Contcode = 'WCM'
GROUP BY
#CSIQUESTIONLOG.Created, #CSIQUESTIONLOG.CSIseqno, #LOGFILE.OpNum
Firstly, in your final query there ins no column Seq2. Those are two seperate queries, and thus, they can't interact with each other.
Without the expected result set, I've just chanegd the SQL to what I believe you're after. If it's not, post your expected result set.
SELECT QL.Created,
QL.CSIseqno,
LF2.OpNum, --Not sure if this should be LF1, or LF2. Guessed 2
COUNT (*) AS TOTAL
FROM #CSIQUESTIONLOG QL
JOIN #LOGFILE LF1 ON QL.LogSeqNo = LF1.Seq AND LF1.Contcode = 'WCM'
JOIN #LOGFILE LF2 ON LF1.ContSeqNo = LF2.ContSeqNo AND LF2.ContCode = 'CSI'
GROUP BY QL.Created, --You were completely missing your GROUP BY
QL.CSIseqno,
LF2.OpNum;

SQL Server with left join/having sum/group by

Table B holds planned values. Table M hold actual values. I need to find all rows in table B where either there is no actual values (ie. joined) row in table M, or where joined rows have different total actual value rows. I am trying a combination of an outer join and having sum ... group by to achieve this, but it isn't working because the 'orphans' in table B are not being returned.
My query is:-
SELECT B.Id, B.Date, b.Ref,SUM(M.Actual_Volume), SUM(B.Planned_Volume),
SUM(M.Actual_Value),SUM(B.Planned_Value)
FROM
TableB B
left JOIN TableM M on M.Id = B.Id
inner JOIN TableX on TableX.FieldX = B.FieldX
WHERE TableX.FieldY = (SELECT T.FieldY from TableX T where T.FieldX = 408344)
AND TableX.FieldZ = (SELECT T1.FieldZ from TableX T1 where T1.FieldX = 408344)
group by B.Id, B.Date, B.Ref
having SUM(M.Actual_Volume) <> SUM(B.Planned_Volume)
OR SUM(M.Actual_Value) <> SUM(B.Planned_Value)
order by b.Id
If I use = instead of <> to compare the actuals and planned I get rows that join, but I need the rows where the actuals don't equal the planned, or where there is a planned but not an actual.
Thanks!
Table B
Id planned_vol planned val
19 2 350
28 1 100
53 3 650
61 1 50
Table M
M.Id B.Id actual_vol actual_val
58 19 2 350
65 28 1 100
66 53 1 150
So the query should return,
B.Id
53 (because planned_vol <> actual_vol and planned_val <> actual_val)
61 (because B.Id 61 is not in table M)
hth!
This is untested, but I think you need to move the having requirements into the left outer join requirements. Using CTEs (i.e. you need to be using SQL Server 2005 or later for this to work) is one way to do it.
Your having clause is forcing SQL Server to treat the B-M join as an inner join. There may be an alternative approach that does not use CTEs which checks for NULLs in all the right places. But I prefer the divide-and-conquer approach.
WITH
[BAlt] AS
(
SELECT
[B].[Id],
[B].[Date],
[B].[Ref],
SUM([B].[Planned_Volume]) AS [Planned_Volume],
SUM([B].[Planned_Value]) AS [Planned_Value],
FROM [TableB] AS [B]
INNER JOIN [TableX] AS [X1] ON [X1].[FieldX] = [B].[FieldX]
AND [X1].[FieldY] =
(
SELECT
[X2].[FieldY]
FROM [TableX] AS [X2]
WHERE [X2].[FieldX] = 408344
)
AND [X1].[FieldZ] =
(
SELECT
[X3].[FieldZ]
FROM [TableX] AS [X2]
WHERE [X3].[FieldX] = 408344
)
GROUP BY
[B].[Id],
[B].[Date],
[B].[Ref]
),
[MAlt] AS
(
SELECT
[M].[Id],
SUM([M].[Actual_Volume]) AS [Actual_Volume],
SUM([M].[Actual_Value]) AS [Actual_Value]
FROM [M]
GROUP BY
[M].[Id]
)
SELECT
[BAlt].[Id],
[BAlt].[Date],
[BAlt].[Ref],
[BAlt].[Planned_Volume],
[BAlt].[Planned_Value],
[MAlt].[Actual_Volume],
[MAlt].[Actual_Value]
FROM [BAlt]
LEFT OUTER JOIN [MAlt] ON [MAlt].[Id] = [BAlt].[Id]
AND
(
[MAlt].[Actual_Volume] <> [BAlt].[Planned_Volume]
OR [MAlt].[Actual_Value] <> [BAlt].[Planned_Value]
)
ORDER BY
[BAlt].[Id]
I really don't see a problem:
create table b
( B_id int
,PlannedVolume int
,PlannedValue int
)
create table M
( M_id int
,B_id int
,ActualVolume int
,ActualValue int
)
insert b (b_id, PlannedVolume, PlannedValue)
values (19, 2, 350),
(28, 1, 100),
(53, 3, 650),
(61, 1, 50)
insert m (m_id, b_id, ActualVolume, ActualValue)
values (58, 19, 2, 350),
(65, 28, 1, 100),
(66, 53, 1, 150),
(67, 53, 1, 100)
select b.b_id
from b
left join
( select b_id
,sum(ActualVolume) as ActualVolume
,sum(ActualValue) as ActualValue
from m
group by b_id
) m
on m.b_id = b.b_id
where
m.b_id is null
or
(m.ActualValue <> b.PlannedValue and m.ActualVolume <> b.PlannedVolume)

Resources