How to make a generic query for search page? - sql-server

I have a Order Search page where user can enter any search criteria and submit.
If if clicks submit without entering any criteria we should show all orders.
If users enters orderId we should show only that respective order.
If user enters date range and status we should show orders with that selected status in selected date range.
I tried as shown.
SELECT * FROM dbo.Orders
WHERE ( OrderNumber ='' OR OrderNumber ='212' )
AND ( (OrderDate BETWEEN '2010-01-01' AND '2012-10-10')
OR (OrderDate BETWEEN '' AND '') )

You cannot check if the field has the value AND if the field does not have the value. You will always get all records regardless. You will need to pull the values out into a variable. Unless you are writing dynamic SQL; if that is the case then you would only add to the where clause if you intend to filter...
DECLARE #OrderNumber VARCHAR, #StartDate VARCHAR, #EndDate VARCHAR
SET #OrderNumber = '212'
SET #StartDate = '2010-01-01'
SET #EndDate = '2012-01-01'
SELECT *
FROM dbo.Orders
WHERE (#OrderNumber ='' OR OrderNumber = #OrderNumber)
AND (#StartDate = '' OR OrderDate >= #StartDate)
AND (#EndDate = '' OR OrderDate <= #EndDate)

Check out this complete article from Erland and in particular the heading "Umachandar's Bag of Tricks"
Also check Implementing a Dynamic WHERE Clause
Extract from the above article showing how to make use of COALESCE to achieve this:
DECLARE #Cus_Name varchar(30),
#Cus_City varchar(30),
#Cus_Country varchar(30)
SET #Cus_Name = NULL
SET #Cus_City = 'Paris'
SET #Cus_Country = NULL
SELECT Cus_Name,
Cus_City,
Cus_Country
FROM Customers
WHERE Cus_Name = COALESCE(#Cus_Name,Cus_Name) AND
Cus_City = COALESCE(#Cus_City,Cus_City) AND
Cus_Country = COALESCE(#Cus_Country,Cus_Country)

this is a very simple query
declare #ordernumber int = null
SELECT * FROM dbo.Orders
WHERE
(#ordernumber is null or (OrderNumber IS NOT NULL AND OrderNumber IN (#ordernumber)))
AND ( (OrderDate BETWEEN '2010-01-01' AND '2012-10-10') OR (OrderDate BETWEEN '' AND '') )
Although this query is simple enough but here goes the explanation : if #ordernumber is null, then it would display all the records, while for the other case it will display only the record corresponding to the passed orderid.

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.

SQL error, Conversion failed when converting date and/or time from character string

declare #StartDate varchar(30), #myStartDate date, #iterate int, #DaysDiff int, #NoOfDays varchar (200) , #username varchar(30), #StaffID int
select #DaysDiff = 6, #iterate =0, #StartDate = '2015-04-29', #username = 'itdsnm'
select #StaffID = StaffID from Staff where LoginName = #username
select #myStartDate = cast(#StartDate as DATE)
begin
Create Table #Temp8(Dates date,MyDay int)
While #iterate < #DaysDiff +1
Begin
Insert Into #Temp8(Dates,MyDay)
Select #myStartDate,DATEPART(dw,#myStartDate)
Select #myStartDate = dateadd(dd,1,#myStartDate)
IF (Select COUNT(*) from #Temp8 where Dates In(Select StartDate From Holidays Where Username = #StaffID )) >0
Begin
Select #NoOfDays = 'One of your days fall between a holiday already taken, please review'
Return
End
Else
Select #iterate = #iterate + 1
End
;With Temp2(Dates,MyDay)
As
(Select Dates,MyDay from #Temp8 Where MyDay not in (1,7))
Select #NoOfDays = COUNT(*) from Temp2 Where Dates Not In (Select Date From BankHolidays)
end
Everything works fine in the above procedure, but when it comes to the With statement, it throws me an error "Conversion failed when converting date and/or time from character string." Can Some one help me with this. Thanks in advance
I agree with Tanner that the schema is required to be able to assess the issue (i.e. the column datatypes).
At first glance, though it appears to me that perhaps [Date] in [BankHolidays] is some sort of string datatype and that column contains certain string values that cannot be converted to a DATE type.

T-SQL Update From Order By

I am trying to write a Stored Procedure that will give me Audit information between a date range. The Audit table stores the Audit Date, Column Name and Old Value. I want to display the Old Value and the New Value in the result set. I need to get the new value from the next most recent Audit entry or from the entity itself. The stored procedure is a multi step approach to get the result set I need.
Create a #results temp table with the Audit records from within the date range.
Create a #currentValues temp table with the current values from the entity.
Update the #results table to store the new value
Here is the structure of the Audit Table:
AuditId uniqueidentifier NEWID()
AuditDate datetime GETDATE()
UserId uniqueidentifier
EntityId uniqueidentifier
ColumnName nvarchar(100)
OldValue nvarchar(MAX)
Here is the sql:
CREATE PROC GetAuditSummary
#StartDate datetime = NULL,
#EndDate datetime = NULL
AS
DECLARE #Results table(
AuditId uniqueidentifier,
AuditDate datetime,
UserId uniqueidentifier,
EndityId uniqueidentifier,
ColumnName nvarchar(100),
OldValue nvarchar(MAX),
NewValue nvarchar(MAX)
INSERT INTO #Results(AuditId, AuditDate, UserId, EntityId, ColumnName, OldValue)
SELECT AuditId, AuditDate, UserId, EntityId, ColumnName, OldValue
FROM Audit
WHERE (AuditDate >= #StartDate) AND (AuditDate < #EndDate)
DECLARE #CurrentValues table(
EntityId uniqueidentifier,
ColumnName nvarchar(100),
Value nvarchar(MAX)
)
--Lengthy Code to fill #CurrentValues temp table. Assume #CurrentValues is populated
UPDATE #Results
SET NewValue = n.Value
FROM #Results r INNER JOIN
(SELECT AUditId, AuditDate, EntityId, ColumnName, OldValue AS Value
FROM Audit
UNION ALL
SELECT NULL, GETDATE(), EntityId, ColumnName, Value
FROM #CurrentValues
ORDER BY AuditDate DESC
) n ON n.EntityId = r.EntityId AND
n.ColumnNmae = r.ColumName NAD
n.AuditDate > r.AuditDate
SELECT * FROM #Results ORDER BY AuditDate DESC
Now, and correct me if I'm wrong, when the update statement executes, the NewValue should be set to the last matching row in the joined result set and since I have the subquery ordered by AuditDate, the AuditDate closest to the current record from #Results should be the value that's set to NewValue. I've tried this, but I get an error telling me I can't use an Order By in a subquery. Is there another way to do this? I'm open to any suggestions, but I need to take performance into consideration as there is a chance of having thousands of rows in the result.
--EDIT
Here is one way to get it working, but I'm not sure it's the best on performance.
UPDATE #Results
SET NewValue = COALESCE(
(SELECT TOP 1 a.OldValue
FROM Audit a
WHERE (a.EntityId = r.EntityId) AND
(a.ColumnName = r.ColumnName) AND
(a.AuditDate > r.AuditDate)
ORDER BY a.AuditDate),
(SELECT TOP 1 c.Value
FROM #CurrentValues c
WHERE (c.EntityId = r.EntityId) AND
(c.ColumnName = r.ColumnName))
FROM #Results r
I would use Row_Number or Rank function to get the row after last matching row.
Following example should work, you may want to change (order by n.AuditDate) to (order by n.AuditDate desc) if you want most recent record after matching date.
UPDATE #Results
SET NewValue = n.Value
FROM #Results r
INNER JOIN
(
select n.EntityId, n.ColumnName, n.Value, Row_Number() over(partition by n.EntityId, n.ColumnName order by n.AuditDate) RowNumber
from
#Results ir
inner join (
SELECT AuditDate, EntityId, ColumnName, OldValue AS Value
FROM Audit
UNION ALL
SELECT GETDATE(), EntityId, ColumnName, Value
FROM #CurrentValues
) inn on inn.EntityId = ir.EntityId AND
inn.ColumnNmae = ir.ColumName NAD
inn.AuditDate > ir.AuditDate
) n ON n.EntityId = r.EntityId AND
n.ColumnNmae = r.ColumName AND
n.RowNumber = 1

Sql to return false if all records not found based on dates and value in field

Sorry if that wasn't too clear but it is hard to sum up.
Table Holiday which has one field named hDate
Table TimeSheet which has tsDate,Code
Sample Data
Holiday
7/4/2012
12/24/2012
12/25/2012
12/26/2012
Sample Data
TimeSheet
12/27/2012,W
12/24/2012,H
12/25/2012,W
I need to verify based on a date range that every record in the Holiday table that is between the date range exists in the Timesheet table and that they have an H value entered for Code
So if the date range passed in was 12/24-12/30 then the results should be empty since no data exists in timesheet for 12/26 and 12/25 has a code W in it. So basically I need an exists query to do this
You may try the following SQL. It counts the number of code <> 'H' and tsDate = null from the left outer join. If the count > 0, then return false.
DECLARE #Holiday TABLE
(
hDate DATETIME NOT NULL
)
DECLARE #TimeSheet TABLE
(
tsDate DATETIME,
Code NCHAR(1)
)
INSERT INTO #Holiday (hDate)
VALUES ('2012-07-04'), ('2012-12-24'), ('2012-12-25'), ('2012-12-26')
INSERT INTO #TimeSheet (tsDate, Code)
VALUES ('2012-12-27', 'W'), ('2012-12-24', 'H'), ('2012-12-25', 'W')
DECLARE #StartDate DATETIME = '2012-12-24'
DECLARE #EndDate DATETIME = '2012-12-30'
SELECT
CASE WHEN COUNT(1) = 0 THEN 'TRUE' ELSE 'FALSE' END AS 'BooleanResult'
FROM
(
SELECT hDate
FROM #Holiday
WHERE hDate BETWEEN #StartDate AND #EndDate
) H
LEFT JOIN #TimeSheet TS
ON TS.tsDate = H.hDate
WHERE
TS.Code != 'H' OR TS.tsDate IS NULL and 'H' not in (select doecode from timesheet
where timesheet.tsdate = TS.tsDate

How to properly populate the age attribute?

I have an MS-SQL Server which keeps track on a number of clients. If the birthday is known it is stored as a datetime attribute called dayOfBirth. Now I would like to have another attribute age, which keeps track of the current age of the client. Since age can change any day I figured a script might be the best idea.
First thing I did, was to create a stored procedure which computes the age given the birthday as datetime. Here is what I came up with:
USE [MyDB]
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE PROCEDURE CalculateAge
#dayOfBirth datetime,
#age INT OUTPUT
AS
DECLARE #today datetime, #thisYearBirthDay datetime
DECLARE #years int
SET #today = GETDATE()
SET #thisYearOfBirth = DATEADD(year, #dayOfBirth, #today), #dayOfBirth)
SET #years = DATEDIFF(year, #dayOfBirth, #today) - (CASE WHEN #thisYearBirthDay > #today THEN 1 ELSE 0 END)
SET #age = #years
Afterwards I created another script which runs through all records that have a non null dayOfBirth attribute and updates the age filled accordingly.
USE [MyDB]
GO
DECLARE #age int;
DECLARE #birth datetime;
DECLARE #id intl
DECLARE cursorQuery CURSOR FOR SELECT clientId FROM Clients WHERE dayOfBirth IS NOT NULL;
OPEN cursorQuery
FETCH NEXT FROM cursorQuery INTO #id
WHILE ##FETCH_STATUS = 0
BEGIN
SET #birth = (SELECT dayOfBirth from Kunden where clientId=#id);
EXEC dbo.CalculateAge #birth, #age OUTPUT;
UPDATE Clients SET Age = #age WHERE clientId = #id;
FETCH NEXT FROM cursorQuery INTO #id
END
CLOSE cursorQuery
DEALLOCATE cursorQuery
I would trigger the script above once per day to populate the age attribute. Thats what I have so far, but I have the feeling there is plenty of room for improvement.
** Thanks Sung Meister **
I ended up with something like this:
CREATE TABLE Client (
ID int identity(1,1) primary key,
DOB datetime null,
AGE as (case
when DOB is null then null
else DATEDIFF(YY,DOB,GETDATE()) - CASE WHEN (MONTH(GETDATE()) = MONTH(DOB) AND DAY(DOB) > DAY(GETDATE()) OR MONTH(GETDATE()) > MONTH(DOB)) THEN 1 ELSE 0 END
end
)
)
Instead of creating a trigger and do any manual updates,
easiest way to get around with this is to create a calculated field called Age.
This way, you do not have to worry about Age data being out of sync (data stays consistent) and no more trigger or manual update is required.
Calculating an age of person can be a bit hairy as you can see in the picture below.
Here is the text used
create table #Person (
ID int identity(1, 1) primary key,
DOB datetime null,
Age as
(case
when DOB is null then null
--; Born today!
when datediff(d, DOB, getdate()) = 0 then 0
--; Person is not even born yet!
when datediff(d, DOB, getdate()) < 0 then null
--; Before the person's B-day month so calculate year difference only
when cast(datepart(m, getdate()) as int) > cast(datepart(m, DOB) as int)
then datediff(year, DOB, getdate())
--; Before Person's b-day
else datediff(year, DOB, getdate()) - 1
end)
)
insert #Person select GetDate()
insert #Person select null
insert #Person select '12/31/1980'
select * from #Person
update #Person
set DOB = '01/01/1980'
where ID = 2
select * from #Person
You really shouldn't be storing the age within the database as it is easily calculated and changes on a daily basis.
I would suggest that you keep the date of birth field and just calculate the age as you need it. If you wish to have the age selected along with the other attributes then consider a view, perhaps with a user defined function to calculate the age.
The following is an example (untested) UDF that you could use
CREATE FUNCTION age
(
#userId int
)
RETURNS int
AS
BEGIN
DECLARE #Result int
SELECT #Result = DATEDIFF(year, dateofBirth, getDate())
FROM person
WHERE userId = #userId
RETURN #Result
END
GO
Then within your queries you can do something similar to the following,
SELECT *,
dbo.age(userId) as age
FROM person
In answer to your question on sorting etc, then you could create a view on the data and use that to show the data so something like this (untested)
CREATE VIEW personview(firstname, surname, dateOfBirth,age) AS
SELECT firstname,
surname,
dateOfbirth,
dbo.age(userid)
FROM person
You can then use this view to perform your queries, there could be a performance hit for filtering and sorting based on the age and if you regularly sort/filter based upon the age field then you may want to create an indexed view.
Use a view and/or function. Never store two fields that arew based on the exact same data if you can help it as they will eventually get out of sync.

Resources