SQL Subqueries concatenation issue - sql-server

I have a simple subquery that works fine
SELECT id, Name, subset
, (select count (1) from anotherTable where qid = someTable.id )
FROM someTable
the value "subset" is a string ie:
"and (PreQ1 like '%A%') and ( PreQ2 like '%A%' or PreQ2 like '%C%')"
And so I'd like to ConCatenate the subquery with the value subset like this:
SELECT id, Name, subset
, exec ( 'select count (1) from anotherTable where qid = someTable.id ' + subset)
FROM someTable
.. but get a "Conversion failed" error

Is this what you want?
SELECT s.id, s.Name, s.subset,
(select cast(count(1) as varchar(255)) + s.subset
from anotherTable a
where a.qid = s.id
)
FROM someTable s;
This does the concatenation within the subquery. You can also put the + s.subset outside the subquery.
Also note that table aliases make the query easier to write and to read.

Related

Getting a Row Count and keeping the columns with no rows are returned

If I run something like this:
select [agent_name], [agent_department], count(*) as [row_count]
from [table_name]
where [agent_name] IS NOT NULL
group by [agent_name] [agent_department];
Nothing will come back if there are no records to return (i.e. the table is empty).
If I run this
select count(*) as [row_count]
from [table_name]
where [agent_name] IS NOT NULL
I will get a row_count of 0.
Is there a way I can run the first query, and, if there are no records, have it return row_count 0?
This might not be very beautiful, but it should bring back what you want:
I start with a tiny mockup:
DECLARE #mockup TABLE(agent_name varchar(100),agent_department varchar(100));
--The query will read your SELECT within a CTE.
WITH cte AS
(
select [agent_name], [agent_department], count(*) as [row_count]
from #mockup
where [agent_name] IS NOT NULL
group by [agent_name],[agent_department]
)
SELECT agent_name,agent_department,row_count FROM cte
UNION ALL SELECT NULL,NULL,0 WHERE (SELECT COUNT(*) FROM cte)=0;
The result
agent_name agent_department row_count
NULL NULL 0
You see, that the resultset is called as is, while there is a UNION ALL SELECT query, which will deliver only in cases, where the cte has no rows.
Now we insert some data to the table
INSERT INTO #mockup VALUES('blah','blub');
WITH cte AS
(
select [agent_name], [agent_department], count(*) as [row_count]
from #mockup
where [agent_name] IS NOT NULL
group by [agent_name],[agent_department]
)
SELECT agent_name,agent_department,row_count FROM cte
UNION ALL SELECT NULL,NULL,0 WHERE (SELECT COUNT(*) FROM cte)=0;
the new result is now
agent_name agent_department row_count
blah blub 1

How to create a 'Like' + 'In' query in SQL Server (SSMS)

I'm looking a way to create a query that includes the functionality of both: 'Like' and 'In' keywords together. So with this I'll be able to make a search of multiple sub-strings in one sentence instead of creating multiple consults:
i.e:
Instead of this:
select * from MyTable where ColumnName like'%substring1%'
select * from MyTable where ColumnName like'%substring2%'
select * from MyTable where ColumnName like '%substring3%'
I want to do something like this:
select * from MyTable where ColumnName in ('%substring1%','%substring2%','%substring3%')
Just to clarify: the wild card (%) is not allowed or it doesn't work together with the "in" keyword so it forces me to look for full strings instead of substrings.
Any idea or approach?
The only approach I can think of is the following:
select * from MyTable where ColumnName like '%substring1%' or ColumnName like '%substring2%' or ColumnName like '%substring3%'
If you have additional conditions, just group it.
select * from MyTable where (ColumnName like '%substring1%' or ColumnName like '%substring2%' or ColumnName like '%substring3%') and AnotherColumn='string'
Pre-populate a temp table with the terms you want to search (or table variable, CTE, or whatever). Then join but use like instead of an equalty operator.
select *
from myTable a
inner join #terms b
on a.myField like '%' + b.term + '%'
Yet another option is pass a delimited string
Example
Declare #Search varchar(max) = 'John,Cappelletti,Some Other Value'
Select Distinct A.*
From MyTable A
Join (
Select RetSeq = Row_Number() over (Order By (Select null))
,RetVal = LTrim(RTrim(B.i.value('(./text())[1]', 'varchar(max)')))
From (Select x = Cast('<x>' + replace(#Search,',','</x><x>')+'</x>' as xml).query('.')) as A
Cross Apply x.nodes('x') AS B(i)
) B on charindex(RetVal,ColumnName)>0
Why not just use or operator;
SELECT ColumnName
FROM MyTable
WHERE ColumnName LIKE '%substring1%' OR ColumnName LIKE '%substring2%' OR ColumnName LIKE '%substring3%'

Stored Procedure Syntax with CTE

This is probably trivial but I am just learning about CTE (thanks to help here).
I have a procedure that is used to determine totals.
The first part is the totals are the sum of a position at their level an below. So I needed a way to retrieve records that (1) determined the level of the record (hierarchy) and (2) returned all records at and below. That was asked and answered here.
Now want to take the CTE table from the answer above and use it in the second part of my procedure (get the totals)
CREATE PROCEDURE [dbo].[GetProgramTotals]
#programId nvarchar(10) = null,
#owner int = null,
#totalAmount money OUT,
#usedAmount money OUT,
#remainingAmount money OUT
AS
BEGIN
WITH rCTE AS
(
SELECT
*, 0 AS Level
FROM Forecasting.dbo.Addressbook
WHERE Addressbook = #owner
UNION ALL
SELECT
t.*, r.Level + 1 AS Level
FROM Addressbook t
INNER JOIN rCTE r ON t.ParentAddressbook = r.Addressbook
)
Select #totalAmount = (Select Sum(Amount) from dbo.Budget where
(#programId IS NULL or (ProgramId = #programId)) and (#owner IS NULL or (BudgetOwner in (SELECT Addressbook from rCTE))))
Select #usedAmount = (Select Sum(SubTotal) from dbo.OrderLine where
(#programId IS NULL or (ProgramId = #programId) and (#owner IS NULL) or (Budget in (SELECT Addressbook from rCTE))))
if (#totalAmount is null)
set #totalAmount = 0
if (#usedAmount is null)
set #usedAmount = 0
Set #remainingAmount = (#totalAmount - #usedAmount)
END
The idea of this procedure is the dynamically calculate an individual (or all) programs based an a users position in a hierarchy.
So a regional managers totals would be the sum of all districts and district reps.
UPDATE: I updated this based on squillman (thank you) comment below.
Now I have a different problem. When I execute the proc - I get 'Invalid object name rCTE'.
You can't use SET in the middle of a query like that. Change it to a SELECT and it should remedy your syntax error.
CREATE PROCEDURE [dbo].[GetProgramTotals]
#programId nvarchar(10) = null,
#owner int = null,
#totalAmount money OUT,
#usedAmount money OUT,
#remainingAmount money OUT
AS
BEGIN
WITH rCTE AS(
SELECT *, 0 AS Level FROM Forecasting.dbo.Addressbook WHERE Addressbook = #owner
UNION ALL
SELECT t.*, r.Level + 1 AS Level
FROM Addressbook t
INNER JOIN rCTE r ON t.ParentAddressbook = r.Addressbook)
SELECT #totalAmount = (Select Sum(Amount) from dbo.Budget where
(#programId IS NULL or (ProgramId = #programId)) and (#owner IS NULL or (BudgetOwner in (SELECT Addressbook from rCTE))))
, #usedAmount = (Select Sum(SubTotal) from dbo.OrderLine where
(#programId IS NULL or (ProgramId = #programId) and (#owner IS NULL) or (Budget in (SELECT Addressbook from rCTE))))
if (#totalAmount is null)
set #totalAmount = 0
if (#usedAmount is null)
set #usedAmount = 0
Set #remainingAmount = (#totalAmount - #usedAmount)
END
CTE's can be a bit confusing at first, but they are really quite simple once they make sense. For me it clicked when I began thinking of them as just another temp table syntax (pro-tip: they're not in reality, just conceptually). So basically:
Create one or more "temp tables". These are your CTE expressions, and there can be more than one.
Perform a standard operation using one or more of the CTE expressions in the statement immediately following your CTE(s).
As Martin mentioned in comments below, the CTE(s) are only scoped for the next immediate statement and fall out of scope after that.
So,
;WITH cte1 AS
(
SELECT Col1 FROM Table1
),
cte2 AS
(
SELECT Col1 FROM Table2
)
SELECT Col1 FROM cte1 //In scope here
UNION
SELECT Col1 FROM cte1; //Still in scope since we're still in the first statement
SELECT Col1 FROM cte1; //Fails. cte1 is now out of scope (as is cte2)
In your case you're using the recursive CTE to form a parent/child hierarchy and then setting variables based on the results. Your CTE syntax is pretty close after the edit, you just need the comma to bring things back together into one statement.
//Variable assignment example
;WITH cte1 AS
(
SELECT Col1 FROM Table1
),
cte2 AS
(
SELECT Col1 FROM Table2
)
SELECT #var1 = (SELECT TOP 1 Col1 FROM cte1)
,#var2 = (SELECT TOP 1 Col1 FROM cte2) //You're missing the comma at the start of this line
Change Select #usedAmount=... to , #usedAmount=...

LIKE operator with a variable number of search conditions, in T-SQL

Is there a short way to look for multiple matches with the LIKE operator, using AND clauses?
I should do it dynamically, with a variable number of terms. Obtaining it statically (with a fixed number of terms) is a no-brainer:
SELECT *
from MyTable
WHERE MyColumn LIKE "%AAA%"
AND MyColumn LIKE "%BBB%"
AND MyColumn LIKE "%CCC%"
Let's assume there is a table variable that contains an unknown number of terms:
DECLARE #Terms table
(
Term nvarchar(500)
)
Is there a way to perform the LIKE statement on MyColumn matching all the items in #Terms?
DECLARE #Terms TABLE
(
WildCards VARCHAR(20)
)
INSERT INTO #Terms
( WildCards )
VALUES
( 'CO' ),
( 'DO' ),
( 'EO' )
DECLARE #FoundAll INT
SELECT #FoundAll = Count(*) FROM #Terms
SELECT mt.MyColumn, COUNT(*), #FoundAll FROM MyTable mt
OUTER APPLY
(
SELECT WildCards FROM #Terms
) d
WHERE mt.MyColumn LIKE ('%' + d.WildCards + '%')
GROUP BY mt.MyColumn
HAVING COUNT(*) = #FoundAll
This will only pull the record which matches ALL of the possible likes.
SQL Fiddle:
select mt.*
from MyTable mt
join #Terms t
on mt.MyColumn like '%' + t.Term + '%'
group by mt.MyColumn
having COUNT(*) = (select COUNT(*) from #Terms)
If you can get away with not using the LIKE then the following would work:
SELECT *
FROM MyTable
WHERE MyColumn IN
(
SELECT Term
FROM Terms
)
Or, how about the following (SQL Fiddle):
SELECT *
FROM MyTable
INNER JOIN Terms
ON MyTable.MyColumn LIKE '%' + Terms.Term + '%'
Try this:
SELECT MT.*
FROM MyTable AS MT INNER JOIN
#Terms AS TR ON CHARINDEX(TR.Term, MT.MyColumn) > 0
GROUP BY MT.MyColumn
HAVING COUNT(*) = (SELECT COUNT(*) FROM Terms)

Get more than 1 result set for recursive CTE?

I have a simple table which has leafs and sub leafs info. ( like a forum questions)
A main message is defined where childId and ParentID are the same
So here we see 2 main questions and their answers.
I've also managed to calc the depth of each element :
In short this is the main query :
WITH CTE AS
(
SELECT childID
,parentID,
0 AS depth,name
FROM #myTable
WHERE childID = parentID AND childID=1 -- problem line
UNION ALL
SELECT TBL.childID
,TBL.parentID,
CTE.depth + 1 , TBL.name
FROM #myTable AS TBL
INNER JOIN CTE ON TBL.parentID = CTE.childID
WHERE TBL.childID<>TBL.parentID
)
SELECT childID,parentID,REPLICATE('----', depth) + name
But the problem is Line #8 (commented).
I currently ask "give me all the cluster for question id #1"
So where is the problem ?
I want to have multiple result set , for each question !
so here i need to have 2 result sets :
one for childId=parentId=1
and one for
one for childId=parentId=6
full working sql online
(and I dont want to use cursor)
You can build your queries dynamically.
DECLARE #SQL NVARCHAR(MAX) =
(SELECT '
WITH CTE AS
(
SELECT childID
,parentID,
0 AS depth,name
FROM myTable
WHERE childID = parentID AND childID = '+CAST(childID AS NVARCHAR(10))+'
UNION ALL
SELECT TBL.childID
,TBL.parentID,
CTE.depth + 1 , TBL.name
FROM myTable AS TBL
INNER JOIN CTE ON TBL.parentID = CTE.childID
WHERE TBL.childID<>TBL.parentID
)
SELECT childID,parentID,REPLICATE(''----'', depth) + name
FROM CTE
ORDER BY
childID;'
FROM myTable
WHERE childID = parentID
FOR XML PATH(''), TYPE).value('text()[1]', 'NVARCHAR(MAX)');
EXEC sp_executesql #SQL;
Update:
As suggested by Bogdan Sahlean we can minimize compilations by making the actual query parameterized.
DECLARE #SQL1 NVARCHAR(MAX) =
'WITH CTE AS
(
SELECT childID
,parentID,
0 AS depth,name
FROM myTable
WHERE childID = parentID AND childID = #childID
UNION ALL
SELECT TBL.childID
,TBL.parentID,
CTE.depth + 1 , TBL.name
FROM myTable AS TBL
INNER JOIN CTE ON TBL.parentID = CTE.childID
WHERE TBL.childID<>TBL.parentID
)
SELECT childID,parentID,REPLICATE(''----'', depth) + name
FROM CTE
ORDER BY
childID;'
DECLARE #SQL2 NVARCHAR(MAX) =
(SELECT 'exec sp_executesql #SQL, N''#childID int'', '+CAST(childID AS NVARCHAR(10))+';'
FROM myTable
WHERE childID = parentID
FOR XML PATH(''), TYPE).value('text()[1]', 'NVARCHAR(MAX)');
EXEC sp_executesql #SQL2, N'#SQL NVARCHAR(MAX)', #SQL1;
To present multiple result sets to your client, you're going to have to use a cursor or a while loop to perform independent SELECT operations. You can't do that from a CTE, since a CTE can only be used by exactly one subsequent query.
Now, the source of the problem has nothing to do with cursors really, but the fact that you're using an HTML repeater. Why do you need to use an HTML repeater for this? A simple DataReader can loop through all of the results from the CTE's single set, and make conditional formatting decisions based on the loop and determining when the root ID changes. So I suggest you look into solving the presentation problem a different way, rather than trying to coerce SQL Server to accommodate your presentation implementation.
I am not sure what you mean by "returning two result sets". Could you just have one result set, with the root question being assigned into another column? The following tweak on your query does this:
WITH CTE AS (
SELECT ChildId as WhichQuestion, childID, parentID, 0 AS depth, name
FROM #myTable
WHERE childID = parentID
UNION ALL
SELECT cte.WhichQuestion, TBL.childID, TBL.parentID, CTE.depth + 1 , TBL.name
FROM #myTable AS TBL
INNER JOIN CTE ON TBL.parentID = CTE.childID
WHERE TBL.childID <> TBL.parentID
)
SELECT WhichQuestion, childID, parentID, REPLICATE('----', depth) + name
FROM CTE
ORDER BY WhichQuestion, childID;
You can save in anchor part childId of root question and then access all the branch by needed Id:
WITH CTE AS (
SELECT childID as RootId
, childID
, parentID
, 0 AS depth,name
FROM #myTable
WHERE childID = parentID
UNION ALL
SELECT CTE.RootId
, TBL.childID
, TBL.parentID
, CTE.depth + 1 , TBL.name
FROM #myTable AS TBL
INNER JOIN CTE ON TBL.parentID = CTE.childID
WHERE TBL.childID<>TBL.parentID
)

Resources