I have table users with columns ID,USERSID
table :
t_A(ID,usersID,ADATE,priceA,priceB)
t_B(ID,usersID,BDATE,priceA,priceB)
t_C(ID,usersID,CDATE,priceA,priceB)
I'm using this query to get SUM of price from 3 tables for X DATE , and USERSID
declare #id int
set #id = 3 -- for example
SELECT SUM(priceA) as TA, SUM(priceB) as TB
FROM t_A,t_B,t_C
WHERE t_A.USERSID = #id
AND t_B.USERSID = #id
AND t_C.USERSID = #id
AND ADATE >= DATEADD(DAY, 0, DATEDIFF(DAY, 0, getdate()))
AND BDATE >= DATEADD(DAY, 0, DATEDIFF(DAY, 0, getdate()))
AND CDATE >= DATEADD(DAY, 0, DATEDIFF(DAY, 0, getdate()))
this script work only if the USERSID had a row in the three tables otherwise script return nothing
Because if only one table has no USERID = 3 that result always will be empty. Workaround: use option with UNION ALL operator
DECLARE #id int
SET #id = 3
SELECT SUM(x.priceA) as TA, SUM(x.priceB) AS TB
FROM (
SELECT priceA, priceB
FROM t_A
WHERE t_A.USERSID = #id
AND ADATE >= DATEDIFF(d,0,dateadd(d,0,getdate()))
UNION ALL
SELECT priceA, priceB
FROM t_B
WHERE t_B.USERSID = #id
AND BDATE >= DATEDIFF(d,0,dateadd(d,0,getdate()))
UNION ALL
SELECT priceA, priceB
FROM t_C
WHERE t_C.USERSID = #id
AND CDATE >= DATEDIFF(d,0,dateadd(d,0,getdate()))
) x
You can't compare a date column with a datediff. Here are some things you could do:
AND ADATE >= GETDATE()
or:
AND DATEDIFF(d, ADATE, GETDATE()) > 1
You can check the syntax and some examples at MSDN.
I think this is what you need. This way, it will return results as long as one of the tables satisfies the condition:
SELECT SUM(priceA) as TA, SUM(priceB) as TB, SUM(priceC) as TC
FROM t_A
FULL JOIN t_B
FULL JOIN t_C
WHERE t_A.USERSID = #id
AND t_B.USERSID = #id
AND t_C.USERSID = #id
AND ADATE >= DATEDIFF(d,0,dateadd(d,0,getdate()))
AND BDATE >= DATEDIFF(d,0,dateadd(d,0,getdate()))
AND CDATE >= DATEDIFF(d,0,dateadd(d,0,getdate()))
Related
I am working on an sql query where i will give a date from which Month and year will be determined. Then the record will be selected from Lab_Analysis and Lab_CSAnalysis table of any Furnace and Product ID. For this i Did following
Declare #Furnace varchar(50)='FUR-A'
Declare #Product bigint=1
Declare #sd date
Declare #ed date
Declare #Date date='02-02-2019'
SET #sd=(SELECT DATEADD(s,1,DATEADD(mm, DATEDIFF(m,0,#Date),0)))
SET #ed=(SELECT DATEADD(s,-1,DATEADD(mm, DATEDIFF(m,0,#Date)+1,0)))
;WITH dates AS (
SELECT #sd as theDate
UNION ALL
SELECT DATEADD(day, 1, theDate)
FROM dates
WHERE DATEADD(day, 1, theDate) <= #ed
)
SELECT DATEPART(dd, D.theDate) AS 'Day', ISNULL(LS.QtyMT, 0) AS QtyMt, ISNULL(SUM(L.Mn), 0) AS Mn, ISNULL(SUM(L.Si), 0) AS Si, ISNULL(SUM(L.P), 0) AS P,
ISNULL(LS.Carbon, 0) AS C, ISNULL(LS.Sulphur, 0) AS S, ISNULL(SUM(L.MnO), 0) AS MnO, ISNULL(SUM(L.CaO), 0) AS CaO, ISNULL(SUM(L.AI2O3), 0)
AS AI2O3, ISNULL(SUM(L.MgO), 0) AS MgO, ISNULL(SUM(L.Fe2O3), 0) AS Fe2O3, ISNULL(SUM(L.SiO2), 0) AS SiO2, ISNULL(SUM(L.Basicity), 0) AS Basicity
FROM Lab_Product AS LP INNER JOIN
Lab_Analysis AS L ON LP.ID = L.Product RIGHT OUTER JOIN
dates AS D ON L.Date = theDate LEFT OUTER JOIN
Lab_CSAnalysis AS LS ON LS.Date = L.Date AND (LP.ID = 1 AND L.Furnace = #Furnace AND LS.Furnace=#Furnace)
GROUP BY D.theDate, LS.QtyMT, LS.Carbon, LS.Sulphur
Here i want to view Record of Furnace='A' and ProductID=1. Then the above query is correct Output. There is no Record in Other than Furnace 'A' and Product ID=1. But when i want to View records of Furnace 'B' then it is still showing records of Furnace 'A'. How to solve this?
because of your filter on the left join. I made some changing over your query
Declare #Furnace varchar(50)='FUR-A'
Declare #Product bigint=1
Declare #sd date
Declare #ed date
Declare #Date date='02-02-2019'
SET #sd=(SELECT DATEADD(s,1,DATEADD(mm, DATEDIFF(m,0,#Date),0)))
SET #ed=(SELECT DATEADD(s,-1,DATEADD(mm, DATEDIFF(m,0,#Date)+1,0)))
;WITH dates AS (
SELECT #sd as theDate
UNION ALL
SELECT DATEADD(day, 1, theDate)
FROM dates
WHERE DATEADD(day, 1, theDate) <= #ed
)
SELECT DATEPART(dd, D.theDate) AS 'Day',
ISNULL(LS.QtyMT, 0) AS QtyMt,
ISNULL(SUM(L.Mn), 0) AS Mn,
ISNULL(SUM(L.Si), 0) AS Si,
ISNULL(SUM(L.P), 0) AS P,
ISNULL(LS.Carbon, 0) AS C,
ISNULL(LS.Sulphur, 0) AS S,
ISNULL(SUM(L.MnO), 0) AS MnO,
ISNULL(SUM(L.CaO), 0) AS CaO,
ISNULL(SUM(L.AI2O3), 0) AS AI2O3,
ISNULL(SUM(L.MgO), 0) AS MgO,
ISNULL(SUM(L.Fe2O3), 0) AS Fe2O3,
ISNULL(SUM(L.SiO2), 0) AS SiO2,
ISNULL(SUM(L.Basicity), 0) AS Basicity
FROM
dates
INNER JOIN Lab_Analysis AS L ON L.Date = dates.theDate AND L.Furnace = #Furnace
LEFT JOIN Lab_Product AS LP ON LP.ID = L.Product AND LP.ID = 1
LEFT JOIN Lab_CSAnalysis AS LS ON LS.Date = L.Date AND (L.Furnace = LS.Furnace)
GROUP BY
D.theDate, LS.QtyMT, LS.Carbon, LS.Sulphur
I am trying to union the same table together with itself while changing some value in the where clause. The problem i have is with the union between the loops. I can not use a table variable since the schema is too complicated to write by hand each time. Temp tables seem to be the way to go but I do not know how to get it to work and the correct syntax.
psuedo code of what I am trying to achieve:
DECLARE #var int, #tempTable
SET #var = someValue
WHILE expressionIncludingVar
#tempTable = SELECT *
FROM someTable
WHERE column = #var
UNION ALL #tempTable
SET #var = someChangeToVar
RETRUN #tempTable
The result of the query should be #tempTable hence the weird "RETURN #tempTable".
Thank you in advance.
EDIT:
Another hardcoded example:
I am trying to unhardcode something like this:
SELECT someAggregateColumns
FROM table
WHERE someDateColumn > #date and < someDateColumn < DATEADD(month, 2, #date)
GROUP BY someColumn
UNION ALL
SELECT someAggregateColumns
FROM table
WHERE someDateColumn > DATEADD(month, 1, #date) and and < someDateColumn < DATEADD(month, 1, DATEADD(month, 3, #date))
GROUP BY someColumn
SELECT someAggregateColumns
FROM table
WHERE someDateColumn = DATEADD(month, 2, #date) DATEADD(month, 1, DATEADD(month, 4, #date))
GROUP BY someColumn
UNION ALL
....etc
Maybe Recursive CTE works for you.
You can try this.
DECLARE #MyTable TABLE(ID INT, ColumnA VARCHAR(10), ColumnB VARCHAR(10))
INSERT INTO #MyTable VALUES
(1,'A', '10'),
(2,'B', '11'),
(3,'C', '12'),
(4,'D', '13'),
(5,'E', '14'),
(6,'F', '15'),
(7,'H', '16')
DECLARE #var INT = 4
;WITH CTE AS (
SELECT * FROM #MyTable WHERE ID = #var
UNION ALL
SELECT T.* FROM CTE INNER JOIN #MyTable T ON CTE.ID - 1 = T.ID
)
SELECT * INTO #tempTable FROM CTE
SELECT * FROM #tempTable
DROP TABLE #tempTable
If the only thing what is different in each cycle is a counter, then why aren't you just write one query including all data?
Instead of WHERE column = #var use WHERE column >= 0 AND column <= #maxVarValue.
If your conditions are more complex, you should consider to have a small (temp) table which contains the columns to be filtered, then just join that table to your source to get the desired result.
According to the comments, you can use a tally table (or numbers table).
Example:
DECLARE #Tally (Number INT);
INSERT INTO #Tally (Number) VALUES (0),(1),(2),(3),(4),(5);
SELECT
someAggregateColumns
FROM
table AGG
INNER JOIN #Tally T
ON AGG.someDateColumn = DATEADD(month, T.Number, #date)
WHERE
T.Number >= 0
AND T.Number <= 3
;
The above query will return the results for the current and the next 3 months.
You can persist a numbers table and re-use it. I usually have one called util.Number.
based in second example what you want is
SELECT someAggregateColumns
FROM table
WHERE someDateColumn IN (
#date,
DATEADD(month, 1, #date),
DATEADD(month, 2, #date),
DATEADD(month, 3, #date)
)
GROUP BY someColumn
Now if you want a range is even easier:
WHERE someDateColumn BETWEEN #date
AND DATEADD(month, 3, #date)
Using a TALLY table and a CROSS APPLY you can generate the Dates to check:
DECLARE #Var INT = 4
DECLARE #Date Date = '2017-01-01'
;WITH Tally
AS
(
SELECT ROW_NUMBER() OVER (ORDER BY Object_Id) -1 AS Num
FROM sys.columns
)
SELECT *
FROM MyTable
CROSS APPLY Tally
WHERE Num < #var AND
MyDate = DATEADD(month, num, #date)
I want to create a graph for my dataset for the last 24 hours.
I found a solution that works but this is pretty bad since the table I am outer joining cotains every single row in the DB since I am using the (now deprecated) "all" parameter in the group by.
Here is the solution that currently kind of works.
First I declare the date intervals that is 24 hours back in time from now. I declare it twice so I can use it later in the procedure aswell.
Declare #StartDate datetime = dateadd(hour, -24, getdate())
Declare #StartDateProc datetime = dateadd(hour, -24, getdate())
Declare #EndDate datetime = getdate()
I populate the dates into a temp table including a special formated datetsring.
create table #tempTable
(
Date datetime,
DateString varchar(11)
)
while #StartDate <= #EndDate
begin
insert into #tempTable (Date, DateString)
values (#StartDate, convert(varchar(8), #StartDate, 5) + '-' + convert(varchar(2), #StartDate, 108));
SET #StartDate = dateadd(hour,1, #StartDate);
end
This gives me data that looks like this:
Date DateString
---------------------------------------------
2015-12-09 13:59:01.970 09-12-15-13
2015-12-09 14:59:01.970 09-12-15-14
2015-12-09 15:59:01.970 09-12-15-15
2015-12-09 16:59:01.970 09-12-15-16
So what I want is to join my dataset on the matching date string and show the date even if the matching rows is zero.
Here is the rest of the query
select
Date = c.Date,
Amount = sum(c.Amount)
from
DbTable a
outer apply
(select
Date = b.DateString,
Amount = count(*)
from
#tempTable b
where
convert(varchar(8), a.DateColumn, 5) + '-' + convert(varchar(2), a.DateColumn, 108) = b.DateString
group by all
b.DateString) c
where
a.SomeParameter = 'test' and
a.DateColumn >= #StartDateProc and
a.DateColumn <= #EndDate
group by
c.Date
drop table #tempTable
Test to show actual data:
Declare #StartDate datetime = dateadd(hour, -24, getdate())
Declare #EndDate datetime = getdate()
select
dateString = convert(varchar(8),a.DateColumn,5) + '-' + convert(varchar(2),a.DateColumn, 108),
Amount = COUNT(*)
from
DbTable a
where
a.someParameter = 'test' and
a.DateColumn>= dateadd(hour, -24, getdate()) and
a.DateColumn<= getdate()
group by
convert(varchar(8),a.DateColumn,5) + '-' + convert(varchar(2),a.DateColumn, 108)
First output rows:
dateString Amount
09-12-15-14 1
09-12-15-15 1
09-12-15-16 1
09-12-15-17 3
09-12-15-18 1
09-12-15-22 3
09-12-15-23 2
As you can see here there is no data for the times from 19.00 to 21.00. This is how I want the data to be displayed:
dateString Amount
09-12-15-14 1
09-12-15-15 1
09-12-15-16 1
09-12-15-17 3
09-12-15-18 1
09-12-15-19 0
09-12-15-20 0
09-12-15-21 0
09-12-15-22 3
09-12-15-23 2
Normally, this would be approached with left join rather than outer apply. The logic is simple: keep all rows in the first table along with any matching information from the second. This means put the dates table first:
select tt.DateString, count(t.DateColumn) as Amount
from #tempTable tt left join
DbTable t
on convert(varchar(8), t.DateColumn, 5) + '-' + convert(varchar(2), t.DateColumn, 108) = tt.DateString and
t.SomeParameter = 'test'
where tt.Date >= #StartDateProc and
tt.Date <= #EndDate
group by tt.DateString;
In addition, your comparison for the dates seems overly complex, but if it works for you, it works.
The best bet here would be to use DATETIME type itself and not to lose the opportunity to use indexes:
Declare #d datetime = GETDATE()
;WITH cte1 AS(SELECT TOP 25 -1 + ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) h
FROM master..spt_values),
cte2 AS(SELECT DATEADD(hh, -h, #d) AS startdate,
DATEADD(hh, -h + 1, #d) AS enddate
FROM cte1)
SELECT c.startdate, c.enddate, count(*) as amount
FROM cte2 c
LEFT JOIN DbTable a ON a.DateColumn >= c.startdate AND
a.DateColumn < c.enddate AND
a.SomeParameter = 'test'
GROUP BY c.startdate, c.enddate
I have a stored procedure like this:
declare #myVal int
select
t2.short_descr , count(*) as count
FROM
t1
INNER JOIN
t2 ON t1.id = t2.id
where
[date] BETWEEN DATEADD(day, DATEDIFF(day, 0, GETDATE()), 0) and GETDATE())
group by
t2.short_descr
It works good, now I want the procedure to return a result even if it is empty; in that case, I want to return
select #myVal
I tried like this :
declare #myVal int
select
t2.short_descr , count(*) as count
FROM
t1
INNER JOIN
t2 ON t1.id = t2.id
where
[date] BETWEEN DATEADD(day, DATEDIFF(day, 0, GETDATE()), 0) and GETDATE())
group by
t2.short_descr
IF ##ROWCOUNT = 0
select #myVal
but when result is empty stored procedure selects 2 results: empty and #myVal. I want if is empty select just #myVal . How do it ?
You can add EXISTS check like this:
IF EXISTS ( SELECT 1 FROM t1 INNER JOIN t2 ON t1.id = t2.ID
where [date] BETWEEN DATEADD(day,DATEDIFF(day,0,GETDATE()),0) and GETDATE()) )
BEGIN
select t2.short_descr , count(*) as count
FROM t1
INNER JOIN t2 ON t1.id = t2.id
where [date] BETWEEN DATEADD(day,DATEDIFF(day,0,GETDATE()),0) and GETDATE())
group by t2.short_descr
END
ELSE
BEGIN
select #myVal
END
I am currently working on a function in which I use a recursive CTE, but it seems that have poor performance. I need this to be in function (so no temp tables) so I can easily use it within stored procedures.
Here is the code:
CREATE FUNCTION [dbo].[Web_GetDailyLoadListUDF]
(
#CustomerID INT
, #StartDate DATETIME
, #Days INT
, #IncludeChildren BIT
)
RETURNS #TableOfValues TABLE
(
RowID SMALLINT IDENTITY(1,1)
, DailyLoadCount INT
, DailyLoadDate VARCHAR(6)
, FullDate DATETIME
)
AS
BEGIN
DECLARE #MaxDate DATETIME;
SET #MaxDate = DATEADD(dd, #Days * -1.7, DATEDIFF(dd, 0, #StartDate));
WITH DateCTE AS
(
SELECT DATEADD(dd, 0, DATEDIFF(dd, 0, #StartDate)) AS DateValue
UNION ALL
SELECT DATEADD(DAY, -1, DateValue)
FROM DateCTE
WHERE DATEADD(DAY, -1, DateValue) > #MaxDate
)
INSERT INTO #TableOfValues
SELECT * FROM
(
SELECT TOP (#Days)
(
SELECT COUNT(*)
FROM dbo.[Load] l WITH (NOLOCK)
JOIN dbo.LoadCustomer lc WITH (NOLOCK)
ON lc.LoadID = l.ID
JOIN dbo.Customer c WITH (NOLOCK)
ON c.ID = lc.CustomerID
WHERE DATEADD(dd, 0, DATEDIFF(dd, 0, l.LoadDate)) = dct.DateValue
AND l.StateType = 1
AND lc.Main = 1
AND (c.ID = #CustomerID OR (#IncludeChildren = 1 AND c.ParentCustomerID = #CustomerID))
) AS DailyLoadCount
, CONVERT(VARCHAR(6), dct.DateValue, 107) AS DailyLoadDate
, dct.DateValue
FROM DateCTE dct
WHERE
DATEPART(DW, dct.DateValue) NOT IN (1, 7)
AND dct.DateValue NOT IN
(
SELECT HolidayDate FROM Holiday
)
ORDER BY dct.DateValue DESC
) AS S
ORDER BY s.DateValue ASC
RETURN
END
What this SQL is supposed to retrieve is the number of loads per day, for the past #Days that are business days (no weekends/holidays).
I basically just need some help optimizing this so that it doesn't run so slow. (Takes up to 20 seconds per customer, and this will be called over thousands).
Your main problem is just here
WHERE DATEADD(dd, 0, DATEDIFF(dd, 0, l.LoadDate)) = dct.DateValue
It should be
WHERE l.LoadDate >= dct.DateValue
AND l.LoadDate < dct.DateValue +1
Create composite indexs on Load(LoadDate, ID) and Load(ID, LoadDate) and drop the one that does not get used in the query plan.
You should show the query plan whenever you are asking questions about performance. To view the query plan, run the query inside the function on its own using variables for the input parameters. From the menu in SSMS, enable the option "Query -> Include Actual Execution Plan"
Since you don't have enough rep to post images, you can reveal the text plan as follows. Provide some sensible parameters in the first SELECT statement.
set showplan_text on;
Then, run the below in TEXT mode, i.e. press Ctrl-T then Ctrl-E.
DECLARE
#CustomerID INT
, #StartDate DATETIME
, #Days INT
, #IncludeChildren BIT
SELECT
#CustomerID = 1
, #StartDate = '20110201'
, #Days = 10
, #IncludeChildren = 1
DECLARE #TableOfValues TABLE
(
RowID SMALLINT IDENTITY(1,1)
, DailyLoadCount INT
, DailyLoadDate VARCHAR(6)
, FullDate DATETIME
)
DECLARE #MaxDate DATETIME;
SET #MaxDate = DATEADD(dd, #Days * -1.7, DATEDIFF(dd, 0, #StartDate));
WITH DateCTE AS
(
SELECT DATEADD(dd, 0, DATEDIFF(dd, 0, #StartDate)) AS DateValue
UNION ALL
SELECT DATEADD(DAY, -1, DateValue)
FROM DateCTE
WHERE DATEADD(DAY, -1, DateValue) > #MaxDate
)
INSERT INTO #TableOfValues
SELECT * FROM
(
SELECT TOP (#Days)
(
SELECT COUNT(*)
FROM dbo.[Load] l WITH (NOLOCK)
JOIN dbo.LoadCustomer lc WITH (NOLOCK)
ON lc.LoadID = l.ID
JOIN dbo.Customer c WITH (NOLOCK)
ON c.ID = lc.CustomerID
WHERE DATEADD(dd, 0, DATEDIFF(dd, 0, l.LoadDate)) = dct.DateValue
AND l.StateType = 1
AND lc.Main = 1
AND (c.ID = #CustomerID OR (#IncludeChildren = 1 AND c.ParentCustomerID = #CustomerID))
) AS DailyLoadCount
, CONVERT(VARCHAR(6), dct.DateValue, 107) AS DailyLoadDate
, dct.DateValue
FROM DateCTE dct
WHERE
DATEPART(DW, dct.DateValue) NOT IN (1, 7)
AND dct.DateValue NOT IN
(
SELECT HolidayDate FROM Holiday
)
ORDER BY dct.DateValue DESC
) AS S
ORDER BY s.DateValue ASC
SELECT * FROM #TableOfValues
Edit the plan into your question
You should use an inline UDF instead (right now you are actually using a temp table)
See http://msdn.microsoft.com/en-us/library/ms189294.aspx
Or convert it into a view instead.
Correlated subqueries run row-by-row, do not use them. Use a join or a join to a derived table instead. You also need to make sure any where clauses can take advantage of the indexing. Search on saragble queries to see what kinds of things cannot use indexes and what can be done to make it use an index.