How to use WITH clause with UNION ALL in SQL Server - sql

I'm trying but I don't know how to combine two SQL statements including the WITH clause with the UNION ALL. In each of the WITH SQL statements the difference is the WHERE clause.
WITH cte AS
(
SELECT
CMCONTRACTS.CMSERIALNUMBER, CMACTIVITIES.CMID,
CMACTIVITIES.CMSTART, CMACTIVITIES.CMFINISH,
CMACTIVITIES.CMSTATUSTYPE,
ROW_NUMBER() OVER (PARTITION BY CMCONTRACTS.CMSERIALNUMBER
ORDER BY CMACTIVITIES.CMFINISH DESC) RN
FROM
CMACTIVITIES
LEFT JOIN
CMCONTRACTS ON CMACTIVITIES.CMCONTRACTID = CMCONTRACTS.CMID
WHERE
CMACTIVITIES.CMSTATUSTYPE = 3
)
SELECT
CMID, CMSTART, CMFINISH, CMSERIALNUMBER, CMSTATUSTYPE
FROM
cte
WHERE
RN = 1
UNION ALL
WITH cte AS
(
SELECT
CMCONTRACTS.CMSERIALNUMBER, CMACTIVITIES.CMID,
CMACTIVITIES.CMSTART, CMACTIVITIES.CMFINISH,
CMACTIVITIES.CMSTATUSTYPE,
ROW_NUMBER() OVER (PARTITION BY CMCONTRACTS.CMSERIALNUMBER
ORDER BY CMACTIVITIES.CMFINISH ASC) RN
FROM
CMACTIVITIES
LEFT JOIN
CMCONTRACTS ON CMACTIVITIES.CMCONTRACTID = CMCONTRACTS.CMID
WHERE
CMACTIVITIES.CMSTATUSTYPE = '2'
)
SELECT
CMID, CMSTART, CMFINISH, CMSERIALNUMBER, CMSTATUSTYPE
-- GXSTARTDATE, GXENDDATE, GXFORMULA, GXPRLSID
FROM
cte
WHERE
RN = 1
When I run it, I get the following error :
Msg 156, Level 15, State 1, Line 26
Incorrect syntax near the keyword 'WITH'.
Msg 319, Level 15, State 1, Line 26
Incorrect syntax near the keyword 'with'. If this statement is a common table expression, an xmlnamespaces clause or a change tracking context clause, the previous statement must be terminated with a semicolon.
Running these two separate SQL queries a take the expected result. But I want to take all results from both previous SQL queries including the WITH clause in one query.

You should first make the CTE's like this:
WITH cte
AS (SELECT
CMCONTRACTS.CMSERIALNUMBER,
CMACTIVITIES.CMID,
CMACTIVITIES.CMSTART,
CMACTIVITIES.CMFINISH,
CMACTIVITIES.CMSTATUSTYPE,
ROW_NUMBER() OVER (PARTITION BY CMCONTRACTS.CMSERIALNUMBER
ORDER BY CMACTIVITIES.CMFINISH DESC
) RN
FROM CMACTIVITIES
LEFT JOIN CMCONTRACTS ON CMACTIVITIES.CMCONTRACTID = CMCONTRACTS.CMID
WHERE CMACTIVITIES.CMSTATUSTYPE = 3),
cte2
AS (SELECT
CMCONTRACTS.CMSERIALNUMBER,
CMACTIVITIES.CMID,
CMACTIVITIES.CMSTART,
CMACTIVITIES.CMFINISH,
CMACTIVITIES.CMSTATUSTYPE,
ROW_NUMBER() OVER (PARTITION BY CMCONTRACTS.CMSERIALNUMBER
ORDER BY CMACTIVITIES.CMFINISH ASC
) RN
FROM CMACTIVITIES
LEFT JOIN CMCONTRACTS ON CMACTIVITIES.CMCONTRACTID = CMCONTRACTS.CMID
WHERE CMACTIVITIES.CMSTATUSTYPE = '2')
SELECT
CMID,
CMSTART,
CMFINISH,
CMSERIALNUMBER,
CMSTATUSTYPE
FROM cte
WHERE RN = 1
UNION ALL
SELECT
CMID,
CMSTART,
CMFINISH,
CMSERIALNUMBER,
CMSTATUSTYPE -- GXSTARTDATE, GXENDDATE, GXFORMULA, GXPRLSID
FROM cte2
WHERE RN = 1;

In this particular instance you don't need two CTEs, just add CMACTIVITIES.CMSTATUSTYPE to the PARTITION BY clause.
WITH cte AS (
SELECT
c.CMSERIALNUMBER,
a.CMID,
a.CMSTART,
a.CMFINISH,
a.CMSTATUSTYPE,
ROW_NUMBER() OVER (PARTITION BY c.CMSERIALNUMBER, a.CMSTATUSTYPE
ORDER BY
CASE WHEN a.CMSTATUSTYPE = 2 THEN a.CMFINISH END ASC,
CASE WHEN a.CMSTATUSTYPE = 3 THEN a.CMFINISH END DESC
) RN
FROM CMACTIVITIES a
LEFT JOIN CMCONTRACTS c ON a.CMCONTRACTID = c.CMID
WHERE a.CMSTATUSTYPE IN (2, 3)
)
SELECT
CMID,
CMSTART,
CMFINISH,
CMSERIALNUMBER,
CMSTATUSTYPE
FROM cte
WHERE RN = 1;
It's unclear if CMSTATUSTYPE is a string or a number. You should stick to the one the column is defined as.

The answer you accepted is not very DRY.
The two branches of the CTE are pretty much identical except for filtering on different CMSTATUSTYPE and differing sort directions for the row numbering.
You can get this efficiently without needing to sort in both directions using LAG and LEAD.
In the below all the rows will have 0 for their IsStartOfGroup and IsEndOfGroup values except when there is no previous or next row (respectively) in which case that flag will be set to 1.
WITH CTE AS
(
SELECT CMCONTRACTS.CMSERIALNUMBER,
CMACTIVITIES.CMID,
CMACTIVITIES.CMSTART,
CMACTIVITIES.CMFINISH,
CMACTIVITIES.CMSTATUSTYPE,
LAG(0,1,1) OVER (PARTITION BY CMCONTRACTS.CMSERIALNUMBER ORDER BY CMACTIVITIES.CMFINISH ASC) AS IsStartOfGroup,
LEAD(0,1,1) OVER (PARTITION BY CMCONTRACTS.CMSERIALNUMBER ORDER BY CMACTIVITIES.CMFINISH ASC) AS IsEndOfGroup
FROM CMACTIVITIES
LEFT JOIN CMCONTRACTS
ON CMACTIVITIES.CMCONTRACTID = CMCONTRACTS.CMID
WHERE CMACTIVITIES.CMSTATUSTYPE IN (2,3)
)
SELECT *
FROM CTE
WHERE (CMSTATUSTYPE = 2 AND IsStartOfGroup = 1)
OR (CMSTATUSTYPE = 3 AND IsEndOfGroup = 1)

Related

PostgreSQL Percent Change using Row Number

I'm trying to find the percent change using row number with PostgreSQL but I'm running into an error where my "percent_change" column shows 0.
Here is what I have as my code.
WITH CTE AS (
SELECT date, sales, ROW_NUMBER() OVER (ORDER by date) AS rn
FROM sales_2019)
SELECT c1.date, c1.sales,
CAST(COALESCE (((c1.sales - c2.sales) * 1.0 / c2.sales) * 100, 0) AS INT) AS percent_change
FROM CTE AS c1
LEFT JOIN CTE AS c2
ON c1.date = c2.date AND c1.rn = c2.rn + 1
Here is my SQL table in case it's needed. Thank you in advance, I greatly appreciate it.
You can use LAG() for your requirement:
select
date,
sales,
round(coalesce((((sales-(lag(sales) over (order by date)))*1.0)/(lag(sales) over (order by date)))*100,0),2)
from sales_2019
or you can try with WITH clause
with cte as ( select
date,
sales,
coalesce(lag(sales) over (order by date),0) as previous_month
from sales_2019
)
select
date,
sales,
round( coalesce( (sales-previous_month)*1.0/nullif(previous_month,0),0 )*100,2)
from cte
DEMO
EDIT as per requirement in comment
with cte as ( select
date_,
sales,
ROW_NUMBER() OVER (ORDER by date_) AS rn1,
ROW_NUMBER() OVER (ORDER by date_)-1 AS rn2
from sales_2019
)
select t1.date_,
t1.sales,
round( coalesce( (t1.sales-t2.sales)*1.0/nullif(t2.sales,0),0 )*100,2)
from cte t1 left join cte t2 on t1.rn2=t2.rn1
DEMO

conditional running sum

I'm trying to return the number of unique users that converted over time.
So I have the following query:
WITH CTE
As
(
SELECT '2020-04-01' as date,'userA' as user,1 as goals Union all
SELECT '2020-04-01','userB',0 Union all
SELECT '2020-04-01','userC',0 Union all
SELECT '2020-04-03','userA',1 Union all
SELECT '2020-04-05','userC',1 Union all
SELECT '2020-04-06','userC',0 Union all
SELECT '2020-04-06','userB',0
)
select
date,
COUNT(DISTINCT
IF
(goals >= 1,
user,
NULL)) AS cad_converters
from CTE
group by date
I'm trying to count distinct user but I need to find a way to apply the distinct count to the whole date. I probably need to do something like a cumulative some...
expected result would be something like this
date, goals, total_unique_converted_users
'2020-04-01',1,1
'2020-04-01',0,1
'2020-04-01',0,1
'2020-04-03',1,2
'2020-04-05',1,2
'2020-04-06',0,2
'2020-04-06',0,2
Below is for BigQuery Standard SQL
#standardSQL
SELECT t.date, t.goals, total_unique_converted_users
FROM `project.dataset.table` t
LEFT JOIN (
SELECT a.date,
COUNT(DISTINCT IF(b.goals >= 1, b.user, NULL)) AS total_unique_converted_users
FROM `project.dataset.table` a
CROSS JOIN `project.dataset.table` b
WHERE a.date >= b.date
GROUP BY a.date
)
USING(date)
I would approach this by tagging when the first goal is scored for each name. Then simply do a cumulative sum:
select cte.* except (seqnum), countif(seqnum = 1) over (order by date)
from (select cte.*,
(case when goals = 1 then row_number() over (partition by user, goals order by date) end) as seqnum
from cte
) cte;
I realize this can be expressed without the case in the subquery:
select cte.* except (seqnum), countif(seqnum = 1 and goals = 1) over (order by date)
from (select cte.*,
row_number() over (partition by user, goals order by date) as seqnum
from cte
) cte;

SQL DISTINCT Column with 2nd Criteria as datetime

I have on just started learning SQL in SQL Server Management Studio and getting thrown into the deep end.
I just need unique DriverID that has a LogoffTime in the last 3 month, with the headings included below.
What I have so far:
SELECT
Dr.DriverName, Dr.DriverNumber, Dr.DriverID,
DL.DriverID, DL.LogoffTime,
ROW_NUMBER() OVER (PARTITION BY DL.DriverID ORDER BY DL.LogoffTime DESC) AS rn
FROM
Taxihistory.dbo.DriverLogon DL, Taxihistory.dbo.Driver Dr
WHERE
DL.DriverID = Dr.DriverID
AND DL.LogoffTime <= '20180931'
AND rn = 1
ORDER BY
DL.LogoffTime DESC;
I am currently getting this error:
Msg 207, Level 16, State 1, Line 7
Invalid column name 'rn'
In case you want to explore CTE (Common Table Expression) option, you may also be able to achieve this with CTE. You can try something like below:
WITH CTE AS (
SELECT dr.drivername,
dr.drivernumber,
dr.driverid,
dl.logofftime,
row_number() OVER (PARTITION BY dl.driverid
ORDER BY dl.logofftime DESC) AS rn
FROM taxihistory.dbo.driverlogon dl
INNER JOIN taxihistory.dbo.driver dr
ON dr.driverid = dl.driverid
WHERE dl.logofftime <= Convert(datetime, '2018-09-30') )
SELECT tbl.drivername,
tbl.drivernumber,
tbl.driverid,
tbl.logofftime
FROM CTE tbl
WHERE tbl.rn = 1
ORDER BY tbl.logofftime DESC;
You cannot use column aliases in the WHERE clause. Neither can you use row_number() there. You have to wrap the query with the row_number() in a subquery and select from that.
SELECT x.drivername,
x.drivernumber,
x.driverid,
x.logofftime
FROM (SELECT dr.drivername,
dr.drivernumber,
dr.driverid,
dl.logofftime,
row_number() OVER (PARTITION BY dl.driverid
ORDER BY dl.logofftime DESC) rn
FROM taxihistory.dbo.driverlogon dl
INNER JOIN taxihistory.dbo.driver dr
ON dr.driverid = dl.driverid
WHERE dl.logofftime <= '20180930') x
WHERE x.rn = 1
ORDER BY x.logofftime DESC;
It is also advisable to use explicit join syntax. And I do hope, that driverlogon.logofftime is not an [n][var]char but some date/time type.
You should get the logon from the last 3 months up to the current date by doing so:
SELECT Dr.DriverName, Dr.DriverNumber, Dr.DriverID, DL.DriverID, DL.LogoffTime
FROM Taxihistory.dbo.DriverLogon DL,
INNER JOIN Taxihistory.dbo.Driver Dr ON DL.DriverID = Dr.DriverID
WHERE DL.LogOffTime < DATEADD(MONTH, -3, GETDATE())
ORDER BY DL.LogoffTime DESC;

SQL Server 2008 - Finding duplicates using ROW_NUMBER

i have the following SQL which works to find duplicates
SELECT *
FROM (SELECT
id,
ShipAddress,
ShipZIPPostal,
ROW_NUMBER() OVER (PARTITION BY shipaddress, shipzippostal ORDER BY shipaddress) ROWNUM
FROM orders
WHERE CONVERT(date, orderdate) = CONVERT(date, GETDATE())) x
WHERE rownum > 1
I would like to only see rows where, if the value of Rownum > 1 then i would like to see its corresponding row where rownum =1.
So basically, if a row has duplicates, i want to see the original row and all its duplicates.
If a row does not have duplicates, then i don't want to see it (it will have rownum = 1 )
How would i do this please?
cheers
Use count(*) rather than row_number():
SELECT *
FROM (SELECT id, ShipAddress, ShipZIPPostal,
COUNT(*) OVER (PARTITION BY shipaddress, shipzippostal) as cnt
FROM orders
WHERE CONVERT(date, orderdate) = CONVERT(date, GETDATE())
) x
WHERE cnt > 1;
In addition to Gordon's answer, if you want to keep the row_number() approach for some academic reason, you can do this:
SELECT *
FROM (SELECT
id,
ShipAddress,
ShipZIPPostal,
ROW_NUMBER() OVER (PARTITION BY shipaddress, shipzippostal ORDER BY shipaddress) ROWNUM
FROM orders
WHERE CONVERT(date, orderdate) = CONVERT(date, GETDATE())) x
WHERE EXISTS(
SELECT * FROM x x2
WHERE x.shipaddress=x2.shipaddress
AND x.shipzippostal=x2.shipzippostal
AND x2.ROWNUM>1
)
I'd actually prefer a cte structure like this personally:
WITH cte AS (
SELECT
id,
ShipAddress,
ShipZIPPostal,
ROW_NUMBER() OVER (PARTITION BY shipaddress, shipzippostal ORDER BY shipaddress) ROWNUM
FROM orders
WHERE CONVERT(date, orderdate) = CONVERT(date, GETDATE())
)
SELECT *
FROM cte
WHERE EXISTS(
SELECT * FROM cte x2
WHERE cte.shipaddress=x2.shipaddress
AND cte.shipzippostal=x2.shipzippostal
AND x2.ROWNUM>1
)
You could add a second row_number, but change the order by to ID so it will be different, and compare the 2 row_numbers
SELECT
*
FROM
(SELECT
id,
ShipAddress,
ShipZIPPostal,
ROW_NUMBER() OVER (PARTITION BY shipaddress,shipzippostal ORDER BY id) ROWNUM1,
ROW_NUMBER() OVER (PARTITION BY shipaddress,shipzippostal ORDER BY id DESC) ROWNUM2
FROM
orders
WHERE
CONVERT(DATE,orderdate) = CONVERT(DATE,GETDATE())
) x
WHERE
ROWNUM1 <> ROWNUM2

Bad performance with sql query

I have a snippet of SQL that compared the last two records and gives the datediff in seconds, however the way I have it is quite slow taking up to 20 seconds to execute depending how many controllerID's I needs to check.
What would be a more efficient way of do doing this?
select
T.controllerID,
datediff(ss, T.Max_dtReading, T1.Max_dtReading) As ElaspedTime
from
(select
controllerID,
max(dtReading) as Max_dtReading
from
ReaderData
where
CardID = 'FFFFFFF0' AND (controllerID in(2,13,28,30,37,40))
group by
controllerID) as T
outer apply
(select
max(T1.dtReading) as Max_dtReading
from
ReaderData as T1
where
T1.CardID = 'FFFFFFF0' AND (controllerID in(2,13,28,30,37,40))
and T1.controllerID = T.controllerID
and T1.dtReading < T.Max_dtReading) as T1
I might suggest conditional aggregation for this:
select controllerID,
datediff(second, max(dtReading), min(dtReading)
) As ElaspedTime
from (select controllerID, dtReading,
row_number() over (partition by controllerID order by dtReading desc) as seqnum
from ReaderData
where CardID = 'FFFFFFF0' AND
controllerID in (2, 13, 28, 30, 37, 40)
) r
where seqnum <= 2
group by controllerID
You can use ROW_NUMBER() in order to locate the records with the 2 highest dtReading values, then join these together to calculate the difference:
;WITH CTE AS (
SLEECT controllerID, dtReading,
ROW_NUMBER() OVER (PARTITION BY controllerID
ORDER BY dtReading DESC) AS rn
FROM ReaderData
WHERE CardID = 'FFFFFFF0' AND (controllerID IN (2,13,28,30,37,40))
)
SELECT c1.controllerID,
DATEDIFF(ss, c1.dtReading, c2.dtReading) AS ElaspedTime
FROM CTE c1
INNER JOIN CTE c2 ON (c1.controllerID = c2.controllerID)
AND c1.rn = 1 AND c2.rn = 2
;WITH CTE AS
(select controllerID
,dtReading
,ROW_NUMBER() OVER (PARTITION BY controllerID ORDER BY dtReading DESC) rn
from ReaderData
where CardID = 'FFFFFFF0'
AND controllerID IN (2,13,28,30,37,40)
)
select C1.controllerID
,datediff(ss, C1.dtReading, C2.dtReading) As ElaspedTime
from CTE C1
LEFT JOIN CTE C2 ON C1.controllerID = C2.controllerID
AND C1.rn = 1
AND C1.rn < C2.rn