Query to show result in the charts - sql-server

I have to implement charts in my application. Suppose i have a table structure
DECLARE #SONGS TABLE
(
[ID] INT IDENTITY,
[SONGNAME] VARCHAR(20),
[CREATEDDATE] DATETIME
)
INSERT INTO #SONGS
SELECT 'SONG1','20091102' UNION ALL
SELECT 'SONG2','20091103' UNION ALL
SELECT 'SONG3','20091107' UNION ALL
SELECT 'SONG4','20091107' UNION ALL
SELECT 'SONG5','20091107' UNION ALL
SELECT 'SONG6','20091109'
Now user will pass start date and end date from outside as parameters like below
DECLARE #STARTDATE DATETIME
DECLARE #ENDDATE DATETIME
SET #STARTDATE='20091101'
SET #ENDDATE='20091111'
Now user has further one more option(SAY #OPTION VARCHAR(20) ) whether he wants the results with dates split into individual dates between the start date and end date, second option he can choose to have the results with dates into the months between the start date and end date, similarly for year.
--OUTPUT I NEED IS when #OPTION IS DATE
DATE [SONGCOUNT]
------------------------------------------
20091101 0
20091102 1
20091103 1
20091104 0
20091105 0
20091106 0
20091107 3
20091108 0
20091109 1
20091110 0
20091111 0
Similarly i want the results with dates splitted according the option(day,week,month,year) having count next to it.
My goal is to display date on xaxis and count on y axis, can you suggest me a way to implement the same.

DECLARE #dimDate TABLE (
myDate datetime
,dt int
,yr int
,ym int
)
DECLARE #dte datetime
SET #dte = #STARTDATE
WHILE #dte <= #ENDDATE
BEGIN
INSERT INTO #dimDate (myDate, dt, yr, ym)
VALUES(
#dte
,datepart(yy,#dte)*10000+ datepart(mm,#dte)*100 + datepart(dd,#dte)
,datepart(yy,#dte)
,datepart(yy,#dte)*100+ datepart(mm,#dte)
)
SET #dte = dateadd(dd,1,#dte)
END
.
DECLARE #option varchar(2)
SET #option ='dt'
.
-- per day
IF #option ='dt'
BEGIN
SELECT d.dt, COUNT(s.ID) AS "song_count"
FROM #dimDate AS d
LEFT JOIN #SONGS AS s ON d.myDate = s.CREATEDDATE
GROUP BY d.dt
END
.
-- per year
IF #option ='yr'
BEGIN
SELECT d.yr, COUNT(s.ID) AS "song_count"
FROM #dimDate AS d
LEFT JOIN #SONGS AS s ON d.myDate = s.CREATEDDATE
GROUP BY d.yr
END
.
-- per year-month
IF #option ='ym'
BEGIN
SELECT d.ym, COUNT(s.ID) AS "song_count"
FROM #dimDate AS d
LEFT JOIN #SONGS AS s ON d.myDate = s.CREATEDDATE
GROUP BY d.ym
END

For making the results in x & y axis, use PIVOT(SQL server 2005+).
This kind of queries are called CROSS TAB QUERIES
For your reference
SQL Server PIVOT examples

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.

Translating SQL Server Cursor to Azure Synapse

I have the following code that loops through a table with unique model numbers and creates a new table that contains, for each model numbers, a row based on the year and week number. How can I translate this so it doesn't use a cursor?
DECLARE #current_model varchar(50);
--declare a cursor that iterates through model numbers in ItemInformation table
DECLARE model_cursor CURSOR FOR
SELECT model from ItemInformation
--start the cursor
OPEN model_cursor
--get the next (first value)
FETCH NEXT FROM model_cursor INTO #current_model;
DECLARE #year_counter SMALLINT;
DECLARE #week_counter TINYINT;
WHILE (##FETCH_STATUS = 0) --fetch status returns the status of the last cursor, if 0 then there is a next value (FETCH statement was successful)
BEGIN
SET #year_counter = 2019;
WHILE (#year_counter <= Datepart(year, Getdate() - 1) + 2)
BEGIN
SET #week_counter = 1;
WHILE (#week_counter <= 52)
BEGIN
INSERT INTO dbo.ModelCalendar(
model,
sales_year,
sales_week
)
VALUES(
#current_model,
#year_counter,
#week_counter
)
SET #week_counter = #week_counter + 1
END
SET #year_counter = #year_counter + 1
END
FETCH NEXT FROM model_cursor INTO #current_model
END;
CLOSE model_cursor;
DEALLOCATE model_cursor;
If ItemInformation contains the following table:
model,invoice
a,4.99
b,9.99
c,1.99
d,8.99
then the expected output is:
model,sales_year,sales_week
A,2019,1
A,2019,2
A,2019,3
...
A,2019,52
A,2020,1
A,2020,2
A,2020,3
...
A,2020,51
A,2020,52
A,2020,53 (this is 53 because 2020 is leap year and has 53 weeks)
A,2021,1
A,2021,2
...
A,2022,1
A,2022,2
...
A,2022,52
B,2019,1
B,2019,2
...
D, 2022,52
Using CTE's you can get all combinations of weeks and years within the range required. Then join your data table on.
declare #Test table (model varchar(1), invoice varchar(4));
insert into #Test (model, invoice)
values
('a', '4.99'),
('b', '9.99'),
('c', '1.99'),
('d', '8.99');
with Week_CTE as (
select 1 as WeekNo
union all
select 1 + WeekNo
from Week_CTE
where WeekNo < 53
), Year_CTE as (
select 2019 YearNo
union all
select 1 + YearNo
from Year_CTE
where YearNo <= datepart(year, current_timestamp)
)
select T.model, yr.YearNo, wk.WeekNo
from Week_CTE wk
cross join (
select YearNo
-- Find the last week of the year (52 or 53) -- might need to change the start day of the week for this to be correct
, datepart(week, dateadd(day, -1, dateadd(year, 1, '01 Jan ' + convert(varchar(4),YearNo)))) LastWeek
from Year_CTE yr
) yr
cross join (
-- Assuming only a single row per model is required, and the invoice column can be ignored
select model
from #Test
group by model
) T
where wk.WeekNo <= yr.LastWeek
order by yr.YearNo, wk.WeekNo;
As you have advised that using a recursive CTE is not an option, you can try using a CTE without recursion:
with T(N) as (
select X.N
from (values (0),(0),(0),(0),(0),(0),(0),(0)) X(N)
), W(N) as (
select top (53) row_number() over (order by ##version) as N
from T T1
cross join T T2
), Y(N) as (
-- Upper limit on number of years
select top (12) 2018 + row_number() over (order by ##version) AS N
from T T1
cross join T T2
)
select W.N as WeekNo, Y.N YearNo, T.model
from W
cross join (
select N
-- Find the last week of the year (52 or 53) -- might need to change the start day of the week for this to be correct
, datepart(week, dateadd(day, -1, dateadd(year, 1, '01 Jan ' + convert(varchar(4),N)))) LastWeek
from Y
) Y
cross join (
-- Assuming only a single row per model is required, and the invoice column can be ignored
select model
from #Test
group by model
) T
-- Filter to required number of years.
where Y.N <= datepart(year, current_timestamp) + 1
and W.N <= Y.LastWeek
order by Y.N, W.N, T.model;
Note: If you setup your sample data in future with the DDL/DML as shown here you will greatly assist people attempting to answer.
I don't like to see a loop solution where a set solution can be found. So here goes Take II with no CTE, no values and no row_number() (the table variable is just to simulate your data so not part of the actual solution):
declare #Test table (model varchar(1), invoice varchar(4));
insert into #Test (model, invoice)
values
('a', '4.99'),
('b', '9.99'),
('c', '1.99'),
('d', '8.99');
select Y.N + 2019 YearNumber, W.WeekNumber, T.Model
from (
-- Cross join 5 * 10, then filter to 52/53 as required
select W1.N * 10 + W2.N + 1 WeekNumber
from (
select 0 N
union all select 1
union all select 2
union all select 3
union all select 4
union all select 5
) W1
cross join (
select 0 N
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
) W2
) W
-- Cross join number of years required, just ensure its more than will ever be needed then filter back
cross join (
select 0 N
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
) Y
cross join (
-- Assuming only a single row per model is required, and the invoice column can be ignored
select model
from #Test
group by model
) T
-- Some filter to restrict the years
where Y.N <= 3
-- Some filter to restrict the weeks
and W.WeekNumber <= 53
order by YearNumber, WeekNumber;
I created a table to temporary calendar table containing all the weeks and years. To account for leap years, I took the last 7 days of a year and got the ISO week for each day. To know how many weeks are in a year, I put these values into another temp table and took the max value of it. Azure Synapse doesn't support multiple values in one insert so it looks a lot longer than it should be. I also have to declare each as variable since Synapse can only insert literal or variable. I then cross-joined with my ItemInformation table.
CREATE TABLE #temp_dates
(
year SMALLINT,
week TINYINT
);
CREATE TABLE #temp_weeks
(
week_num TINYINT
);
DECLARE #year_counter SMALLINT
SET #year_counter = 2019
DECLARE #week_counter TINYINT
WHILE ( #year_counter <= Datepart(year, Getdate() - 1) + 2 )
BEGIN
SET #week_counter = 1;
DECLARE #day_1 TINYINT
SET #day_1 = Datepart(isowk, Concat('12-25-', #year_counter))
DECLARE #day_2 TINYINT
SET #day_2 = Datepart(isowk, Concat('12-26-', #year_counter))
DECLARE #day_3 TINYINT
SET #day_3 = Datepart(isowk, Concat('12-27-', #year_counter))
DECLARE #day_4 TINYINT
SET #day_4 = Datepart(isowk, Concat('12-28-', #year_counter))
DECLARE #day_5 TINYINT
SET #day_5 = Datepart(isowk, Concat('12-29-', #year_counter))
DECLARE #day_6 TINYINT
SET #day_6 = Datepart(isowk, Concat('12-30-', #year_counter))
DECLARE #day_7 TINYINT
SET #day_7 = Datepart(isowk, Concat('12-31-', #year_counter))
TRUNCATE TABLE #temp_weeks
INSERT INTO #temp_weeks
(week_num)
VALUES (#day_1)
INSERT INTO #temp_weeks
(week_num)
VALUES (#day_2)
INSERT INTO #temp_weeks
(week_num)
VALUES (#day_3)
INSERT INTO #temp_weeks
(week_num)
VALUES (#day_4)
INSERT INTO #temp_weeks
(week_num)
VALUES (#day_5)
INSERT INTO #temp_weeks
(week_num)
VALUES (#day_6)
INSERT INTO #temp_weeks
(week_num)
VALUES (#day_7)
DECLARE #max_week TINYINT
SET #max_week = (SELECT Max(week_num)
FROM #temp_weeks)
WHILE ( #week_counter <= #max_week )
BEGIN
INSERT INTO #temp_dates
(year,
week)
VALUES ( #year_counter,
#week_counter )
SET #week_counter = #week_counter + 1
END
SET #year_counter = #year_counter + 1
END
DROP TABLE #temp_weeks;
SELECT i.model,
d.year,
d.week
FROM dbo.iteminformation i
CROSS JOIN #temp_dates d
ORDER BY model,
year,
week
DROP TABLE #temp_dates

Calculate Annual Leaves for All employees and return more than one column in SQL Server

The following procedure calculates annual leaves of employees based on the provided empid. First Question: How can I create/modify this procedure to calculate annual leaves for all employees. Second Question: Return more columns like empname, designation, annual leave balance? please note that i am using sql server 2016 community edition.
ALTER proc [dbo].[spAvailalbeAL](#empID int)
as
begin
declare #StartDate datetime
declare #totMonths int
declare #aAnnualLeaves int
declare #avlAL int
set #avlAL = (select sum(Availed) from LeaveDetails where empid = #empID AND TypeID=3)
if ( #avlAL IS NULL)
begin
set #StartDate = '2017-07-01'
set #totMonths = (SELECT DATEDIFF(mm, #StartDate, GETDATE()))
set #aAnnualLeaves = 2
set #aAnnualLeaves = (#aAnnualLeaves*#totMonths)
select #aAnnualLeaves
end
else
begin
set #StartDate = '2017-07-01'
set #totMonths = (SELECT DATEDIFF(mm, #StartDate, GETDATE()))
set #aAnnualLeaves = 2
set #aAnnualLeaves = (#aAnnualLeaves*#totMonths)-#avlAL
select #aAnnualLeaves
end
end
For showing all employees you can pass 0 or Null for #empid parameter and then you can create Temporary Table like
CREATE TABLE #LeaveDetails(EmpID INT,TotalALAvailed INT)
then fill the table
INSERT INTO #LeaveDetails(EmpID,TotalALAvailed) SELECT EmpID,SUM(Availed) FROM LeaveDetails WHERE (EmpID=#EmpID OR #EmpID=0) AND Type=3 GROUP BY EmpID
You can select all required columns in last Select statement.
You need something like this:
Create proc [dbo].[spAvailalbeAL]
as
begin
declare #StartDate datetime = '2017-07-01'
declare #totMonths int
declare #aAnnualLeaves int
set #totMonths = (SELECT DATEDIFF(mm, #StartDate, GETDATE()))
print #totMonths
set #aAnnualLeaves = 2
set #aAnnualLeaves = (#aAnnualLeaves*#totMonths)
print #aAnnualLeaves
SELECT distinct e.EMPLOYEE_NAME,e.desg_cd,sum(isnull(ea.Availed,0)) over ( partition by e.EmpID ),#aAnnualLeaves - sum(isnull(ea.Availed,0)) over ( partition by e.EmpID ) as leaves
from Employee e
inner join LeaveDetails ea
on e.EMPLOYEE_CD = ea.EMPLOYEE_CD
where DATE > #StartDate
AND ea.TypeID=3
end
-- exec spAvailalbeAL

Pivot Activity Code Days Months

I am trying to get the activity codes for specific days to show the 31 days in every month of the year for a specific staff member.
If the staff member was present, sick, holiday leave, etc... I want those activity codes to display based on the output below for a year act_date range.
Thanks!
Pivot Activity Code Days Months
This can be achieved with pivoting. Here you can enter the staff id in the query to fetch the results for that particular staff.
--create table
create table staff_info
(
staffId int,
actDate datetime,
activityCode int
)
--insert values
insert into staff_info values
(2699, '01/02/2017', 101),
(2699, '05/14/2017', 303),
(2699, '08/06/2017', 101),
(1927, '10/25/2017', 105)
--actual solution
select * from
(
select staffId, day(actDate) as act_day,month(actDate) as actual_month,
activityCode
from staff_info
where staffId=2699 ----- enter the staff id here
) src
pivot
(
sum(activityCode)
for act_day in ([1],[2],[3],[4],[5],[6],[7],[8],[9],[10],[11],[12],[13],
[14],[15],
[16],[17],[18],[19],[20],[21],[22],[23],[24],[25],[26],[27],[28],[29],[30],
[31]
)
) p
Result:
Firstly, create a function which would give the date values for a specific range
CREATE FUNCTION [dbo].[GetAllDatesBetweenRange]
(
#FromDate DATE
,#ToDate DATE
)
RETURNS #Dates TABLE
(
DateVal DATE
)
AS
BEGIN
;WITH CTE
AS
(
SELECT #FromDate AS FromDate
UNION ALL
SELECT DATEADD(DD,1,FromDate)
FROM CTE
WHERE FromDate < #ToDate
)
INSERT INTO #Dates
SELECT FromDate FROM CTE
OPTION (MAXRECURSION 0)
RETURN;
END
GO
Use the below dynamic query to pivot for the specific date range
DECLARE #Sql NVARCHAR(MAX);
DECLARE #DateVal NVARCHAR(MAX);
SELECT #DateVal = STUFF((SELECT ',['+CAST(DateVal AS NVARCHAR(50))+']'
FROM [dbo].[GetAllDatesBetweenRange]('2017-01-01','2017-12-31')
FOR XML PATH('')),1,1,'')
SET #Sql = '
;WITH CTE
AS
(
SELECT Res1.STAFF_ID
,Res2.DateVal
,Res1.ACTIVITY_CODE
FROM [dbo].[GetAllDatesBetweenRange](''''2017-01-01'''',''''2017-12-31'''') Res1
LEFT JOIN TableA A ON A.ACT_DATE = Res1.DateVal
)
SELECT STAFF_ID
,*
FROM CTE
PIVOT
(
MAX(ACTIVITY_CODE)
FOR DateVal IN ('+#DateVal+')'+'
)'
EXEC SP_EXECUTESQL #Sql

SQL Query for showing dates not stored in the table

I have a table in which i am storing dates and other information.
I wanna display the records for the dates which are not stored in the table.
Eg.. i have dates 01/01/2012[dd/mm/yyyy] , 03/01/2012 , 06/01/2012.
I wanna show the output for the dates 02/01/2012 ,04/05/2012 , 05/01/2012.
Query for this please in SQLServer2008
You can use the DATEADD function. For example, to add 1 day to current date:
SELECT DATEADD(dd, 1, GETDATE())
You can find more information on MSDN.
;WITH MYCTE AS
(
SELECT CAST('1900-01-01' AS DATETIME) DateValue
UNION ALL
SELECT DateValue + 1
FROM MYCTE
WHERE DateValue + 1 < '3550-12-31'
)
SELECT DateValue, B.SomeColumn
FROM MYCTE A
LEFT JOIN MyTable B ON A.DateValue = B.DateValue
OPTION (MAXRECURSION 0)
I got the date range cte from here (and it takes 4 seconds to generate a table of 250000 rows)
This code displays all dates from 2011 year missing in your table:
create table #dates (d datetime);
declare #start_period datetime;
set #start_period='01.01.2011';
declare #end_period datetime;
set #end_period='01.01.2012';
declare #d datetime;
set #d=#start_period
while(#d<#end_period)
begin
insert into #dates (d) values (#d)
SET #d=#d+1
end
select d from #dates where d not in (select <date> from <your_table>)

Resources