Override TSQL query to another - sql-server

In our company we have a windows application that we can't develop it any more. This application use one of our live databases.
there is a query like this, that we spend a lot of resource to response this expensive query:
SELECT * FROM City
My question is, is there any way that when this query execute to database, override it with some thing else. For example change it to:
SELECT TOP 10 Name, Code FROM City

Spinning on the idea in #Stu's comment...
Table is renamed into City_Table (sqlfiddle does not work at the time of writing, sorry):
CREATE TABLE City_Table (Code INT, Name VARCHAR(20))
GO
INSERT INTO City_Table (Code, Name)
SELECT 1, 'City1'
UNION ALL
SELECT 2, 'City2'
UNION ALL
SELECT 3, 'City3'
Then a view could try to check the last command issued from current spid:
CREATE VIEW City
AS
SELECT Code, [Name] FROM
(
SELECT ROW_NUMBER() OVER (ORDER BY Code) rownum, Code, [Name]
FROM City_Table
) sub
WHERE
LTRIM(RTRIM(
(SELECT TEXT as query
FROM sys.dm_exec_connections
CROSS APPLY sys.dm_exec_sql_text(most_recent_sql_handle)
WHERE session_id = (SELECT ##SPID)
)
))
!= 'SELECT * FROM City'
OR sub.rownum <= 2
Then a query SELECT * FROM City will return
Code
Name
1
City1
2
City2
whereas a query like SELECT * FROM City ORDER BY 1 will return
Code
Name
1
City1
2
City2
3
City3
But to be honest: I'm not sure at all if this is a reliable way to find the last issued command in a specific application's scenario. And for sure querying sys views by an app user might raise security issues. Just been curious to test it out.

Related

Order SQL statement by count of sub-records efficiently (MS-SQL)

I have a table with two columns, an ID and a serial number. Let's call this table vehicle. The serial numbers are used for an auto-complete textbox. Right now, I simply narrow the list with a contains/like statement and select the top 5 hits. Obviously, this returns the 5 oldest records.
These vehicle entries are referenced by another table via foreign key. Let's call that one appearence. To improve the user experience, I want to present the five vehicles, which appeared most often during the last three months.
A simple approach would be, to simply join the two tables together, group by the serial number, count the number of appearences in the specified time range, and finally order it by that count descending.
The problem is, that this auto-complete textbox is accessed relatively often, so I wonder, if there is a built in optimization for this. My first idea was, to create a daily job, which updated a new probability column and order by that column. I've also seen the statistics option in SQL Server Management Studio, and I wonder, if that could help me. Or would there be a way to use views for that?
Updating the order every day would be abundant.
Here's the select statement (without the time component yet):
SELECT RegistrationNumber, count(lbt.ID) as cnt
FROM [Trailer] t
INNER JOIN [LoadingBay_Trailer] lbt ON t.ID = lbt.ID_Trailer
group by t.RegistrationNumber
order by cnt desc
For now, I've solved the problem with a simple view. I don't like this approach, because it creates a lot of load on the server, as the view is called after every keystroke the user performs:
CREATE VIEW [dbo].[TrailerAutocompleteHelper] AS
SELECT
t.ID,
t.RegistrationNumber,
t.InUse,
ROW_NUMBER() OVER(ORDER BY COUNT_BIG(lbt.ID_Trailer) DESC, RegistrationNumber ASC) AS Probability
FROM [dbo].[AvailableTrailers] t
LEFT JOIN [dbo].[LoadingBay_Trailer] lbt ON
t.ID = lbt.ID_Trailer AND
lbt.Arrival > DATEADD(month, -3, GETDATE())
GROUP BY t.ID, t.RegistrationNumber, t.InUse
GO
And C#
public async Task<List<TrailerAutocomplete>> GetTrailers(string registrationNumberPart) {
return await mapi.db.TrailerAutocompleteHelpers
.Where(t => t.RegistrationNumber.Contains(registrationNumberPart))
.OrderBy(t => t.Probability)
.Select(t => new TrailerAutocomplete() {
ID = t.ID,
RegistrationNumber = t.RegistrationNumber,
InUse = t.InUse
})
.Take(10)
.ToListAsync();
}
And here's what Entity framework makes out of my command:
exec sp_executesql N'SELECT TOP (10)
[Project1].[ID] AS [ID],
[Project1].[RegistrationNumber] AS [RegistrationNumber],
[Project1].[InUse] AS [InUse]
FROM ( SELECT
[Extent1].[ID] AS [ID],
[Extent1].[RegistrationNumber] AS [RegistrationNumber],
[Extent1].[InUse] AS [InUse],
[Extent1].[Probability] AS [Probability]
FROM (SELECT
[TrailerAutocompleteHelper].[ID] AS [ID],
[TrailerAutocompleteHelper].[RegistrationNumber] AS [RegistrationNumber],
[TrailerAutocompleteHelper].[InUse] AS [InUse],
[TrailerAutocompleteHelper].[Probability] AS [Probability]
FROM [dbo].[TrailerAutocompleteHelper] AS [TrailerAutocompleteHelper]) AS [Extent1]
WHERE [Extent1].[RegistrationNumber] LIKE #p__linq__0 ESCAPE N''~''
) AS [Project1]
ORDER BY [Project1].[Probability] ASC',N'#p__linq__0 nvarchar(4000)',#p__linq__0=N'%ABC%'

Is it possible to get two sets of data with one query

I need to get some items from database with top three comments for each item.
Now I have two stored procedures GetAllItems and GetTopThreeeCommentsByItemId.
In application I get 100 items and then in foreach loop I call GetTopThreeeCommentsByItemId procedure to get top three comments.
I know that this is bad from performance standpoint.
Is there some technique that allows to get this with one query?
I can use OUTER APPLY to get one top comment (if any) but I don't know how to get three.
Items {ItemId, Title, Description, Price etc.}
Comments {CommentId, ItemId etc.}
Sample data that I want to get
Item_1
-- comment_1
-- comment_2
-- comment_3
Item_2
-- comment_4
-- comment_5
One approach would be to use a CTE (Common Table Expression) if you're on SQL Server 2005 and newer (you aren't specific enough in that regard).
With this CTE, you can partition your data by some criteria - i.e. your ItemId - and have SQL Server number all your rows starting at 1 for each of those "partitions", ordered by some criteria.
So try something like this:
;WITH ItemsAndComments AS
(
SELECT
i.ItemId, i.Title, i.Description, i.Price,
c.CommentId, c.CommentText,
ROW_NUMBER() OVER(PARTITION BY i.ItemId ORDER BY c.CommentId) AS 'RowNum'
FROM
dbo.Items i
LEFT OUTER JOIN
dbo.Comments c ON c.ItemId = i.ItemId
WHERE
......
)
SELECT
ItemId, Title, Description, Price,
CommentId, CommentText
FROM
ItemsAndComments
WHERE
RowNum <= 3
Here, I am selecting up to three entries (i.e. comments) for each "partition" (i.e. for each item) - ordered by the CommentId.
Does that approach what you're looking for??
You can write a single stored procedure which calls GetAllItems and GetTopThreeeCommentsByItemId, takes results in temp tables and join those tables to produce the single resultset you need.
If you do not have a chance to use a stored procedure, you can still do the same by running a single SQL script from data access tier, which calls GetAllItems and GetTopThreeeCommentsByItemId and takes results into temp tables and join them later to return a single resultset.
This gets two elder brother using OUTER APPLY:
select m.*, elder.*
from Member m
outer apply
(
select top 2 ElderBirthDate = x.BirthDate, ElderFirstname = x.Firstname
from Member x
where x.BirthDate < m.BirthDate
order by x.BirthDate desc
) as elder
order by m.BirthDate, elder.ElderBirthDate desc
Source data:
create table Member
(
Firstname varchar(20) not null,
Lastname varchar(20) not null,
BirthDate date not null unique
);
insert into Member(Firstname,Lastname,Birthdate) values
('John','Lennon','Oct 9, 1940'),
('Paul','McCartney','June 8, 1942'),
('George','Harrison','February 25, 1943'),
('Ringo','Starr','July 7, 1940');
Output:
Firstname Lastname BirthDate ElderBirthDate ElderFirstname
-------------------- -------------------- ---------- -------------- --------------------
Ringo Starr 1940-07-07 NULL NULL
John Lennon 1940-10-09 1940-07-07 Ringo
Paul McCartney 1942-06-08 1940-10-09 John
Paul McCartney 1942-06-08 1940-07-07 Ringo
George Harrison 1943-02-25 1942-06-08 Paul
George Harrison 1943-02-25 1940-10-09 John
(6 row(s) affected)
Live test: http://www.sqlfiddle.com/#!3/19a63/2
marc's answer is better, just use OUTER APPLY if you need to query "near" entities (e.g. geospatial, elder brothers, nearest date to due date, etc) to the main entity.
Outer apply walkthrough: http://www.ienablemuch.com/2012/04/outer-apply-walkthrough.html
You might need DENSE_RANK instead of ROW_NUMBER/RANK though, as the criteria of a comment being a top could yield ties. TOP 1 could yield more than one, TOP 3 could yield more than three too. Example of that scenario(DENSE_RANK walkthrough): http://www.anicehumble.com/2012/03/postgresql-denserank.html
Its better that you select the statement by using the row_number statement and select the top 3 alone
select a.* from
(
Select *,row_number() over(partition by column)[dup]
) as a
where dup<=3

Using Row_Number() Over (Order By Column) When Column is in Different DB Returns Error

I am submitting the following query in Sql Server (2008)
WITH query AS (SELECT TOP 100 PERCENT
ROW_NUMBER() OVER
(ORDER BY [tbl2].[col2] ASC) AS TableRowNumber ,
[tbl1].[col1] ,
[tbl2].[col2]
FROM [db1].[dbo].[tbl1] AS [tbl1]
JOIN [db2].[dbo].[tbl2] AS [tbl2]
ON [tbl1].[id] = [tbl2].[id])
SELECT
*
FROM query
WHERE TableRowNumber BETWEEN 1 AND 15
ORDER BY TableRowNumber ASC
When this query is run, it returns the following error message:
Msg 207, Level 16, State 1, Line 3
Invalid column name 'col2'.
The sql join itself runs fine (when run as a normal query. The issue seems to be with using the Row_Number() over (Order By COLUMN) when COLUMN is in a different database from the FROM table of the query.
If I would change line 3 to read (Order By [tbl1].[col1] ASC) then it runs without any issues. The error only happens when the sort column is in a different DB.
Does anyone know why this is happening? Any suggestions on how to fix this?
This works for me no problem:
SELECT a.name, b.object_id, rn = ROW_NUMBER() OVER (ORDER BY b.object_id DESC)
FROM sys.columns AS a
INNER JOIN tempdb.sys.objects AS b
ON a.object_id = b.object_id;
So I suspect there is some other issue going on (e.g. col2 really doesn't exist). Also I noticed that you are calling the thing tb2 and tbl2 - is it possible you have both a tb2 and a tbl2 in the other database, and you're referencing the wrong one?
EDIT I created this:
CREATE DATABASE db1;
GO
USE db1;
GO
CREATE TABLE dbo.tbl1(ID INT, col1 INT);
GO
INSERT dbo.tbl1 SELECT 1, 5
UNION ALL SELECT 2, 10;
GO
CREATE DATABASE db2;
GO
USE db2;
GO
CREATE TABLE dbo.tbl2(ID INT, col2 INT);
GO
INSERT dbo.tbl2 SELECT 1, 9
UNION ALL SELECT 2, 4;
GO
USE db1;
GO
Then ran your query in the context of db1. It ran fine. So for the last time I will suggest that there is something you're not telling us about the schema, or perhaps the fact that you've obfuscated the names (and already had to correct one typo from doing so) has obfuscated something too much even for you...

Create SQL job that verifies daily entry of data into a table?

Writing my first SQL query to run specifically as a SQL Job and I'm a little out of my depth. I have a table within a SQL Server 2005 Database which is populated each day with data from various buildings. To monitor the system better, I am attempting to write a SQL Job that will run a query (or stored procedure) to verify the following:
- At least one row of data appears each day per building
My question has two main parts;
How can I verify that data exists for each building? While there is a "building" column, I'm not sure how to verify each one. I need the query/sp to fail unless all locations have reported it. Do I need to create a control table for the query/sp to compare against? (as the number of building reporting in can change)
How do I make this query fail so that the SQL Job fails? Do I need to wrap it in some sort of error handling code?
Table:
Employee RawDate Building
Bob 2010-07-22 06:04:00.000 2
Sally 2010-07-22 01:00:00.000 9
Jane 2010-07-22 06:04:00.000 12
Alex 2010-07-22 05:54:00.000 EA
Vince 2010-07-22 07:59:00.000 30
Note that the building column has at least one non-numeric value. The range of buildings that report in changes over time, so I would prefer to avoid hard-coding of building values (a table that I can update would be fine).
Should I use a cursor or dynamic SQL to run a looping SELECT statement that checks for each building based on a control table listing each currently active building?
Any help would be appreciated.
Edit: spelling
You could create a stored procedure that checks for missing entries. The procedure could call raiserror to make the job fail. For example:
if OBJECT_ID('CheckBuildingEntries') is null
exec ('create procedure CheckBuildingEntries as select 1')
go
alter procedure CheckBuildingEntries(
#check_date datetime)
as
declare #missing_buildings int
select #missing_buildings = COUNT(*)
from Buildings as b
left join
YourTable as yt
on yt.Building = b.name
and dateadd(dd,0, datediff(dd,0,yt.RawDate)) =
dateadd(dd,0, datediff(dd,0,#check_date))
where yt.Building is null
if #missing_buildings > 0
begin
raiserror('OMG!', 16, 0)
end
go
An example scheduled job running at 4AM to check yesterday's entries:
declare #yesterday datetime
set #yesterday = dateadd(day, -1, GETDATE())
exec CheckBuildingEntries #yesterday
If an entry was missing, the job would fail. You could set it up to send you an email.
Test tables:
create table Buildings (id int identity, name varchar(50))
create table YourTable (Employee varchar(50), RawDate datetime,
Building varchar(50))
insert into Buildings (name)
select '2'
union all select '9'
union all select '12'
union all select 'EA'
union all select '30'
insert into YourTable (Employee, RawDate, Building)
select 'Bob', '2010-07-22 06:04:00.000', '2'
union all select 'Sally', '2010-07-22 01:00:00.000', '9'
union all select 'Jane', '2010-07-22 06:04:00.000', '12'
union all select 'Alex', '2010-07-22 05:54:00.000', 'EA'
union all select 'Vince', '2010-07-22 07:59:00.000', '30'
Recommendations:
Do use a control table for the buildings - you may find that one
already exists, if you use the Object
Explorer in SQL Server Management
Studio
Don't use a cursor or dynamic SQL to run a loop - use set based
commands instead, possibly something
like the following:
SELECT BCT.Building, COUNT(YDT.Building) Build
FROM dbo.BuildingControlTable BCT
LEFT JOIN dbo.YourDataTable YDT
ON BCT.Building = YDT.Building AND
CAST(FLOOR( CAST( GETDATE() AS FLOAT ) - 1 ) AS DATETIME ) =
CAST(FLOOR( CAST( YDT.RawDate AS FLOAT ) ) AS DATETIME )
GROUP BY BCT.Building

Sql Reporting Services Parameter Question

I'm new with SQL Reporting Services 2008 and cannot find out how to do something that should be simple.
What I have is a single select parameter that is populated with a list of salesman from a query. This works great. And I have the dataset query responding to this parameter working well. So my reports for a single salesman are working fine.
My problem is that I would also like to have an option that is ALL Salesman so that the manager can print out a complete list.
Any suggestions on how to do this.
I usually UNION ALL a custom value to the top of my query with a special value that would indicate to my later query that it should not filter.
I usually use NULL, 0, or '%' for this.
Something like:
SELECT 0 [UserId], '(All Users)' [Username]
UNION ALL
SELECT
[UserId],
[Username]
FROM
dbo.[Users]
And then, in the later query:
SELECT * FROM Data WHERE (UserID = #UserID OR #UserID = 0)
Your sales person query probably looks like this:
SELECT SalesPersonName FROM SalesPerson ORDER BY SalesPersonName ASC
You should change it to this:
SELECT 1 as SortOrder, SalesPersonName FROM SalesPerson
UNION SELECT 0 as SortOrder, 'All' ORDER BY SortOrder, SalesPersonName
Now your query will return:
All
Adam
Barry
Steve
...
Now when you pull your data for the report you can do:
WHERE (SalesPersonName = #SalesPersonName OR #SalesPersonName='All')
And make sure you set the default to 'All' if that is what your manager wants.

Resources