Is it necessary to OUTER APPLY multiple times in the following query, or could I get the same result by somehow using 1 APPLY with some included CASE statements?
(p.s. I realise FIRST_VALUE is an alternative but I'm interested in the use of APPLY)
SELECT
Name,
AgeCat,
Country,
X.DateKey,
WagerAmt = SUM(Revenue),
FirstNAC = z.DateKey,
FirstNA = j.DateKey,
FirstN = q.DateKey
FROM #x X
OUTER APPLY (
SELECT TOP(1) DateKey
FROM #x Y
WHERE X.Name = Y.Name AND
X.AgeCat = Y.AgeCat AND
x.Country = y.Country
ORDER BY DateKey
) z
OUTER APPLY (
SELECT TOP(1) DateKey
FROM #x Y
WHERE X.Name = Y.Name AND
X.AgeCat = Y.AgeCat
ORDER BY DateKey
) j
OUTER APPLY (
SELECT TOP(1) DateKey
FROM #x Y
WHERE X.Name = Y.Name
ORDER BY DateKey
) q
GROUP BY
Name,
AgeCat,
Country,
X.DateKey
z.DateKey,
j.DateKey,
q.DateKey;
I don't see how this can be done with a case. A sub-select of some type will be necessary.
EDIT:
As JOINs the query would look like this:
SELECT
Name,
AgeCat,
Country,
X.DateKey,
WagerAmt = SUM(Revenue),
FirstNAC = z.DateKey,
FirstNA = j.DateKey,
FirstN = q.DateKey
FROM #x X
inner join
(
SELECT TOP(1) DateKey
FROM #x Y
WHERE X.Name = Y.Name AND
X.AgeCat = Y.AgeCat AND
x.Country = y.Country
ORDER BY DateKey
) z
inner join
(
SELECT TOP(1) DateKey
FROM #x Y
WHERE X.Name = Y.Name AND
X.AgeCat = Y.AgeCat
ORDER BY DateKey
) j
inner join (
SELECT TOP(1) DateKey
FROM #x Y
WHERE X.Name = Y.Name
ORDER BY DateKey
) q
GROUP BY
Name,
AgeCat,
Country,
X.DateKey,
z.DateKey,
j.DateKey,
q.DateKey;
Note that the GROUP BY has changed as well: since APPLY returns 1 row only, and theoretically these sub-selects could return multiples, their fields need to be in the GROUP too.
Related
I want to update the below table where name is null to repeat the pattern from first 10 rows.
enter image description here
sql:
declare #name varchar(255)
set #name = select distinct name from #temp
update #temp
set name = #name
where name is not null
Obviously the above the query will not work due to multiple values. I want to update the table where it's null to fill with the pattern from above.
While I agree with Damien's suggestion that you not conflate databases and spreadsheets, you can do this quite easily with window functions and the modulo operator:
WITH x AS
(
SELECT dt,
name,
seq = ROW_NUMBER() OVER (ORDER BY dt),
c = COUNT(*) OVER()
FROM #temp
WHERE name IS NOT NULL
),
y AS
(
SELECT dt,
name,
seq = ROW_NUMBER() OVER (ORDER BY dt)
FROM #temp WHERE dt > (SELECT MAX(dt) FROM x)
)
UPDATE y SET y.name = x.name
FROM x INNER JOIN y
ON x.seq % x.c = y.seq % x.c;
Working example in this fiddle.
This doesn't validate, though, that the non-null values are all contiguous at the beginning - if you need the query to validate that, it's considerably more complicated.
I am just posting an Answer according to your picture.
I think if you want to use it some where else , then you should change it. (It is not General)
update
t1
set
t1.name = t2.name
from
(
select *,row_number() over (order by dt) - 1 as rn
from
(
select
*
from
tbl1
where
name is not null
)
as a
)
as t1
inner join
(
select
name,
day,
mod(rn, 10) as rn
from
(
select *,row_number() over (order by dt) - 1 as rn
from
(
select
*
from
tbl1
where
name is null
)
as b
)
as tmp
)
as t2
on t1.day = t2.day
and t1.rn = t2.rn
SELECT id,
login_id,
count,
case when count = 0 then 'Cat_A'
WHEN count between 1 and 10 then 'Cat_B'
WHEN count > 10 then 'Cat_C'
WHEN count IS NULL THEN 'Cat D'
END as Category
FROM
(
select id,login_id,min(ord_count) AS count
FROM table_1 X
JOIN table_2 Y
ON X.id_col = Y.id_col
WHERE date = '2022-02-02'
AND login_id = 'True'
group by id,login_id
)A
LEFT JOIN
(
SELECT id,COUNT(X.ord_no) AS count_of_orders
FROM table_1 X
WHERE X.date = '2022-02-02'
group by id
)B
ON A.id=B.id
When I join these two tables, I'm getting NULL values for the unmatched records.
I need to replace those NULL records to some hardcoded value say 'XYZ'.
Any guidance on how to achieve this please?
So the top level select needs to name which ID it is using (other DB's don't require this snowflake does), given you are selecting from A and b.id might be missing, it should be a.id
count_of_orders is not used, so currently the LEFT JOIN to B is pointless, given your question is about LEFT JOIN this must be the column you a referring to??
The replace NULL values can be done via COALESCE or NVL or ZEROIFNULL, given the only null thing is a count, zeroifnull seems to make sense here.
which all make me think your SQL needs to look like:
SELECT
a.id,
a.login_id,
a.count,
case
WHEN a.count = 0 then 'Cat_A'
WHEN a.count between 1 and 10 then 'Cat_B'
WHEN a.count > 10 then 'Cat_C'
WHEN a.count IS NULL THEN 'Cat D'
END as Category,
ZEROIFNULL(b.count_of_orders) as count_of_orders
FROM (
SELECT
id,
login_id,
min(ord_count) AS count
FROM table_1 AS X
JOIN table_2 AS Y
ON X.id_col = Y.id_col
WHERE date = '2022-02-02'
AND login_id = 'True'
group by id,login_id
) as A
LEFT JOIN (
SELECT
x.id,
COUNT(X.ord_no) AS count_of_orders
FROM table_1 as X
WHERE X.date = '2022-02-02'
group by x.id
)as B
ON A.id=B.id
The A sub-select really should use the aliases you named X, Y so we know which tables id, login_id, ord_count, & date all come from.
I need to create something like this
SELECT x.id
, x.name
, x.type
,(
IF x.type = 1
(SELECT SUM(Col1) FROM TableA WHERE ... etc)
ELSE IF x.type = 2
(SELECT SUM(Col2) FROM TableB WHERE ... etc)
) AS Total
FROM TableX as x
So I am trying to select a different sub query according to the value of x.type
Wing
Try to use LEFT JOIN and COALESCE. Use your conditions of x.type to join the tables.
COALESCE (Transact-SQL): Evaluates the arguments in order and returns the current value of the first expression that initially does not evaluate to NULL.
https://msdn.microsoft.com/en-us/library/ms190349.aspx
SELECT x.id
, x.name
, x.type
, COALESCE(SUM(TableA.Column), SUM(TableB.Column)) as column_xyz
FROM TableX as x
LEFT JOIN TableA ON x.type = 1 AND ...
LEFT JOIN TableB ON x.type = 2 AND ...
You can also use CASE WHEN ... THEN ... instead of COALESCE to define which column to use.
Use CASE statement
SELECT x.id,
x.name,
x.type,
CASE
WHEN x.type = 1 THEN (SELECT Sum(Col1)
FROM TableA Where...)
WHEN x.type = 2 THEN (SELECT Sum(Col2)
FROM TableB Where .. )
END AS Total
FROM TableX AS x
You can use CASE WHEN as the below:
SELECT
x.id,
x.name,
x.type,
CASE
WHEN x.type = 1 THEN (SELECT SUM(A.Col1) FROM TableA A WHERE 1 = 1)
WHEN x.type = 2 THEN (SELECT SUM(B.Col2) FROM TableB B WHERE 1 = 1)
ELSE NULL END AS Total
FROM
TableX as x
You can use case expression:
select t.* ,
Case when t.type = 1 then (select sum(col1) ... TableA)
when t.type = 2 then (select sum(col2) ... TableB)
End as Total
From tableX t
By using variable -
DECLARE #SumA INT = SELECT SUM(Col1) FROM TableA WHERE ... etc
DECLARE #SumB INT = SELECT SUM(Col2) FROM TableB WHERE ... etc
SELECT x.id
, x.name
, x.type
,( CASE x.type
WHEN 1 THEN #SumA
WHEN 2 THEN #SumB
END
) AS Total
FROM TableX as x
Choose the datatype for Sum variable accordingly (if Decimal).
I have a ProductStatus table as listed below. I need to list all products whose latest status is “SU”. Along with that I need to list what was the previous status of this product.
Based on referring various posts, it seems like CROSS APPLY will be suitable for this. I made an attempt as listed below but that didn’t give the expected result.
What is the best way to achieve this in SQL Server 2005 (without using subquery)?
DECLARE #ProductStatus TABLE (ProductStatusID INT, productCode VARCHAR(5), statusCode VARCHAR(2))
INSERT INTO #ProductStatus
SELECT 1,'10011','RE' --Recevied
UNION
SELECT 2,'10011','SU' --Suspended
UNION
SELECT 3,'10012','IT' -- In Transit
UNION
SELECT 4,'10012','RE' -- Received
UNION
SELECT 10,'10012','PR' -- Produced
UNION
SELECT 12,'10012','SU' -- Suspended
UNION
SELECT 14,'10013','RE' -- Recevied
UNION
SELECT 16,'10014','SU' -- Recevied
UNION
SELECT 18,'10014','RE' -- Recevied
CROSS APPLY attempt
SELECT *
FROM #ProductStatus P
CROSS APPLY
(
SELECT MAX(V.ProductStatusID) as maxVal
FROM #ProductStatus V
WHERE V.ProductCode = P.ProductCode
AND V. ProductStatusID < P.ProductStatusID
GROUP BY V.ProductCode
)ML
WHERE P.statusCode = 'SU'
EXPECTED RESULT
You can do this with cross apply but I think row_number() is an easier approach:
select ProductCode,
max(case when seqnum = 1 then statusCode end) as LastStatus,
max(case when seqnum = 2 then statusCode end) as PrevStatus
from (select p.*,
row_number() over (partition by ProductCode order by ProductStatusId desc) as seqnum
from #ProductStatus p
) p
group by ProductCode
having max(case when seqnum = 1 then statusCode end) = 'SU';
Lijo, I've structured it as a CTE so you can see how I've developed my ideas. You can refactor it as sub-queries without affecting the meaning if you are more comfortable with those.
;with MostRecentStatus as (
select
MAX(ProductStatusID) as ProductStatusID,
productCode
from #ProductStatus as p1
group by productCode
)
,MostRecentIsSU as (
select
p2.ProductStatusID,
p2.productCode,
p2.statusCode
from MostRecentStatus as mrs
inner join #ProductStatus as p2
on p2.ProductStatusID = mrs.ProductStatusID
and p2.statusCode = 'SU'
)
select
m.ProductStatusID,
m.productCode,
m.statusCode,
p3.statusCode as PrevStatus,
p3.ProductStatusID as PrevProductStatusID
from MostRecentIsSU as m
left outer join #ProductStatus as p3
on p3.productCode = m.productcode
and p3.ProductStatusID = m.ProductStatusID - 1;
Edit: ..and here's the ROW_NUMBER() version with kudos to #attila.
;with InSquence as
(
select
ProductStatusID,
productCode,
statusCode,
ROW_NUMBER() OVER(PARTITION BY productCode ORDER BY ProductStatusID desc) as Sequence
from #ProductStatus
)
,FirstIsSU as
(
select
ProductStatusID,
productCode
from InSquence
where Sequence = 1
and statusCode = 'SU'
)
,PreviousCode as
(
select
ProductStatusID,
productCode,
statusCode
from InSquence
where Sequence = 2
)
select
f.ProductStatusID,
f.productCode,
'SU' as CurrentStatus,
p.statusCode as PrevStatus,
p.ProductStatusID as PrevProductStatusID
from FirstIsSU as f
left outer join PreviousCode as p
on p.productCode = f.ProductCode;
Here is a convoluted solution which serves mainly to illustrate that this should probably be done using row_number() :)
SELECT
F.productCode, F.statusCode, F.productStatusID, F.PriorProductStatusID, PriorStatus.statusCode
FROM
(
SELECT
PCS.productCode, PCS.statusCode, PCS.productStatusID, MAX(PS.productStatusID) PriorProductStatusID
FROM
(
SELECT productCode, MAX(productStatusID) productStatusID
FROM #ProductStatus
GROUP BY productCode
) LatestStatus
INNER JOIN
#ProductStatus PCS
ON PCS.productCode = LatestStatus.productCode
AND PCS.productStatusID = LatestStatus.productStatusID
AND PCS.statusCode = 'SU'
LEFT OUTER JOIN
#ProductStatus PS
ON PS.productCode = PCS.productCode
AND PS.productStatusID < PCS.productStatusID
GROUP BY PCS.productCode, PCS.statusCode, PCS.productStatusID
) F
LEFT OUTER JOIN
#ProductStatus PriorStatus
ON F.productCode = PriorStatus.productCode
AND F.PriorProductStatusID = PriorStatus.ProductStatusID
I have to COUNT some rows from multiple tables. Before I can do multiple COUNT I will have to subselect. The problem here is that I need to JOIN some values in order to get the right result.
SELECT
sponsor.Name As SponsorName,
COUNT(participants.[Table]) AS ParticipantCount,
( SELECT
COUNT(guestcards.[Table])
FROM
guestcards
WHERE
guestcards.EventID = #EventID
AND
guestcards.[Table] = #Table
AND
guestcards.SponsorID = participants.SponsorID
-- Here lies the problem.
-- I will need to check up on another value to ensure I get the right rows, but participants.SponsorID is not here because of no join :-(
) AS GuestParticipantCount
FROM
participants
LEFT JOIN
sponsor
ON
sponsor.ID = participants.SponsorID
WHERE
participants.EventID = #EventID
AND
participants.[Table] = #Table
GROUP BY
sponsor.Name
Guestcards table holds: sponsorid, eventid, tablename
Participantstable holds: sponsorid, eventid, tablename
Sponsor table holds: id, name
I need to count how many "Participants" there are and how many "Guestcards" that in a particulary event. These participants have a table (where they should sit) and so does the guestcards. I need to check up on if its the same "table" where they sit.
So I need to count how many are sitting at table "A1" or table "A2" etc.
The result I am after is like:
"Sponsor Name has 5 participants and 3 guestcards. They sit on A1"
I hope I made my self clear
Here's exact equivalent of you query (grouping on sponsor.Name):
SELECT sponsor.name,
COALESCE(SUM(participantCount), 0),
COALESCE(SUM(guestcardsCount), 0)
FROM (
SELECT sponsorId, COUNT(*) AS participantCount
FROM participants
WHERE eventId = #eventId
AND [table] = #table
GROUP BY
sponsorId
) p
FULL JOIN
(
SELECT sponsorId, COUNT(*) AS guestcardsCount
FROM guestdcards
WHERE eventId = #eventId
AND [table] = #table
GROUP BY
sponsorId
) g
ON g.sponsorId = p.sponsorId
FULL JOIN
sponsor s
ON s.id = COALESCE(p.sponsorId, g.sponsorId)
GROUP BY
s.sponsorName
However, I believe you want something more simple:
SELECT sponsorName, participantCount, guestcardsCount
FROM sponsor s
CROSS APLLY
(
SELECT COUNT(*) AS participantCount
FROM participants
WHERE sponsorId = s.id
AND eventId = #eventId
AND [table] = #table
) p
CROSS APLLY
(
SELECT COUNT(*) AS guestcardsCount
FROM guestdcards
WHERE sponsorId = s.id
AND eventId = #eventId
AND [table] = #table
) g
Update:
SELECT sponsor.name,
COALESCE(participantCount, 0),
COALESCE(guestcardsCount, 0)
FROM (
SELECT sponsorId, COUNT(*) AS participantCount
FROM participants
WHERE eventId = #eventId
AND [table] = #table
GROUP BY
sponsorId
) p
FULL JOIN
(
SELECT sponsorId, COUNT(*) AS guestcardsCount
FROM guestdcards
WHERE eventId = #eventId
AND [table] = #table
GROUP BY
sponsorId
) g
ON g.sponsorId = p.sponsorId
JOIN sponsor s
ON s.id = COALESCE(p.sponsorId, g.sponsorId)