SQL return values if row count > X - sql

DECLARE #sql_string varchar(7000)
set #sql_string = (select top 1 statement from queries where name = 'report name')
EXECUTE (#sql_string)
#sql_string is holding another SQL statement. This query works for me. It returns all the values from the query from the statement on the queries table. From this, I need to figure out how to only return the results IF the number of rows returned exceeds a threshold (for my particular case, 25). Else return nothing. I can't quite figure out how to get this conditional statement to work.
Much appreciated for any direction on this.

If all the queries return the same columns, you could simply store the data in a temporary table or table variable and then use logic such as:
select t.*
from #t t
where (select count(*) from #t) > 25;
An alternative is to try constructing a new query from the existing query. I don't recommend trying to parse the existing string, if you can avoid that. Assuming that the query does not use CTEs or have an ORDER BY clause, for instance, something like this should work:
set #sql = '
with q as (
' + #sql + '
)
select q.*
from q
where (select count(*) from q) > 25
';

That did the trick #Gordon. Here was my final:
DECLARE #report_name varchar(100)
DECLARE #sql_string varchar(7000)
DECLARE #sql varchar(7000)
DECLARE #days int
set #report_name = 'Complex Pass Failed within 1 day'
set #days = 5
set #sql_string = (select top 1 statement from queries where name = #report_name )
set #sql = 'with q as (' + #sql_string + ') select q.* from q where (select count(*) from q) > ' + convert(varchar(100), #days)
EXECUTE (#sql)
Worked with 2 nuances.
The SQL returned could not include an end ";" charicter
The statement cannot include an "order by" statement

Related

Dynamic SQL Procedure with Pivot displaying counts based on Date Range

I have a table which contains multiple user entries.
I want to pull counts of user entries based on date range passed to a stored procedure.
start date: 11/9/2017
end date: 11/11/2017
However the response needs to be dynamic based on amount of days in the date range.
Here is a desired format:
Now that you have provided examples, I have updated my answer which provides you with a solution based on the data you have provided.
Note that you are able to change the date range and the query will update accordingly.
Bare in mind that this SQL query is for SQL Server:
create table #tbl1 (
[UserId] int
,[UserName] nvarchar(max)
,[EntryDateTime] datetime
);
insert into #tbl1 ([UserId],[UserName],[EntryDateTime])
values
(1,'John Doe','20171109')
,(1,'John Doe','20171109')
,(1,'John Doe','20171110')
,(1,'John Doe','20171111')
,(2,'Mike Smith','20171109')
,(2,'Mike Smith','20171110')
,(2,'Mike Smith','20171110')
,(2,'Mike Smith','20171110')
;
-- declare variables
declare
#p1 date
,#p2 date
,#diff int
,#counter1 int
,#counter2 int
,#dynamicSQL nvarchar(max)
;
-- set variables
set #p1 = '20171109'; -- ENTER THE START DATE IN THE FORMAT YYYYMMDD
set #p2 = '20171111'; -- ENTER THE END DATE IN THE FORMAT YYYYMMDD
set #diff = datediff(dd,#p1,#p2); -- used to calculate the difference in days
set #counter1 = 0; -- first counter to be used in while loop
set #counter2 = 0; -- second counter to be used in while loop
set #dynamicSQL = 'select pivotTable.[UserId] ,pivotTable.[UserName] as [Name] '; -- start of the dynamic SQL statement
-- to get the dates into the query in a dynamic way, you need to do a while loop (or use a cursor)
while (#counter1 < #diff)
begin
set #dynamicSQL += ',pivotTable.[' + convert(nvarchar(10),dateadd(dd,#counter1,#p1),120) + '] '
set #counter1 = (#counter1 +1)
end
-- continuation of the dynamic SQL statement
set #dynamicSQL += ' from (
select
t.[UserId]
,t.[UserName]
,cast(t.[EntryDateTime] as date) as [EntryDate]
,count(t.[UserId]) as [UserCount]
from #tbl1 as t
where
t.[EntryDateTime] >= ''' + convert(nvarchar(10),#p1,120) + ''' ' +
' and t.[EntryDateTime] <= ''' + convert(nvarchar(10),#p2,120) + ''' ' +
'group by
t.[UserId]
,t.[UserName]
,t.[EntryDateTime]
) as mainQuery
pivot (
sum(mainQuery.[UserCount]) for mainQuery.[EntryDate]
in ('
;
-- the second while loop which is used to create the columns in the pivot table
while (#counter2 < #diff)
begin
set #dynamicSQL += ',[' + convert(nvarchar(10),dateadd(dd,#counter2,#p1),120) + ']'
set #counter2 = (#counter2 +1)
end
-- continuation of the SQL statement
set #dynamicSQL += ')
) as pivotTable'
;
-- this is the easiet way I could think of to get rid of the leading comma in the query
set #dynamicSQL = replace(#dynamicSQL,'in (,','in (');
print #dynamicSQL -- included this so that you can see the SQL statement that is generated
exec sp_executesql #dynamicSQL; -- this will run the generate dynamic SQL statement
drop table #tbl1;
Let me know if that's what you were looking for.
If you are using MySQL this will make what you want:
SELECT UserID,
UserName,
SUM(Date = '2017-11-09') '2017-11-09',
SUM(Date = '2017-11-10') '2017-11-10',
SUM(Date = '2017-11-11') '2017-11-11'
FROM src
GROUP BY UserID
If you are using SQL Server, you could try it with PIVOT:
SELECT *
FROM
(SELECT userID, userName, EntryDateTime
FROM t) src
PIVOT
(COUNT(userID)
FOR EntryDateTime IN (['2017-11-09'], ['2017-11-10'], ['2017-11-11'])) pvt

Scope of table when using with clause

Below is a piece of my stored proc.
I am getting error as invalid object MyCount, Please let me know where am going wrong
;With MyCount AS
(
Select DispatchToRegionId ,FolderNo, row_number() OVER(ORDER BY FolderNo DESC) as Row
from tblTransite where FolderNo = #VAL group by DispatchToRegionId,FolderNo
)
select #cnt = COUNT(*) from MyCount
if #cnt = 0
begin
set #InvalidFolderNo = #VAL
print 'cnt -' + cast(#cnt as varchar(max) ) + 'invalid folder - ' + cast(#InvalidFolderNo as varchar(max) )
return
end
select #Region =( Select top 1 DispatchToRegionId from MyCount
order by Row desc )
MSDN clearly states the following about the scope of a Common Table Expression (CTE):
A common table expression (CTE) can be thought of as a temporary result set that is defined within the execution scope of a single SELECT, INSERT, UPDATE, DELETE, or CREATE VIEW statement
Once you have run the first select query, you can no longer use the CTE for your next one. You may want to consider storing the data in a temporary table or table variable if you want to access it in multiple queries.
MyCount is available only for the first query after it. Try to select all you need in this one query:
;With MyCount AS
(
Select DispatchToRegionId ,FolderNo, row_number() OVER(ORDER BY FolderNo DESC) as Row
from tblTransite where FolderNo = #VAL group by DispatchToRegionId,FolderNo
)
SELECT
#cnt = (select COUNT(*) from MyCount),
#Region =( Select top 1 DispatchToRegionId from MyCount
order by Row desc )
if #cnt = 0
begin
set #InvalidFolderNo = #VAL
print 'cnt -' + cast(#cnt as varchar(max) ) + 'invalid folder - ' + cast(#InvalidFolderNo as varchar(max) )
return
end

Export data from a non-normalized database

I need to export data from a non-normalized database where there are multiple columns to a new normalized database.
One example is the Products table, which has 30 boolean columns (ValidSize1, ValidSize2 ecc...) and every record has a foreign key which points to a Sizes table where there are 30 columns with the size codes (XS, S, M etc...). In order to take the valid sizes for a product I have to scan both tables and take the value SizeCodeX from the Sizes table only if ValidSizeX on the product is true. Something like this:
Products Table
--------------
ProductCode <PK>
Description
SizesTableCode <FK>
ValidSize1
ValidSize2
[...]
ValidSize30
Sizes Table
-----------
SizesTableCode <PK>
SizeCode1
SizeCode2
[...]
SizeCode30
For now I am using a "template" query which I repeat for 30 times:
SELECT
Products.Code,
Sizes.SizesTableCode, -- I need this code because different codes can have same size codes
Sizes.Size_1
FROM Products
INNER JOIN Sizes
ON Sizes.SizesTableCode = Products.SizesTableCode
WHERE Sizes.Size_1 IS NOT NULL
AND Products.ValidSize_1 = 1
I am just putting this query inside a loop and I replace the "_1" with the loop index:
SET #counter = 1;
SET #max = 30;
SET #sql = '';
WHILE (#counter <= #max)
BEGIN
SET #sql = #sql + ('[...]'); -- Here goes my query with dynamic indexes
IF #counter < #max
SET #sql = #sql + ' UNION ';
SET #counter = #counter + 1;
END
INSERT INTO DestDb.ProductsSizes EXEC(#sql); -- Insert statement
GO
Is there a better, cleaner or faster method to do this? I am using SQL Server and I can only use SQL/TSQL.
You can prepare a dynamic query using the SYS.Syscolumns table to get all value in row
DECLARE #SqlStmt Varchar(MAX)
SET #SqlStmt=''
SELECT #SqlStmt = #SqlStmt + 'SELECT '''+ name +''' column , UNION ALL '
FROM SYS.Syscolumns WITH (READUNCOMMITTED)
WHERE Object_Id('dbo.Products')=Id AND ([Name] like 'SizeCode%' OR [Name] like 'ProductCode%')
IF REVERSE(#SqlStmt) LIKE REVERSE('UNION ALL ') + '%'
SET #SqlStmt = LEFT(#SqlStmt, LEN(#SqlStmt) - LEN('UNION ALL '))
print ( #SqlStmt )
Well, it seems that a "clean" (and much faster!) solution is the UNPIVOT function.
I found a very good example here:
http://pratchev.blogspot.it/2009/02/unpivoting-multiple-columns.html

Pass EXEC statement to APPLY as a parameter

I have a need to grab data from multiple databases which has tables with the same schema. For this I created synonyms for this tables in the one of the databases. The number of databases will grow with time. So, the procedure, which will grab the data should be flexible. I wrote the following code snippet to resolve the problem:
WHILE #i < #count
BEGIN
SELECT #synonymName = [Name]
FROM Synonyms
WHERE [ID] = #i
SELECT #sql = 'SELECT TOP (1) *
FROM [dbo].[synonym' + #synonymName + '] as syn
WHERE [syn].[Id] = tr.[Id]
ORDER BY [syn].[System.ChangedDate] DESC'
INSERT INTO #tmp
SELECT col1, col2
FROM
(
SELECT * FROM TableThatHasRelatedDataFromAllTheSynonyms
WHERE [Date] > #dateFrom
) AS tr
OUTER APPLY (EXEC(#sql)) result
SET #i = #i + 1
END
I also appreciate for any ideas on how to simplify the solution.
Actually, it's better to import data from all tables into one table (maybe with additional column for source table name) and use it. Importing can be performed through SP or SSIS package.
Regarding initial question - you can achieve it through TVF wrapper for exec statement (with exec .. into inside it).
UPD: As noticed in the comments exec doesn't work inside TVF. So, if you really don't want to change DB structure and you need to use a lot of tables I suggest to:
OR select all data from synonym*** table into variables (as I see you select only one row) and use them
OR prepare dynamic SQL for complete statement (with insert, etc.) and use temporary table instead of table variable here.
My solution is quite simple. Just to put all the query to the string and exec it. Unfortunately it works 3 times slower than just copy/past the code for all the synonyms.
WHILE #i < #count
BEGIN
SELECT #synonymName = [Name]
FROM Synonyms
WHERE [ID] = #i
SELECT #sql = 'SELECT col1, col2
FROM
(
SELECT * FROM TableThatHasRelatedDataFromAllTheSynonyms
WHERE [Date] > ''' + #dateFrom + '''
) AS tr
OUTER APPLY (SELECT TOP (1) *
FROM [dbo].[synonym' + #synonymName + '] as syn
WHERE [syn].[Id] = tr.[Id]
ORDER BY [syn].[System.ChangedDate] DESC) result'
INSERT INTO #tmp
EXEC(#sql)
SET #i = #i + 1
END

Calling table-valued-function for each result in query

Say I had a query like this:
SELECT X FROM Table WHERE Y = 'Z'
How could I execute a Stored Procedure using each X from the above query as the parameter?
UPDATE
I have changed the SP to be a Table-valued function instead. So for each call to the function it will return a table. What I need to do is store all these results in perhaps a temp table and have my SP return this table.
SOLUTION
Finally managed to get this to work with some help from #cyberkiwi. Here is my final solution:
DECLARE #Fields TABLE (
Field int)
INSERT INTO #Fields (X) SELECT * FROM tvf_GetFields(#SomeIdentifier)
SELECT * FROM #Fields
CROSS APPLY dbo.tvf_DoSomethingWithEachField([#Fields].Field)
You can generate a batch statement out of it and then EXEC it
DECLARE #sql nvarchar(max)
SELECT #sql = coalesce(#sql + ';', '')
+ 'exec sprocname ' + QuoteName(AField, '''')
FROM Table
WHERE AField2 = 'SomeIdentifier'
AND AField is not null
EXEC (#sql)
Before the edit (to TVF), you could have changed the SP to continue to populate a temp table.
Post-edit to TVF, you can use a cross apply:
SELECT F.*
FROM Tbl CROSS APPLY dbo.TVFName(Tbl.AField) F
WHERE Tbl.AField2 = 'SomeIdentifier'
Which returns all the "table results" from each invocation of Tbl.AField into a single result set