show the column names to go along with the sum - sql-server

am just doing some practice in SQL Server. My below query sums a quantity column but I also want to return the corresponding item number column and item description column with it. each time I try it tells me the item_no column is ambiguous. I have done some reading as to what this means but am still not clear on it. What am I missing?
declare #startdate int = '20161201'
declare #enddate int = '20170401'
SELECT sum(qty) as total_units, item_no as item_number from fact_sales
inner join dim_item
on fact_sales.item_no=dim_item.item_no
where vendor_id = 'roche' and date_key between #startdate and #enddate
order by fact_sales.item_no

You are missing couple of things
DECLARE #startdate INT = '20161201'
DECLARE #enddate INT = '20170401'
SELECT sum(fact_sales.qty) AS total_units
,dim_item.item_no AS item_number
,dim_item.item_discription
FROM fact_sales
INNER JOIN dim_item ON fact_sales.item_no = dim_item.item_no
WHERE vendor_id = 'roche'
AND date_key BETWEEN #startdate
AND #enddate
GROUP BY dim_item.item_no,dim_item.item_discription
ORDER BY fact_sales.item_no
Group By
Prefixing column name with Table name/ Table alias

Related

How to optimize below my SQL query shown here

This query is written for those users who did not log-in to the system between 1st July to 31 July.
However when we run the query in query analyzer then it's taking more than 2 minutes. But in application side giving error as 'Execution Timeout Expired. The timeout period elapsed prior to completion of the operation or the server is not responding'.
Below query takes start date as 1st July 2022 and get all the users and add those users into temp table called '#TABLE_TEMP' and increases to next date.
Again while loop runs and fetch users for 2nd July and so on until it reaches to 31st July.
Can anyone help on this to optimize the query using CTE or any other mechanism?
H
ow can we avoid While loop for better performance?
DECLARE #TABLE_TEMP TABLE
(
Row int IDENTITY(1,1),
[UserId] int,
[UserName] nvarchar(100),
[StartDate] nvarchar(20),
[FirstLogin] nvarchar(20),
[LastLogout] nvarchar(20)
)
DECLARE #START_DATE datetime = '2022-07-01';
DECLARE #END_DATE datetime = '2022-07-31';
DECLARE #USER_ID nvarchar(max) = '1,2,3,4,5,6,7,8,9';
DECLARE #QUERY nvarchar(max) = '';
WHILE(#START_DATE < #END_DATE OR #START_DATE = #END_DATE)
BEGIN
SET #QUERY = 'SELECT
s.userid AS [UserId],
s.username AS [UserName],
''' + CAST(#START_DATE as nvarchar) + ''' AS [StartDate],
MAX(h.START_TIME) as [FirstLogin],
MAX(ISNULL(h.END_TIME, s.LAST_SEEN_TIME)) as [LastLogout]
FROM USER s
LEFT JOIN USER_LOGIN_HISTORY h ON h.userid = s.userid
LEFT JOIN TEMP_USER_INACTIVATION TUI ON TUI.userid = s.userid AND ('''+ CAST(#START_DATE as nvarchar) +''' BETWEEN ACTIVATED_DATE AND DEACTIVATD_DATE)
WHERE s.userid IN (' + #USER_ID + ')
AND h.userid NOT IN (SELECT userid FROM USER_LOGIN_HISTORY WHERE CAST(START_TIME AS DATE) = '''+ CONVERT(nvarchar,(CAST(#START_DATE AS DATE))) +''') AND ACTIVATED_DATE IS NOT NULL
GROUP BY s.userid, h.userid, s.username, s.last_seen_time
HAVING CAST(MAX(ISNULL(h.END_TIME, s.LAST_SEEN_TIME)) AS DATE) <> '''+ CONVERT(nvarchar,(CAST(#START_DATE AS DATE))) + '''
ORDER BY [User Name]'
INSERT INTO #TABLE_TEMP
EXEC(#QUERY)
SET #START_DATE = DATEADD(DD, 1, #START_DATE)
END
Without the query plan, it's hard to say for sure.
But there are some clear efficiencies to be had.
Firstly, there is no need for a WHILE loop. Create a Dates table which has every single date in it. Then you can simply join it.
Furthermore, do not inject the #USER_ID values. Instead, pass them thorugh as a Table Valued Parameter. At the least, split what you have now into a temp table or table variable.
Do not cast values you want to join on. For example, to check if START_TIME falls on a certain date, you can do WHERE START_TIME >= BeginningOfDate AND START_TIME < BeginningOfNextDate.
The LEFT JOINs are suspicious, especially given you are filtering on those tables in the WHERE.
Use NOT EXISTS instead of NOT IN or you could get incorrect results
DECLARE #START_DATE date = '2022-07-01';
DECLARE #END_DATE date = '2022-07-31';
DECLARE #USER_ID nvarchar(max) = '1,2,3,4,5,6,7,8,9';
DECLARE #userIds TABLE (userId int PRIMARY KEY);
INSERT #userIds (userId)
SELECT CAST(value AS int)
FROM STRING_SPLIT(#USER_ID, ',');
SELECT
s.userid as [UserId],
s.username as [UserName],
d.Date as [StartDate],
MAX(h.START_TIME) as [FirstLogin],
MAX(ISNULL(h.END_TIME, s.LAST_SEEN_TIME)) as [LastLogout]
FROM Dates d
JOIN USER s
LEFT JOIN USER_LOGIN_HISTORY h ON h.userid = s.userid
LEFT JOIN TEMP_USER_INACTIVATION TUI
ON TUI.userid = s.userid
ON d.Date BETWEEN ACTIVATED_DATE AND DEACTIVATD_DATE -- specify table alias (don't know which?)
WHERE s.userid in (SELECT u.userId FROM #userIds u)
AND NOT EXISTS (SELECT 1
FROM USER_LOGIN_HISTORY ulh
WHERE ulh.START_TIME >= CAST(d.date AS datetime)
AND ulh.START_TIME < CAST(DATEADD(day, 1, d.date) AS datetime)
AND ulh.userid = h.userid
)
AND ACTIVATED_DATE IS NOT NULL
AND d.Date BETWEEN #START_DATE AND #END_DATE
GROUP BY
d.Date,
s.userid,
s.username,
s.last_seen_time
HAVING CAST(MAX(ISNULL(h.END_TIME, s.LAST_SEEN_TIME)) AS DATE) <> d.date
ORDER BY -- do you need this? remove if possible.
s.username;
Better to collect dates in a table rather than running query in a loop. Use following query to collect dates between given date range:
DECLARE #day INT= 1
DECLARE #dates TABLE(datDate DATE)
--creates dates table first and then create dates for the given month.
WHILE ISDATE('2022-8-' + CAST(#day AS VARCHAR)) = 1
BEGIN
INSERT INTO #dates
VALUES (DATEFROMPARTS(2022, 8, #day))
SET #day = #day + 1
END
Then to get all dates where user did not login, you have to use Cartesian join and left join as illustrated below
SELECT allDates.userID,
allDates.userName,
allDates.datDate notLoggedOn
FROM
(
--This will reutrun all users for all dates in a month i.e. 31 rows for august for every user
SELECT *
FROM Users,
#dates
) allDates
LEFT JOIN
(
--now get last login date for every user between given date range
SELECT userID,
MAX(login_date) last_Login_date
FROM USER_LOGIN_HISTORY
WHERE login_date BETWEEN '2022-08-01' AND '2022-08-31'
GROUP BY userID
) loggedDates ON loggedDates.last_Login_date = allDates.datDate
WHERE loggedDates.last_Login_date IS NULL --filter out only those users who have not logged in
ORDER BY allDates.userID,
allDates.datDate
From this query you will get every day of month when a user did not logged in.
If there is no need to list every single date when user did not log in, then Cartesian join can be omitted. This will further improve the performance.
I hope this will help.

Can I "left join" days between 2 dates in sql server?

There is a table in SQL Server where data is entered day by day. In this table, data is not filled in some days.
Therefore, there are no records in the table.
Sample: dataTable
I need to generate a report like the one below from this table.
Create a table with all the days of the year. I know that I can output a report by "joining" the "dataTable" table.
But this solution seems a bit strange to me.
Is there another way?
the code i use for temp date table
CREATE TABLE tempDate (
calendarDate date,
PRIMARY KEY (calendarDate)
)
DECLARE
#start DATE= '2021-01-01',
#dateCount INT= 730,
#rowNumber INT=1
WHILE (#rowNumber < #dateCount)
BEGIN
INSERT INTO tempDate values (DATEADD(DAY, #rowNumber, #start))
set #rowNumber=#rowNumber+1
END
GO
select * from tempDate
This is how I join using this table
SELECT
*
FROM
tempDate td WITH (NOLOCK)
LEFT JOIN dataTable dt WITH (NOLOCK) ON dt.reportDate = td.calendarDate
WHERE
td.calendarDate BETWEEN '2021-09-05' AND '2021-09-15'
Create a table with all the days of the year. I know that I can output a report by "joining" the "dataTable" table.
This is the way. You can generate that "table" on the fly if you really want to, but normally the best way is to simply have a calendar table.
You can use common expression tables for dates. The code you need:
IF(OBJECT_ID('tempdb..#t') IS NOT NULL)
BEGIN
DROP TABLE #t
END
CREATE TABLE #t
(
id int,
dt date,
dsc varchar(100),
)
INSERT INTO #t
VALUES
(1, '2021.09.08', 'a'),
(1, '2021.09.09', 'b'),
(1, '2021.09.12', 'c')
DECLARE #minDate AS DATE
SET #minDate = (SELECT MIN(dt) FROM #t)
DECLARE #maxDate AS DATE
SET #maxDate = (SELECT MAX(dt) FROM #t)
;WITH cte
AS
(
SELECT #minDate AS [dt]
UNION ALL
SELECT DATEADD(DAY, 1, [dt])
FROM cte
WHERE DATEADD(DAY, 1, [dt])<=#maxDate
)
SELECT
ISNULL(CAST(t.id AS VARCHAR(10)), '') AS [id],
cte.dt AS [dt],
ISNULL(t.dsc, 'No record has been entered in the table.') AS [dsc]
FROM
cte
LEFT JOIN #t t on t.dt=cte.dt
The fastest method is to use a numbers table, you can get a date list between 2 dates with that:
DECLARE #Date1 DATE, #Date2 DATE
SET #Date1 = '20200528'
SET #Date2 = '20200625'
SELECT DATEADD(DAY,number+1,#Date1) [Date]
FROM master..spt_values
WHERE type = 'P'
AND DATEADD(DAY,number+1,#Date1) < #Date2
If you go go in LEFT JOIN this select, whit your table, you have the result that you want.
SELECT *
FROM (SELECT DATEADD(DAY,number+1,#Date1) [Date]
FROM master..spt_values WITH (NOLOCK)
WHERE type = 'P'
AND DATEADD(DAY,number+1,#Date1) < #Date2 ) as a
LEFT JOIN yourTable dt WITH (NOLOCK) ON a.date = dt.reportDate
WHERE td.[Date] BETWEEN '2021-09-05' AND '2021-09-15'

Get the average Hour period from a selected Date range

I have a order table which happens to have a column of DataType "Datetime" which contains Date and Time of the arrival of product to the Company(customer) and i have multiple type of users
their types are like:
Employee, CompanyOwners(customers),Dispatchers..etc
I have managed to get the date range of orders against to the specific user who is the owner of the company using the following T-SQL query below:
declare #username varchar(130), #DateFrom varchar(10), #DateTo varchar(10)
set #username = 'chabow';set #DateFrom='4/01/2018';set #DateTo='6/30/2018'
;with cte as (
select count(o.ArrivalDate) as orders_count, cast(o.ArrivalDate as Date) as ArrivalDate
from view_MembershipUsers msu
inner join XrefCompanyUsers rfu on msu.UserName = rfu.UserName
inner join Company f on f.CompanyID = rfu.CompanyID
inner join orders o on o.CompanyID = f.CompanyID
where msu.UserName = #username and
(o.ArrivalDate >= #DateFrom and o.ArrivalDate <= #DateTo)
group by cast(o.ArrivalDate as Date)
)
select *,
(select max(orders_count)+2 from cte) as Total
from cte
order by ArrivalDate
What i actually need to retrieve is the Average hour of arrival of product along with the data i have retrieved using above query from selected date range. I am clueless to how i can get the average in hours from this date range without getting the date difference, because i need to read "date" of every row to get the most possible accurate average hour.
I have extracted some sample dates for ease in order for you guys to help me.
create table SelectedData
(ArrivalDate date, ActualDateTime datetime)
insert into SelectedData values
('2018-04-01','2018-04-01 20:45:00.000'),
('2018-04-04','2018-04-04 19:00:00.000'),
('2018-04-05','2018-04-05 14:00:00.000'),
('2018-04-05','2018-04-05 14:23:00.000'),
('2018-04-05','2018-04-05 18:30:00.000'),
('2018-04-06','2018-04-06 12:30:00.000'),
('2018-04-06','2018-04-06 18:08:00.000')
declare #DateFrom varchar(10), #DateTo varchar(10)
set #DateFrom='4/01/2018';set #DateTo='6/30/2018'
select *
from SelectedData
order by ArrivalDate
Thanks and regards.
Change your type to DateTime, and not date. Like this :
drop table SelectedData
create table SelectedData
(ArrivalDate date, ActualDateTime datetime)
insert into SelectedData values
('2018-04-01','2018-04-01 20:45:00.000'),
('2018-04-04','2018-04-04 19:00:00.000'),
('2018-04-05','2018-04-05 14:00:00.000'),
('2018-04-05','2018-04-05 14:23:00.000'),
('2018-04-05','2018-04-05 18:30:00.000'),
('2018-04-06','2018-04-06 12:30:00.000'),
('2018-04-06','2018-04-06 18:08:00.000')
declare #DateFrom varchar(10), #DateTo varchar(10)
set #DateFrom='4/01/2018';set #DateTo='6/30/2018'
select avg(datePart(hour, ActualDateTime))
from SelectedData
--order by ArrivalDate
And then get the datepart of hours from the date, and then the average of that.

Using date parameter in SQL Server CTE

I'm trying to use a start and end date parameter in a T-SQL common table expression. I'm very new to SQL Server development and I'm unsure of what I'm missing in the query.
I can specify values for #startdate & #enddate and get correct results.
However, I'm trying to figure out how to make the two parameters open so a user can specify start and end date values. The query will be used in an SSRS report.
DECLARE #startdate Datetime,
#enddate Datetime;
SET #startdate = '2017-02-09';
SET #enddate = '2017-02-10';
WITH ManHours AS
(
SELECT DISTINCT
a.plant_name AS Plant, SUM(tc.total_hr) AS TotalHours
FROM
area AS a
INNER JOIN
tf_department AS dep ON a.plant_id = dep.plant_id
INNER JOIN
tf_timecard AS tc ON dep.department_id = tc.department_id
WHERE
tc.timecard_dt BETWEEN #startdate AND #enddate
AND tc.department_id IN (266, 453, ...endlessly long list of IDs......)
AND tc.hourtype_id = 1
GROUP BY
a.plant_name),
Tonnage AS
(
SELECT DISTINCT
a.plant_name AS Plant, SUM(tglt.postqty) AS TotalTonnage
FROM
area AS a
INNER JOIN
plantgl AS pgl ON a.plant_id = pgl.plant_id
INNER JOIN
tgltransaction AS tglt ON pgl.glacckey = tglt.glacctkey
WHERE
tglt.postdate BETWEEN #startdate AND #enddate
GROUP BY
a.plant_name
)
SELECT DISTINCT
ManHours.Plant,
SUM(TotalTonnage) as 'Production Tons' ,
SUM(TotalHours) as 'Man Hours',
TotalTonnage / TotalHours AS TonsPerManHour
FROM
ManHours
LEFT OUTER JOIN
Tonnage ON ManHours.Plant = tonnage.Plant
GROUP BY
ManHours.Plant, ManHours.TotalHours, Tonnage.TotalTonnage
Below is an example of a stored procedure you could use. In addition, I provided two alternatives to the "endlessly long list of IDs" that is specified in the CTE. In my opinion, it is optimal to pull this logic out of the query and place it at the beginning of the stored procedure. This will enable you, or others, to easily go back and modify this list if / when it changes. Even better, I provided a TABLE VARIABLE (#ListOfDeptIdsFromTable) that you can use to actually retrieve this data as opposed to hard-coding a string.
CREATE PROCEDURE Report
#startdate DATETIME,
#enddate DATETIME
AS
BEGIN
SET NOCOUNT ON;
DECLARE #ListOfDeptIds VARCHAR(MAX) = '266, 453, ...endlessly long list of IDs......';
DECLARE #ListOfDeptIdsFromTable TABLE
(
department_id INT
)
INSERT INTO #ListOfDeptIdsFromTable (department_id)
SELECT department_id
FROM -- Table here
WHERE -- Where credentials to retrieve the long list
WITH ManHours AS
(
SELECT DISTINCT
a.plant_name AS Plant, SUM(tc.total_hr) AS TotalHours
FROM
area AS a
INNER JOIN
tf_department AS dep ON a.plant_id = dep.plant_id
INNER JOIN
tf_timecard AS tc ON dep.department_id = tc.department_id
WHERE
tc.timecard_dt BETWEEN #startdate AND #enddate
AND tc.department_id IN (#ListOfDeptIds) -- or ... IN (SELECT department_id FROM #ListOfDeptIdsFromTable)
AND tc.hourtype_id = 1
GROUP BY
a.plant_name),
Tonnage AS
(
SELECT DISTINCT
a.plant_name AS Plant, SUM(tglt.postqty) AS TotalTonnage
FROM
area AS a
INNER JOIN
plantgl AS pgl ON a.plant_id = pgl.plant_id
INNER JOIN
tgltransaction AS tglt ON pgl.glacckey = tglt.glacctkey
WHERE
tglt.postdate BETWEEN #startdate AND #enddate
GROUP BY
a.plant_name
)
SELECT DISTINCT
ManHours.Plant,
SUM(TotalTonnage) as 'Production Tons' ,
SUM(TotalHours) as 'Man Hours',
TotalTonnage / TotalHours AS TonsPerManHour
FROM
ManHours
LEFT OUTER JOIN
Tonnage ON ManHours.Plant = tonnage.Plant
GROUP BY
ManHours.Plant, ManHours.TotalHours, Tonnage.TotalTonnage
END
GO

Count days in date range with set of exclusions which may overlap

Given the following example query, what is a sound and performant approach to counting the total days in a date range when also given a set of ranges to exclude, given that those ranges may have dates which overlap?
More simply, I have a table with a set of date ranges where the billing is turned off, I start with a date range (say Jan1 - Jan31) and I need to determine how many billable days occured in that range. Simply a datediff of the days minus a sum of the datediff on the disabled days. However, there is a chance that the disabled date ranges overlap, ie disabled Jan5-Jan8 in one record and Jan7-Jan10 in another record - thus a simple sum would double count Jan7. What is the best way to exclude these overlaps and get an accurage count.
Declare #disableranges table (disableFrom datetime, disableTo datetime)
insert into #disableranges
select '01/05/2013', '01/08/2013' union
select '01/07/2013', '01/10/2013' union
select '01/15/2013', '01/20/2013'
declare #fromDate datetime = '01/01/2013'
declare #toDate datetime = '01/31/2013'
declare #totalDays int = DATEDIFF(day,#fromDate,#toDate)
declare #disabledDays int = (0 /*not sure best way to calc this*/)
select #totalDays - #disabledDays
You can use a recursive CTE to generate dates between #dateFrom and #dateTo. Then compare the dates with the ranges, and find all dates that are in any range. Finally, count the number of rows in the result to get the count of disabled dates (DEMO):
-- recursive CTE to generate dates
;with dates as (
select #fromDate as date
union all
select dateadd(day, 1, date)
from dates
where date < #toDate
)
-- join with disable ranges to find dates in any range
, disabledDates as (
select date from dates D
left join #disableranges R
on D.date >= R.disableFrom and d.Date < R.disableTo
group by date
having count(R.disablefrom) >= 1
)
-- count up the total disabled dates
select #disabledDays=count(*) from disabledDates;
Tried this and working okay as far as I am concerned.
Declare #disableranges table (disableFrom datetime, disableTo datetime)
insert into #disableranges
select '01/05/2013', '01/08/2013' union
select '01/07/2013', '01/10/2013' union
select '01/15/2013', '01/20/2013'
declare #fromDate datetime = '01/01/2013'
declare #toDate datetime = '01/31/2013'
declare #totalDays int = DATEDIFF(day,#fromDate,#toDate) + 1 /*Without +1 it is giving 30 instead of 31*/
declare #disabledDays int = (0 /*not sure best way to calc this*/)
/*Fill temporary table with the given date range.*/
SELECT DATEADD(DAY, nbr - 1, #fromDate) TempDate INTO #Temp
FROM ( SELECT ROW_NUMBER() OVER ( ORDER BY c.object_id ) AS Nbr
FROM sys.columns c
) nbrs
WHERE nbr - 1 <= DATEDIFF(DAY, #fromDate, #toDate)
/*Check how many dates exists in the disableranges table*/
SELECT #disabledDays=count(*) from #Temp t WHERE
EXISTS(SELECT * FROM #disableranges
WHERE t.TempDate BETWEEN disableFrom AND DATEADD(d, -1, disableTo))
select #totalDays /*Output:31*/
select #disabledDays /*Output:10*/
select #totalDays - #disabledDays /*Output:21*/
drop table #Temp
Taken help from the answer https://stackoverflow.com/a/7825036/341117 to fill table with date range

Resources