I have a legacy VB6 application that's connected to SQL Server where all data is stored. I'm creating a report for the user where I'll need to pass 2 dates (FROM and TO). In SQL Server, I have created a few views that fulfill all my criteria and use the data from there to populate a dbo.tblTempTable (which is not actually #tempTable, but a normal table). For now the dates are hardcoded, and I'm struggling with finding a way of passing those dates. I'm pretty sure I am unable to pass parameters to a view. To populate the table I have a simple stored procedure that goes like this....
TRUNCATE TABLE dbo.tbl_TTableReport
INSERT INTO tbl_TTableReport (UserID, CompanyID, CompanyName, Sold, Voided, Returned, Subtotal)
SELECT
1234 AS USERID, CompanyID, CompanyName,
Sold, Voided, Returned, Subtotal
FROM dbo.vCompanyInfo
So in this select statement, every piece of data comes from my final view ..vCompanyInfo. Sold, Voided, and Returned need to be filtered by the FROM and TO dates. How would I do something like that?
EDITED. I'm trying to create a stored procedure but I'm getting some weird errors...I have never create a complex multi SELECT statement SP before so I'm still trying to work out the kinks.
This is me trynig to build a SP with select statement, but I keep seeing the same error : Subquery returned more than 1 value. This is not permitted when the subquery follows =, !=, <, <= , >, >= or when the subquery is used as an expression.
TRUNCATE TABLE dbo.tbl_TTableReport
INSERT INTO tbl_TTableReport
(UserID
, CompanyID)
, CompanyName)
SELECT 1234 AS USERID
,(Select CompanyID from dbo.tblMainInv)
,(select CompanyName from dbo.tblCompanies)
For both the CompanyID and companyName there are about 30 values. I'm trying to have them get loaded into the table, I don't know where my error is.
I recommend you create an ADP Access project to connect to your SQL Server database. This allows you more control over the queries that you send to the Sql Server for reports and data entry forms. When creating the project file you designate the server and database with which you need to connect to. Then setting up the data source for your report is as simple as including the select statement to issue against the database into the data source of the report. You can also refer to text boxes on forms or have automatic prompts for parameters.
As for the error you are getting in your second query in your edit... you are using the sub-query as an expression. SQL Server is being nice in letting you use a sub-query as one of the values to output in your select statement, but it's expecting just that... one value. So you could do something like:
Select
(select count(*) from MyTableValues) as CountTotal,
(select sum(*) from MyTableValues) as SumTotal
However, what you are trying to do is return several rows when SQL Server is expecting you to return just one. If you want to join the data of two tables, you use a join... something like:
SELECT 1234 AS USERID, CompanyID, CompanyName
from dbo.tblMainInv
join dbo.tblCompanies
on tblMainInv.CompanyID = tblCompanies.CompanyID
Which is probably something that your view dbo.vCompanyInfo probably already looks like.
If your determined to use your temp table approach (although I highly recommend you reconsider as your just making more work for future maintenance)... this is something that your procedure might look like:
GO
CREATE PROCEDURE sp_LoadReportData
(
#UserID as int,
#StartDate as datetime,
#EndDate as datetime
) AS
delete from tbl_TTableReport
where USERID = #UserID
INSERT INTO tbl_TTableReport
(UserID, CompanyID, CompanyName, Sold, Voided, Returned, Subtotal)
SELECT
#UserID AS USERID,
CompanyID,
CompanyName,
Sold,
Voided,
Returned,
Subtotal
FROM vCompanyInfo
where
Sold between #StartDate and #EndDate
and Voided between #StartDate and #EndDate
and Returned between #StartDate and #EndDate
GO
You would then call the procedure with the filters you want and send the UserID as a parameter like this:
exec sp_LoadReportData 1234, '1/1/2016', '1/20/2016'
But this still doesn't solve your problem... because you'll still have to send the UserID as a parameter in your report's data source like:
select * from tbl_TTableReport where UserID=1234
Unless you want to create a table and procedure for every user, you should really rethink your strategy and learn how to do it the standard way.
Related
In a T-SQL stored procedure, when supplied with two tables each of which has the same number of rows, how can I pair-wise match the rows based on row order rather than a join criteria?
Basically, an equivalent of .NET's IEnumerable.Zip() method?
I'm using SQL Server 2016.
Background
The purpose of the stored procedure is to act as an integration adapter between two other applications. I do not control the source code for either application.
The "client" application contains extensibility objects which can be configured to invoke a stored procedure in an SQL Server database. The configuration options for the extensibility point allow me to name a stored procedure which will be invoked, and provide a statically configured list of named parameters and their associated values, which will be passed to the stored procedure. Only scalar parameters are supported, not table-valued parameters.
The stored procedure needs to collect data from the "server" application (which is exposed through an OLE-DB provider) and transform it into a suitable result set for consumption by the client application.
For maintenance reasons, I want to avoid storing any configuration in the adapter database. I want to write generic, flexible logic in the stored procedure, and pass all necessary configuration information as parameters to that stored procedure.
The configuration information that's needed for the stored procedure is, essentially, equivalent to the following table variable schema:
DECLARE #TableOfServerQueryParameterValues AS TABLE (
tag NVARCHAR(50),
filterexpr NVARCHAR(500)
)
This table can then be used as the left-hand side of JOIN and CROSS APPLY queries in the stored proc which are run against the "server" application interfaces.
The problem I encountered is that I did not know of any way of passing a table of parameter info from the client application, because its extensibility points only include scalar parameter support.
So, I thought I would pass two scalar parameters. One would be a comma-separated list of tag values. The other would be a comma-separated list of filterexpr values.
Inside the stored proc, it's easy to use STRING_SPLIT to convert each of those parameters into a single-column table. But then I needed to match the two columns together into a two-column table, which I could then use as the basis for INNER JOIN or CROSS APPLY to query the server application.
The best solution I've come up with so far is selecting each table into a table variable and use the ROW_NUMBER() function to assign a row number, and then join the two tables together by matching on the extra ROW_NUMBER column. Is there an easier way to do it than that? It would be nice not to have to declare all the columns in the table variables.
Your suggestion of using row_number seems sound.
Instead of table variables you can use subqueries or CTEs; there should be little difference overall, though avoiding the table variable reduces the number of passes you need to make & avoids the additional code to maintain.
select a.*, b.* --specify whatever columns you want to return
from (
select *
, row_number() over (order by someArbitraryColumnPreferablyYourClusteredIndex) r
from TableA
) a
full outer join --use a full outer if your have different numbers of rows in the tables & want
--results from the larger table with nulls from the smaller for the bonus rows
--otherwise use an inner join to only get matches for both tables
(
select *
, row_number() over (order by someArbitraryColumnPreferablyYourClusteredIndex) r
from TableA
) b
on b.r = a.r
Update
Regarding #PanagiotisKanavos's comment on passing structured data, here's a simple example of how you could convert a value passed as an xml type to table data:
declare #tableA xml = '<TableA>
<row><col1>x</col1><col2>Anne</col2><col3>Droid</col3></row>
<row><col1>y</col1><col2>Si</col2><col3>Borg</col3></row>
<row><col1>z</col1><col2>Roe</col2><col3>Bott</col3></row>
</TableA>'
select row_number() over (order by aRow) r
, x.aRow.value('(./col1/text())[1]' , 'nvarchar(32)') Code
, x.aRow.value('(./col2/text())[1]' , 'nvarchar(32)') GivenName
, x.aRow.value('(./col3/text())[1]' , 'nvarchar(32)') Surname
from #tableA.nodes('/*/*') x(aRow)
You may get a performance boost over the above by using the following. This creates a dummy column allowing us to do an order by where we don't care about the order. This should be faster than the above as ordering by 1 will be simpler than sorting based on the xml type.
select row_number() over (order by ignoreMe) r
, x.aRow.value('(./col1/text())[1]' , 'nvarchar(32)') Code
, x.aRow.value('(./col2/text())[1]' , 'nvarchar(32)') GivenName
, x.aRow.value('(./col3/text())[1]' , 'nvarchar(32)') Surname
from #tableA.nodes('/*/*') x(aRow)
cross join (select 1) a(ignoreMe)
If you do care about the order, you can order by the data's fields, as such:
select row_number() over (order by x.aRow.value('(./col1/text())[1]' , 'nvarchar(32)') ) r
, x.aRow.value('(./col1/text())[1]' , 'nvarchar(32)') Code
, x.aRow.value('(./col2/text())[1]' , 'nvarchar(32)') GivenName
, x.aRow.value('(./col3/text())[1]' , 'nvarchar(32)') Surname
from #tableA.nodes('/*/*') x(aRow)
I have searched for paging in SQL Server. I found most of the solution look like that
What is the best way to paginate results in SQL Server
But it don't meet my expectation.
Here is my situation:
I work on JasperReport, for that: to export the report I just need pass the any Select query into the template, it will auto generated out the report
EX : I have a select query like this:
Select * from table A
I don't know any column names in table A. So I can't use
Select ROW_NUMBER() Over (Order By columsName)
And I also don't want it order by any columns.
Anyone can help me do it?
PS: In Oracle , it have rownum very helpful in this case.
Select * from tableA where rownum > 100 and rownum <200
Paging with Oracle
You should use ROW_NUMBER with an ORDER BY - because without an ORDER BY there is no determinism in how rows are returned. You can run the same query three times and get the results back in three different orders. Especially if merry-go-round scans come into play.
So unless you want your report to have the possibility of showing the same rows to users on multiple pages, or some rows never on any page, you need to find a way to order the result set to make it deterministic.
From my opinion, you can use sql query to find out how many columns in a table, and then find out a proper one for ' order by ' to depend on.
The script of how to get out columns of an table refer to : How can I get column names from a table in SQL Server?
Check out this link
http://msdn.microsoft.com/en-us/library/ms186734.aspx
SQL Server has similar function ROW_NUMBER. Though it behaves a bit differently.
SQL Server provides no guarantee of row order unless you have have specified a column in order by clause. I would recommend that you give an order by clause that has unique values.
Thank for all your help. Because of order by are required when paging in MS SQL Server, so I used ResultSetMetaData to get the Columns name and do paging as well.
You can use the below query aswell.
declare #test table(
id int,
value1 varchar(100),
value2 int)
insert into #test values(1,'10/50',50)
insert into #test values(2,'10/60',60)
insert into #test values(3,'10/60',61)
insert into #test values(4,'10/60',10)
insert into #test values(5,'10/60',11)
insert into #test values(6,'10/60',09)
select *
from ( select row_number() over (order by (select 0)) as rownumber,* from #test )test
where test.rownumber<=5
Please consider the below example:
CREATE VIEW VW_YearlySales
AS
SELECT 2011 AS YearNo, ProductID, SUM(Amount) FROM InvoiceTable2011
UNION ALL
SELECT 2012 AS YearNo, ProductID, SUM(Amount) FROM InvoiceTable2012
UNION ALL
SELECT 2013 AS YearNo, ProductID, SUM(Amount) FROM InvoiceTable2013
GO
The InvoiceTable2013 doesn't exist actually and I don't want to create it right now, it will be created automatically when recording the first invoice for year 2013.
Can anyone help me on how to specify a condition that will verify the existence of the table before doing the UNION ALL?
Many thanks for your help.
As others have correctly said, you can't achieve this with a view, because the select statement has to reference a concrete set of tables - and if any of them don't exist, the query will fail to execute.
It seems to me like your problem is more fundamental. Clearly there should conceptually be exactly one InvoiceTable, with rows for different dates. Separating this out into different logical tables by year is presumably something that's been done for optimisation (unless the columns are different, which I very much doubt).
In this case, partitioning seems like the way to remedy this problem (partitioning large tables by year/quarter/month is the canonical example). This would let you have a single InvoiceTable logically, yet specify that SQL Server should store the data behind the scenes as if it were different tables split out by year. You get the best of both worlds - an accurate model, and fast performance - and this makes your view definition simple.
No, according to my knowledge its not possible in view, you have to use Stored Procedure. In Stored Procedure you can validate table existance & based on the existance of that table you can change your SQL.
EDIT:
CREATE PROCEDURE GetYearlySales
AS
IF (EXISTS (SELECT *
FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_NAME = 'InvoiceTable2013'))
BEGIN
SELECT 2011 AS YearNo, ProductID, SUM(Amount) FROM InvoiceTable2011
UNION ALL
SELECT 2012 AS YearNo, ProductID, SUM(Amount) FROM InvoiceTable2012
UNION ALL
SELECT 2013 AS YearNo, ProductID, SUM(Amount) FROM InvoiceTable2013
END
ELSE
BEGIN
SELECT 2011 AS YearNo, ProductID, SUM(Amount) FROM InvoiceTable2011
UNION ALL
SELECT 2012 AS YearNo, ProductID, SUM(Amount) FROM InvoiceTable2012
END
Looks like you want to have a table for every year and you want to ensure that you have the query for SP without modifying the SP. This is slightly risky , you will have to maintain the naming conventions all the time. In this case what you will have to do is query the informationschema tables for table_name like 'InvoiceTable%'. Get the records in a table and then loop through the records attaching the fixed SQL. And then execute the dynamic sql like its done here http://www.vishalseth.com/post/2008/07/10/Dynamic-SQL-sp_executesql.aspx
I'm creating a stored procedure in SQL server and want to achieve the following:
I have to look up all the ResourceID's (could be any number of resources) for a certain ClientID in the Resources table.
Then I have to insert a row in the Planning table for each ResourceID found.
So I'll have something like
WHILE EXISTS (SELECT ResourceID AS #ResourceID FROM Resources WHERE ClientID = #ClientID)
BEGIN
INSERT INTO Planning (ResourceID, Date)
VALUES (#ResourceID, #Date)
NEXT
END
I have already found that using a cursor might be the way, but that doesn't lead me to anything usable
No need for loops:
INSERT INTO Planning (ResourceID, Date)
SELECT ResourceID, GETDATE()
FROM Resources
WHERE ClientID = #ClientID
In general, good practice is to avoid loops/cursors in SQL. Use set based options whenever possible.
No need for a cursor. Keep it set-based:
insert into planning (resourceid, date)
select resourceid, #SomeDate
from resources
where clientid = #ClientId
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