Calling a function repeatedly and joining its output - sql-server

I have 1 function given to me that takes parameters of #month and #year.
The function returns a table like this: Assuming #month=9 and #year=2020
select * FROM dbo.TotalClicksForMonth(9,2020)
Partner,Percentage
Partner1,0.25
Partner2,0.5
Partner3,0.25
I have a scalar function that returns a float
select dbo.TotalSpendForMonth(9,2020)
100
If I run a select statement
select dbo.TotalSpendForMonth(9,2020)*cp.PercentageClicks as percentsplit from dbo.TotalClicksForMonth(9,2020) as cp
This works, and I get an output like
Partner PercentageClicks percentsplit
Partner1 0.25 25
Partner2 0.50 50
Partner3 0.25 25
Taking the total amount and allocating it across all the rows in the right ratios.
Now, thats ok for Sept 2020 (9,2020). But I need the output in for all the months and years specified in a 3rd table
select datepart(MONTH,[date]) as mh,datepart(year,[date]) as yr
from sales
I cant work out how to do this.
I tried
select datepart(MONTH,sales.date) as mh,datepart(year,sales.date) as yr
join (select dbo.TotalspendForMonth(sales.mh,sales.yr)*cp.PercentageClicks as percentsplit from dbo.TotalClicksForMonth(sales.mh,sales.yr) as cp) as xx on 1=1
from sales
group by datepart(MONTH,sales.date),datepart(year,sales.date)
But that doesnt work.
I think my options are to create a temporary table and then repeatedly in a cursor call the select statement to add the rows, but there must be a better way,
Can you help please?

I appears what you need to do is APPLY to the function:
SELECT DATEPART(MONTH,s.[date]) AS mh,
DATEPART(year,s.[date]) AS yr,
dbo.TotalSpendForMonth(DATEPART(MONTH,s.[date]),DATEPART(year,s.[date])) * cp.PercentageClicks AS percentsplit
FROM dbo.sales s
CROSS APPLY dbo.TotalClicksForMonth(DATEPART(MONTH,s.[date]),DATEPART(year,s.[date])) cp;
If you prefer, you can derive the DATEPARTs in a VALUES table construct, so as not the repeat the expressions:
SELECT V.mh,
V.yr,
dbo.TotalSpendForMonth(V.mh,V.yr) * cp.PercentageClicks AS percentsplit
FROM dbo.sales s
CROSS APPLY (VALUES(DATEPART(MONTH,s.[date]),DATEPART(year,s.[date])))V(mh,yr)
CROSS APPLY dbo.TotalClicksForMonth(V.mh,V.yr) cp;

To calculate the click percentages you could CROSS APPLY the table valued function. To summarize across MONTH and YEAR you could use the SUM aggregate function and GROUP BY. Something like this.
select dt.mo, dt.yr,
sum(dbo.TotalSpendForMonth(dt.mo, dt.yr)*cp.PercentageClicks) total_amount
from dbo.sales s
cross apply (select datepart(month,s.[date]) as mo,
datepart(year,s.[date]) as yr) dt
cross apply dbo.TotalClicksForMonth(dt.mo, dt.yr) cp
group by dt.mo, dt.yr
order by dt.yr, dt.mo;

Related

Turn quarterly data into monthly by repeating the quarterly rows by 3

I'm wondering how to repeat each of these rows 3 times to get them from Quarters into months.
I need to repeat the same values in the first 2 columns but depending on the quarter in the third column I would need the other months in that quarter, i.e for the first row '31/01/2021' and '28/02/2021'
So desired output would look like:
Another option is via a CROSS APPLY
Select A.Code
,A.Value
,B.Date
From YourTable A
Cross Apply ( values (EOMonth(dateadd(MONTH,-2,A.Date)))
,(EOMonth(dateadd(MONTH,-1,A.Date)))
,(EOMonth(dateadd(MONTH,-0,A.Date)))
) B(Date)
Results
WITH TABLE_DATA(CODE,VAL,DATED)AS
(
SELECT 'R01',777,'2021-03-31' UNION ALL
SELECT 'R01',833,'2021-06-30' UNION ALL
SELECT 'R01',882,'2021-09-30'
)
SELECT D.CODE,D.VAL,CAST(DATEADD(MONTH,-X.PLACEHOLDER,D.DATED)AS DATE)AS DATED,X.PLACEHOLDER
FROM TABLE_DATA AS D
CROSS JOIN
(
SELECT 0 AS PLACEHOLDER
UNION ALL
SELECT 1
UNION ALL
SELECT 2
)X
ORDER BY D.CODE,DATED;
Could you please check if this query is suitable for you. TABLE_DATA is an example of data you have provided

SQL Server : Join If Between

I have 2 tables:
Query1: contains 3 columns, Due_Date, Received_Date, Diff
where Diff is the difference in the two dates in days
QueryHol with 2 columns, Date, Count
This has a list of dates and the count is set to 1 for everything. All these dates represent public holidays.
I want to be able to get the sum of QueryHol["Count"] if QueryHol["Date"] is between Query1["Due_Date"] and Query1["Received_Date"]
Result Wanted: a column joined onto Query1 to state how many public holidays fell into the date range so they can be subtracted from the Query1["Diff"] column to give a reflection of working days.
Because the 01-01-19 is a bank holiday i would want to minus that from the Diff to end up with results like below
Let me know if you require any more info.
Here's an option:
SELECT query1.due_date
, query1.received_date
, query1.diff
, queryhol.count
, COALESCE(query1.diff - queryhol.count, query1.diff) as DiffCount
FROM Query1
OUTER APPLY(
SELECT COUNT(*) AS count
FROM QueryHol
WHERE QueryHol.Date <= Query1.Received_Date
AND QueryHol.Date >= Query1.Due_Date
) AS queryhol
You may need to play around with the join condition - as it is assumes that the Received_Date is always later than the Due_Date which there is not enough data to know all of the use cases.
If I understand your problem, I think this is a possible solution:
select due_date,
receive_date,
diff,
(select sum(table2.count)
from table2
where table2.due_date between table1.due_date and table1.due_date) sum_holi,
table1.diff - (select sum(table2.count)
from table2
where table2.date between table1.due_date and table2.due_date) diff_holi
from table1
where [...] --here your conditions over table1.

T-SQL Multi-layered CTE query with Aggregates

I have a long Common Table Expression (CTE) query which is trying to calculate percent difference between each users' average score and group average score.
I would like for my multi-layered CTE query to filter and reduce bulk of records down to the following table:
UserID Tag UserAvg GroupAvg PercentDifference
1 Cat 72.50 73 -0.68
2 Cat 75.50 73 3.36
3 Cat 75 73 2.70
4 Cat 73.25 73 0.34
5 Cat 52.3333 73 -32.97
6 Cat 86.25 73 16.64
My problem is getting GroupAvg column so that I can perform % Difference calculation.
To illustrate the current approach I am using; here is the summary of my CTE query:
WITH
-- select 1st 3 columns
UserScores AS (select UserID, Tag, Score FROM {multiple-table} WHERE Tag = 'Cat'),
-- add UserAvg column by grouping records
ScoreAverages AS (select UserID, Tag, AVG(Score) AS UserAvg GROUP BY UserID, Tag FROM UserScores),
-- calculate GroupAvg
GroupAverage AS (select AVG(UserAvg) AS GroupAvg FROM ScoreAverages),
-- calculate % difference
PercentDiff AS (select UserID, Tag, UserAvg, 73 AS GroupAvg, (((UserAvg-73)/((UserAvg+73)/2))*100) AS PercentDifference FROM ScoreAverages )
-- do something with results
select * from PercentDiff
Simple enough; right?
Notice that I have hard coded 73 as my GroupAvg value. I am unsure how to construct required sql query that would allow me to go from ScoreAverages to PercentDiff table.
Is it possible to perform SELECT within a SELECT statement? And I am not looking for something of the following:
select * from X where Id in (select Id from Y where Name like '%abc%')
Or I am simply trying to do too much in one go?
Yes, it's called a sub-select:
SELECT Column1, Column2, (SELECT QUERY THAT GETS GROUP AVERAGE) AS GroupAverage, Column3
FROM ...
To use the result of the sub-select in another column's calculation, you can either repeat the sub-select:
SELECT Column1, Column2, (SELECT QUERY THAT GETS GROUP AVERAGE) AS GroupAverage, (Column3 - (SELECT QUERY THAT GETS GROUP AVERAGE)) AS Column4
FROM ...
Or you can reference it the same as you would any other column in the outer query or a subsequent CTE:
WITH CTE1 AS (SELECT Column1, Column2, (SELECT QUERY THAT GETS GROUP AVERAGE) AS GroupAverage
FROM ...)
, CTE2 AS (SELECT *, Column3-GroupAverage) AS Column4
FROM CTE1
JOIN ...
It is possible, as shown in Tab Alleman's answer, but in your case it's not necessary. Since you already calculate the GroupAvg in the cte chain, you can use it in the final query. and since the GroupAverage only contains one row, you can simply add a CROSS JOIN to it:
;WITH
-- select 1st 3 columns
UserScores AS (
select UserID, Tag, Score
FROM {multiple-table}
WHERE Tag = 'Cat'),
-- add UserAvg column by grouping records
ScoreAverages AS (
select UserID, Tag, AVG(Score) AS UserAvg
FROM UserScores
GROUP BY UserID, Tag),
-- calculate GroupAvg
GroupAverage AS (
select AVG(UserAvg) AS GroupAvg
FROM ScoreAverages),
-- calculate % difference
PercentDiff AS (
select UserID, Tag, UserAvg, GroupAvg,
(((UserAvg-GroupAvg)/((UserAvg+GroupAvg)/2))*100) AS PercentDifference
FROM ScoreAverages
CROSS JOIN GroupAverage)
-- do something with results
select * from PercentDiff
I just thought you could do this with a single cte like so.
;WITH UserAverages AS
(
SELECT UserID,
Tag,
AVG(Score) AS UserAvg,
AVG(AVG(Score)) OVER () AS GroupAvg
FROM {multiple-table}
WHERE Tag = 'Cat'
GROUP BY UserID, Tag
)
SELECT UserID,
Tag,
UserAvg,
GroupAvg,
(((UserAvg-GroupAvg)/((UserAvg+GroupAvg)/2))*100) AS PercentDifference
FROM UserAverages

concatenate columns from 2 tables in resultset

Here is simplified version of my schema. Using Sql Server 2012 enterprise edition.
CREATE table #abc (a INT , b INT);
CREATE TABLE #def ( a INT , c INT ,d INT);
INSERT INTO #abc values(1,23),(1,24);
INSERT INTO #def VALUES(1,53,54),(1,56,57)
Table #abc JOINs TO #def ON COLUMN a
Basically it is concatenation of rows from both tables based on column a. Tried inner join\cross apply but they all results in cross join kind of resultset understandably . I have workaround using another temp table(then update) but kind of feel that this can be done easily in single select . I am missing something simple here.
Need output like this:
a b c d
1 23 53 54
1 24 56 57
Thanks
-N
You need some sort of sequence number to join the tables together. You can generate one using row_number() as follows:
select a.a, a.b, d.c, d.d
from (select a.*, row_number() over (order by (select NULL)) as seqnum
from #abc a
) a join
(select d.*, row_number() over (order by (select NULL)) as seqnum
from #def d
) d
on a.seqnum = d.seqnum;
Now the caution, caution, caution. The order by clause does not really specify the ordering, so the sequence numbers may not be what you expect. You should really have a column to specify the ordering.
You need to have a unique key value in each row to be able to join the tables in the way you would like. Then, an inner join will return the result set you require.
If you introduce referential integrity between the tables, then this will be enforced and return the expected results.

How to retrieve the total row count of a query with TOP

I have a SQL Server 2008 query
SELECT TOP 10 *
FROM T
WHERE ...
ORDER BY ...
I'd like to get also the total number of the rows. The obious way is to make a second query
SELECT COUNT(*)
FROM T
WHERE ...
ORDER BY ...
Is there an efficient method?
Thanks
Do you want a second query?
SELECT TOP 10
*, foo.bar
FROM
T
CROSS JOIN
(SELECT COUNT(*) AS bar FROM T WHERE ...) foo
WHERE
...
ORDER BY
...
OR
DECLARE #bar int
SELECT #bar = COUNT(*) AS bar FROM T WHERE ...
SELECT TOP 10
*, #bar
FROM
T
CROSS JOIN
(SELECT COUNT(*) AS bar FROM T WHERE ...) foo
WHERE
...
ORDER BY
...
Or (Edit: using WITH)
WITH cTotal AS
(
SELECT COUNT(*) AS bar FROM T WHERE ...)
)
SELECT TOP 10
*, cTotal .bar
FROM
T
WHERE
...
ORDER BY
...
What is in this answer seems to work:
https://stackoverflow.com/a/19125458/16241
Basically you do a:
SELECT top 100 YourColumns, TotalCount = Count(*) Over()
From YourTable
Where SomeValue = 32
TotalCount will have the total number of rows. It is listed on each row though.
When I tested this the query plan showed the table only being hit once.
Remove the ORDER BY clause from the 2nd query as well.
No.
SQL Server doesn't keep COUNT(*) in metadata like MyISAM, it calculates it every time.
UPDATE: If you need an estimate, you can use statistics metadata:
SELECT rows
FROM dbo.sysindexes
WHERE name = #primary_key,
where #primary_key is your table's primary key name.
This will return the COUNT(*) from last statistics update.
SELECT TOP (2) *,
(SELECT COUNT(*) AS Expr1 FROM T) AS C
FROM T

Resources