Select x times for each row without union - sql

Is it possible to write union select queries like the following more succintly?
select
id,
1,
(1 + #defCapUp) * (p.Value + p.Premium),
getdate()
from Products p
union
select
id,
1,
(1 - #defCapDown) * (p.Value - p.Premium),
getdate()
from Products p
union
select
id,
case when p.Paydate > getdate() then 1 else 0 end,
(1 - #defCapUp) * (p.Value - p.Premium),
#nextYear
from Products p
union
select
id,
case when p.Paydate > getdate() then 1 else 0 end,
(1 + #defCapDown) * (p.Value + p.Premium),
#nextYear
from Products p
The statement selects four rows for each row in the Products table. The only thing varying is the formula used to calculate the values for column two and tree. I think there should be a way in sql to write the above without so much ugly code duplication. If only functions were first class objects and sql allowed lambda expressions...
Richard's solution down below is perfect, works very well for the example provided. But I had two typos in the orignal example which makes the problem somewhat tougher:
select
id,
1,
(1 + #defCapUp) * (p.Value + p.Premium),
getdate()
from Products p
union
select
id,
1,
(1 - #defCapDown) * (p.Value - p.Payout),
getdate()
from Products p
union
select
id,
case when p.Paydate > getdate() then 1 else 0 end,
(1 - #defCapUp) * (p.Value - p.Premium),
#nextYear
from Products p
union
select
id,
case when p.Paydate <= getdate() then 1 else 0 end,
(1 + #defCapDown) * (p.Value + p.Payout),
#nextYear
from Products p
The big problem is the case expression in which the comparison operator differs. My problem is that it is very hard to "neatly" handle those cases. What if there were a third case where the comparison was p.Paydate = getdate() for example?

(Not sure how lambda expressions would have helped you)
select
id,
case when p.Paydate > X.CompareDate then 1 else 0 end,
(1 + Cap) * (p.Value + ModF * p.Premium),
#nextYear
from Products p
cross join (
select #defCapUp Cap, Cast(0 as datetime) CompareDate, 1 Modf union all
select -#defCapDown, 0, -1 union all
select -#defCapUp, GETDATE(), -1 union all
select #defCapDown, GETDATE(), 1
) X
BTW, you should have been using UNION ALL, not UNION.

If the order doesn't matter, you could use WHERE.
SELECT id, field2, field3, field4
FROM Products p
WHERE (
field4 = getdate() AND field2=1 AND
(
field3=(1 + #defCapUp) * (p.Value + p.Premium) OR
field3=(1 - #defCapDown) * (p.Value - p.Premium)
)
)
OR
(
field4=#nextYear AND field2=(case when p.Paydate > getdate() then 1 else 0 end) AND
(
field3=(1 - #defCapUp) * (p.Value - p.Premium) OR
field3=(1 + #defCapDown) * (p.Value + p.Premium)
)
)

Related

Hive query takes forever on Superset

I have a query that was written in Presto SQL format (100 lines of insert a query result to a table that already exists) and takes within 10 minutes to get the result.
Now I am going to use Airflow and need to change the query to Hive SQL format to append previous month's data, there is no error, but it is taking 75+ minutes now and the query is still running and not returning any result.
Shall I 'stop' it or is there anything else to consider?
SET hive.limit.query.max.table.partition = 1000000;
INSERT INTO TABLE schema.temp_tbl partition(year_month_key)
Select
distinct
tbl.account_id,
tbl.theme_status,
streaming.streaming_hours,
tbl.year_month as year_month_key
From
(
Select
tbl_0.year_month,
tbl_0.account_id,
case when max(tbl_0.theme_status) = 1 then 'With Theme' else 'No Theme' end as theme_status
From
(Select
streaming.year_month,
streaming.account_id,
case when theme_events.account_id is not null then 1 else 0 end as theme_status
from
(
Select
substring(date_key, 1, 7) as year_month,
last_day(add_months(date_key, -1)) as year_month_ed,
date_key,
upper(account_id) as account_id,
play_seconds
from agg_device_streaming_metrics_daily
Where date_key between date_add(last_day(add_months(current_date, -2)),1) and last_day(add_months(current_date, -1))
and play_seconds > 0
) streaming
left join
(
Select
upper(theme.virtualuserid) as account_id,
min(theme.createddate) as min_createddate,
min(theme.date_key) as date_key
From
(
select * from theme_activate_event_history
where date_key between '2019-01-01' and '2020-01-01'
and activate = 'true' and themetype in ('ThemeBundle','ScreenSaver','Skin','Audio')
union
select * from theme_activate_event_history
where date_key between '2020-01-01' and '2021-01-01'
and activate = 'true' and themetype in ('ThemeBundle','ScreenSaver','Skin','Audio')
union
select * from theme_activate_event_history
where date_key between '2021-01-01' and '2022-01-01'
and activate = 'true' and themetype in ('ThemeBundle','ScreenSaver','Skin','Audio')
union
select * from theme_activate_event_history
where date_key between cast('2022-01-01' as date) and last_day(add_months(current_date, -1))
and activate = 'true' and themetype in ('ThemeBundle','ScreenSaver','Skin','Audio')
) theme
group by theme.virtualuserid
) theme_events
on streaming.account_id = theme_events.account_id
and date(theme_events.date_key) <= date(streaming.year_month_ed)
) tbl_0
group by tbl_0.year_month, tbl_0.account_id
) tbl
inner join
(Select
substring(date_key, 1, 7) as year_month,
upper(account_id) as account_id,
cast(sum(play_seconds) / 3600 as double) as streaming_hours
from agg_device_streaming_metrics_daily
Where date_key between date_add(last_day(add_months(current_date, -2)),1) and last_day(add_months(current_date, -1))
and play_seconds > 0
group by substring(date_key, 1, 7), upper(account_id)
) streaming
on tbl.account_id = streaming.account_id and tbl.year_month = streaming.year_month;

SQL query from Oracle SQL to T-SQL

I have a subquery which is used for an Oracle database, but I want to use an equivalent query for a SQL Server database.
I didn't figure out how to migrate the TO_TIMESTAMP(TO_CHAR(TO_DATE part and also didn't know how to handle the thing with rownums in T-SQL.
Is it even possible to migrate this query?
SELECT 0 run_id,
0 tran_id,
0 sort_id,
' ' tran_type,
10 prod_id,
72 type_id,
1 value,
TO_TIMESTAMP(TO_CHAR(TO_DATE('2016-03-18 00:00:00', 'YYYY.MM.DD HH24:MI:SS') + rownum -1, 'YYYY.MM.DD') || to_char(sw.end_time, 'HH24:MI:SS'), 'YYYY.MM.DD HH24:MI:SS') event_publication,
EXTRACT (YEAR
FROM (TO_DATE('2016-03-18 00:00:00', 'YYYY.MM.DD HH24:MI:SS') + rownum -1)) y,
EXTRACT (MONTH
FROM (TO_DATE('2016-03-18 00:00:00', 'YYYY.MM.DD HH24:MI:SS') + rownum -1)) mo,
EXTRACT (DAY
FROM (TO_DATE('2016-03-18 00:00:00', 'YYYY.MM.DD HH24:MI:SS') + rownum -1)) d,
to_number(to_char (sw.end_time, 'HH24')) h,
to_number(to_char (sw.end_time, 'MI')) mi,
to_number(to_char (sw.end_time, 'SS')) s,
0 ms
FROM all_objects ao,
settlement_win sw,
prod_def pd
WHERE pd.prod_id = 10
AND sw.country = pd.country
AND sw.commodity = pd.commodity
AND rownum <= TO_DATE('2016-03-18 23:59:00', 'YYYY.MM.DD HH24:MI:SS') -TO_DATE('2016-03-18 00:00:00', 'YYYY.MM.DD HH24:MI:SS')+1
The first thing to address is the use of rownum which has no direct equivalent in TSQL but we can mimic it, and for this particular query you need to recognize that the table ALL_OBJECTS is only being used to produce a number of rows. It has no other purpose to the query.
In TSQL we can generate rows using a CTE and there are many many variants of this, but for here I suggest:
;WITH
cteDigits AS (
SELECT 0 AS digit UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL
SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9
)
, cteTally AS (
SELECT
d1s.digit
+ d10s.digit * 10
+ d100s.digit * 100 /* add more like this as needed */
-- + d1000s.digit * 1000 /* add more like this as needed */
+ 1 AS rownum
FROM cteDigits d1s
CROSS JOIN cteDigits d10s
CROSS JOIN cteDigits d100s /* add more like this as needed */
--CROSS JOIN cteDigits d1000s /* add more like this as needed */
)
This will quickly spin-up 1000 rows as is and can be extended to produce many more rows by adding more cross joins. Note this returns a column called rownum which starts at 1 thus mimicking the Oracle rownum.
So next you can just add some of the remaining query, like this:
SELECT
0 run_id
, 0 tran_id
, 0 sort_id
, ' ' tran_type
, 10 prod_id
, 72 type_id
, 1 value
, convert(varchar, dateadd(day, rownum - 1,'20160318'),121) event_publication
-- several missing rows here
, 0 ms
FOM cteTally
INNER JOIN settlement_win sw
INNER JOIN prod_def pd ON sw.country = pd.country AND sw.commodity = pd.commodity
WHERE pd.prod_id = 10
AND rownum <= datediff(day,'20160318','20160318') + 1
Note that you really do not need a to_timestamp() equivalent you just need the ability to output date and time to the maximum precision of your data which appears to be to the level of seconds.
To progress further (I think) requires an understanding of the data held in the column sw.end_time. If this can be converted to the mssql datetime data type then it is just a matter of adding a number of days to that value to arrive at the event_publication and similarly if sw.end_time is converted to a datetime data type then use date_part() to get the hours, minutes and seconds from that column. e.g.
, DATEADD(day,rownum-1,CONVERT(datetime, sw.end_time)) AS event_publication
also, if such a calculation works then it would be possible to use an apply operator to simplify the overall query, something like this
;WITH
cteDigits AS (
SELECT 0 AS digit UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL
SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9
)
, cteTally AS (
SELECT
d1s.digit
+ d10s.digit * 10
+ d100s.digit * 100 /* add more like this as needed */
-- + d1000s.digit * 1000 /* add more like this as needed */
+ 1 AS rownum
FROM cteDigits d1s
CROSS JOIN cteDigits d10s
CROSS JOIN cteDigits d100s /* add more like this as needed */
--CROSS JOIN cteDigits d1000s /* add more like this as needed */
)
SELECT
0 run_id
, 0 tran_id
, 0 sort_id
, ' ' tran_type
, 10 prod_id
, 72 type_id
, 1 value
, convert(varchar(23), CA.Event_publication, 121) Event_publication
, datepart(day,CA.Event_publication) dd
, datepart(month,CA.Event_publication) mm
, datepart(year,CA.Event_publication) yyyy
, datepart(hour,CA.Event_publication) hh24
, datepart(minute,CA.Event_publication) mi
, datepart(second,CA.Event_publication) ss
, 0 ms
FOM cteTally
INNER JOIN settlement_win sw
INNER JOIN prod_def pd ON sw.country = pd.country AND sw.commodity = pd.commodity
CROSS APPLY (
SELECT DATEADD(day,rownum-1,CONVERT(datetime, sw.end_time)) AS event_publication ) CA
WHERE pd.prod_id = 10
AND rownum <= datediff(day,'20160318','20160318') + 1
NB: IT may be necessary to include this datediff(day,'19000101,'20160318') (which equals 42445) into the calculation of the event_date e.g.
SELECT DATEADD(day,42445 + (rownum-1),CONVERT(datetime, sw.end_time)) AS event_publication
One last point is that you could use datetime2 instead of datetime if you really do need a greater degree of time precision but there is no easily apparent requirement for that.

COALESCE (DATEPART) Not validating

I'm in the process of validating the following query where my expected result is a row with a revenue value of 0 for any week where there is no revenue to SUM. What I'm getting is only one 0 revenue record where I know there are many. Can someone take a look at my code and see if there is anything obvious I screwed up?
SELECT dbo.LMCustomer.Name,
SUM(dbo.LMDelivery.LdryCensChrg + dbo.LMDelivery.LdryWghtChrg + dbo.LMDelivery.LdryPiecChrg - dbo.LMDelivery.RetnWghtCred - dbo.LMDelivery.RetnPiecCred - dbo.LMDelivery.VrncChrg + dbo.LMDelivery.LdryDelvChrg +
dbo.LMDelivery.PrchChrg + dbo.LMDelivery.LdryPcntChrg + dbo.LMDelivery.AuxpChrg01 + dbo.LMDelivery.AuxpChrg02 + dbo.LMDelivery.AuxpChrg03 + dbo.LMDelivery.AuxpChrg04 + dbo.LMDelivery.AuxpChrg05 + dbo.LMDelivery.AuxpChrg06
+ dbo.LMDelivery.AuxpChrg07 + dbo.LMDelivery.AuxpChrg08 + dbo.LMDelivery.AuxpChrg09 + dbo.LMDelivery.AuxpChrg10 + dbo.LMDelivery.AuxpChrg11 + dbo.LMDelivery.AuxpChrg12 - dbo.LMDelivery.AuxpCred01 - dbo.LMDelivery.AuxpCred02
- dbo.LMDelivery.AuxpCred03 - dbo.LMDelivery.AuxpCred04 - dbo.LMDelivery.AuxpCred05 - dbo.LMDelivery.AuxpCred06 - dbo.LMDelivery.AuxpCred07 - dbo.LMDelivery.AuxpCred08 - dbo.LMDelivery.AuxpCred09 - dbo.LMDelivery.AuxpCred10
- dbo.LMDelivery.AuxpCred11 - dbo.LMDelivery.AuxpCred12 + dbo.LMDelivery.AuxmChrg01 + dbo.LMDelivery.AuxmChrg02 + dbo.LMDelivery.AuxmChrg03 + dbo.LMDelivery.AuxmChrg04 + dbo.LMDelivery.AuxmChrg05 + dbo.LMDelivery.AuxmChrg06
+ dbo.LMDelivery.AuxmChrg07 + dbo.LMDelivery.AuxmChrg08 - dbo.LMDelivery.AuxmCred01 - dbo.LMDelivery.AuxmCred02 - dbo.LMDelivery.AuxmCred03 - dbo.LMDelivery.AuxmCred04 - dbo.LMDelivery.AuxmCred05 - dbo.LMDelivery.AuxmCred06
- dbo.LMDelivery.AuxmCred07 - dbo.LMDelivery.AuxmCred08) AS Revenue
FROM dbo.LMDelivery INNER JOIN
dbo.LMCustomer ON dbo.LMDelivery.ShipCustRcID = dbo.LMCustomer.RcID INNER JOIN
dbo.LMContract ON dbo.LMDelivery.ContRcID = dbo.LMContract.RcID
WHERE (dbo.LMDelivery.UsefCanc = 0) AND (dbo.LMContract.StrtDate >= '2018-01-01') AND (dbo.LMDelivery.LdryDelvDate >= '2018-01-01')
GROUP BY dbo.LMCustomer.RcID, dbo.LMCustomer.Name, COALESCE (DATEPART(week, dbo.LMDelivery.LdryDelvDate), 0)
Your query is limited to only show weeks where there is a delivery (and thus, presumably, revenue) by the use of the LMDelivery table in the FROM clause.
If you wished to see a list of all customers, all weeks, and whatever delivery information is necessary, then you're going to need to start with a list of all customers and all weeks.
Assuming SQL Server, you could use a CTE to come up with a list of the weeks that have been in the year so far, connect that with your customer list, then seek out any relevant deliveries and their associated contracts. Something like the following:
;WITH
e1(n) AS
(
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1
), -- 10
e2(n) AS (SELECT ROW_NUMBER() OVER (ORDER BY e1.n) AS n FROM e1 CROSS JOIN e1 AS b), -- 10*10
num(WeekOfYear) AS
(
SELECT
n - 1
FROM e2
WHERE n <= DATEPART(WEEK, GETDATE()) + 1
)
SELECT dbo.LMCustomer.Name,
SUM(dbo.LMDelivery.LdryCensChrg + dbo.LMDelivery.LdryWghtChrg + dbo.LMDelivery.LdryPiecChrg - dbo.LMDelivery.RetnWghtCred - dbo.LMDelivery.RetnPiecCred - dbo.LMDelivery.VrncChrg + dbo.LMDelivery.LdryDelvChrg +
dbo.LMDelivery.PrchChrg + dbo.LMDelivery.LdryPcntChrg + dbo.LMDelivery.AuxpChrg01 + dbo.LMDelivery.AuxpChrg02 + dbo.LMDelivery.AuxpChrg03 + dbo.LMDelivery.AuxpChrg04 + dbo.LMDelivery.AuxpChrg05 + dbo.LMDelivery.AuxpChrg06
+ dbo.LMDelivery.AuxpChrg07 + dbo.LMDelivery.AuxpChrg08 + dbo.LMDelivery.AuxpChrg09 + dbo.LMDelivery.AuxpChrg10 + dbo.LMDelivery.AuxpChrg11 + dbo.LMDelivery.AuxpChrg12 - dbo.LMDelivery.AuxpCred01 - dbo.LMDelivery.AuxpCred02
- dbo.LMDelivery.AuxpCred03 - dbo.LMDelivery.AuxpCred04 - dbo.LMDelivery.AuxpCred05 - dbo.LMDelivery.AuxpCred06 - dbo.LMDelivery.AuxpCred07 - dbo.LMDelivery.AuxpCred08 - dbo.LMDelivery.AuxpCred09 - dbo.LMDelivery.AuxpCred10
- dbo.LMDelivery.AuxpCred11 - dbo.LMDelivery.AuxpCred12 + dbo.LMDelivery.AuxmChrg01 + dbo.LMDelivery.AuxmChrg02 + dbo.LMDelivery.AuxmChrg03 + dbo.LMDelivery.AuxmChrg04 + dbo.LMDelivery.AuxmChrg05 + dbo.LMDelivery.AuxmChrg06
+ dbo.LMDelivery.AuxmChrg07 + dbo.LMDelivery.AuxmChrg08 - dbo.LMDelivery.AuxmCred01 - dbo.LMDelivery.AuxmCred02 - dbo.LMDelivery.AuxmCred03 - dbo.LMDelivery.AuxmCred04 - dbo.LMDelivery.AuxmCred05 - dbo.LMDelivery.AuxmCred06
- dbo.LMDelivery.AuxmCred07 - dbo.LMDelivery.AuxmCred08) AS Revenue
FROM
dbo.LMCustomer INNER JOIN
num ON 1=1 LEFT JOIN
dbo.LMDelivery ON dbo.LMDelivery.ShipCustRcID = dbo.LMCustomer.RcID LEFT JOIN
dbo.LMContract ON dbo.LMDelivery.ContRcID = dbo.LMContract.RcID
WHERE LMDelivery.ShipCustRcID IS NULL OR (
(dbo.LMDelivery.UsefCanc = 0) AND (dbo.LMContract.StrtDate >= '2018-01-01') AND (dbo.LMDelivery.LdryDelvDate >= '2018-01-01')
AND COALESCE (DATEPART(week, LMDelivery.LdryDelvDate), 0) = num.WeekOfYear
)
GROUP BY dbo.LMCustomer.RcID, dbo.LMCustomer.NAME, num.WeekOfYear

what is the divisor is equal to zero in plsql?

SELECT pstartdate,
opbal,
joined,
resign,
( opbal + joined - resign ) clbal
,
( Round(( ( resign * 100 ) / ( opbal + joined ) ) / 100, 2) * 100
) attriation
FROM (SELECT pstartdate,
penddate,
Getopempbal(pstartdate) OpBal,
Getempjn(pstartdate, penddate) Joined,
Getempres(pstartdate, penddate) Resign
FROM (SELECT Add_months(:startdate, LEVEL - 1) pstartdate,
Add_months(:startdate, LEVEL) - 1 penddate
FROM dual
CONNECT BY LEVEL <= Months_between( :enddate, :startdate ) + 1))
ORDER BY To_number(1)
When i executed this query Error came
divisor is equal to zero.
i think error this place
(round(((resign*100)/(opbal+joined))/100,2)*100) attriation
I recommend that you use the nullif() function when doing division:
select . . .
( Round(( ( resign * 100 ) / nullif( opbal + joined, 0 ) ) / 100, 2) * 100
) attriation
This returns NULL if the denominator is zero.
SELECT pstartdate,
opbal,
joined,
resign,
(opbal+joined-resign) clbal,
round((**NULLIF**(resign,0) *100)/(opbal + joined)) ab
FROM
(SELECT pstartdate,
penddate,
getopempbal(pstartdate) opbal,
getempjn(pstartdate,penddate) joined,
getempres(pstartdate,penddate) resign
FROM
(SELECT add_months(:startdate, LEVEL-1) pstartdate,
add_months(:startdate, LEVEL)-1 penddate
FROM dual CONNECT BY LEVEL <= months_between(:enddate, :startdate) + 1))
ORDER BY to_number(1)

group by on union all result SQL Server

I am new to SQL Server. I have a SQL query where I performed an union all, the 2 individual queries have group by.
select top 5
Starttime, convert(date,row_date) as Date,
sum(acdcalls + abncalls) [Offered],
sum(acdcalls) [Handled],
sum(abncalls) [Abandoned],
sum(acdcalls1 + acdcalls2 + acdcalls3 + acdcalls4 + acdcalls5) [Answered within SLA],
case
when sum(acdcalls) != 0
then cast((sum(acdcalls1 + acdcalls2 + acdcalls3 + acdcalls4 + acdcalls5)) * 1.0 / sum((acdcalls)) * 1.0 * 100 as decimal(10, 2))
else 0
end as [SLA in %]
from
db1
where
row_date = getdate()
group by
Starttime, row_Date
union all
select top 5
Starttime, convert(date,row_date) as Date,
sum(acdcalls + abncalls) [Offered],
sum(acdcalls) [Handled],
sum(abncalls) [Abandoned],
sum(acdcalls1 + acdcalls2 + acdcalls3 + acdcalls4 + acdcalls5) [Answered within SLA],
case
when sum(acdcalls) != 0
then cast((sum(acdcalls1 + acdcalls2 + acdcalls3 + acdcalls4 + acdcalls5)) * 1.0 / sum((acdcalls)) * 1.0 * 100 as decimal(10, 2))
else 0
end as [SLA in %]
from
db2
where
row_date = getdate()
group by
Starttime, row_Date
Starttime column has common values. I want to do group by Starttime for the result. How can I do that? Any help would be much appreciated
You need to do the union first, then aggregate. The following example uses a subquery, but you can use a temp table instead if you prefer:
Select StartTime, Row_Date, sum(acdcalls+abncalls)...[other sums here]
From (
select * from db1
union all
select * from db2
) a
group by StartTime, RowDate
You can still have your where clauses and your specific columns in the subquery if necessary (the example above will only work if db1 and db2 have the same columns in the same order - otherwise you will need to specify your columns). I am not sure why you want to group by Row_Date if you are limiting both of your selects to Row_Date = GetDate(), though.