Tsql query Case statment - sql-server

I'm trying to write a query (Microsoft SQL SERVER) the looks like :
select productid from T1
union
select productid from T2
union
select ProductID from T3
The issue is that I want that one of that union will be depend of a value(lets say #x)
So, I write this query :
select case when #x=1 then productid end
from T1
union
select productid from T2
union
select ProductID from T3
But its still scan all T1.
I know that "Case" scan the table even if its not match the condition:
select case when 1=2 then productid end from T1
How can I write a query that base on a value will now if execute or not ??
Something like : case #x=1 then select productid from T1 end....
* I need to find a way without dynamic sql
Thanks

use this:
Declare #SQL nvarchar(max)
Set #SQL = N' select productid From T1 '
Set #SQl = #SQL + N' UNION select productid From T2'
if(#x=1)
Set #SQl = #SQL + N' UNION select productid From T3'
exec (#SQL)

Thank you very much for your help,
I manage to do it as Pieter suggest, with a where clause . The query look like
select productid from t1 union select productid from t2
union select productid from t3 inner join t4 on t3.id=t4.t3id where t3.value=#x
Although I didn't want to use t4 table (because #x is one per all t3)
Thank you all.

Related

Count columns of a result from with block?

I am exploring provided code. I have various CTEs, and I want to count the columns in each CTE? Or select the differences in columns per CTE?
The below code will grab the count from a table in the DB, but I'm not sure how to apply this to CTEs?
select count(*)
from INFORMATION_SCHEMA.columns
where TABLE_CATALOG = 'db_name'
and TABLE_NAME = 'table_name'
First Create CTE and then insert just one record into temp table , then query the sys.columns table to get the list of the columns in CTE
IF OBJECT_ID('tempdb.dbo.#temp', 'U') IS NOT NULL
DROP TABLE #temp;
go
With CTE1 as (select * from table_name)
select top 1 * into #temp from CTE1
SELECT count(*) FROM tempdb.sys.columns where object_id = object_id('tempdb..#temp')

Multiple tables in the where clause SQL

I have 2 tables:-
Table_1
GetID UnitID
1 1,2,3
2 4,5
3 5,6
4 6
Table_2
ID UnitID UserID
1 1 1
1 2 1
1 3 1
1 4 1
1 5 2
1 6 3
I want the 'GetID' based on 'UserID'.
Let me explain you with an example.
For e.g.
I want all the GetID where UserID is 1.
The result set should be 1 and 2. 2 is included because one of the Units of 2 has UserID 1.
I want all the GetID where UserID is 2
The result set should be 2 and 3. 2 is included because one of Units of 2 has UserID 2.
I want to achieve this.
Thank you in Advance.
You can try a query like this:
See live demo
select
distinct userid,getid
from Table_1 t1
join Table_2 t2
on t1.unitId+',' like '%' +cast(t2.unitid as varchar(max))+',%'
and t2.userid=1
The query for this will be relatively ugly, because you made the mistake of storing CSV data in the UnitID column (or maybe someone else did and you are stuck with it).
SELECT DISTINCT
t1.GetID
FROM Table_1 t1
INNER JOIN Table_2 t2
ON ',' + t1.UnitID + ',' LIKE '%,' + CONVERT(varchar(10), t2.UnitID) + ',%'
WHERE
t2.UserID = 1;
Demo
To understand the join trick being used here, for the first row of Table_1 we are comparing ,1,2,3, against other single UnitID values from Table_2, e.g. %,1,%. Hopefully it is clear that my logic would match a single UnitID value in the CSV string in any position, including the first and last.
But a much better long term approach would be to separate those CSV values across separate records. Then, in addition to requiring a much simpler query, you could take advantage of things like indices.
try this:
declare #Table_1 table(GetID INT, UnitId VARCHAR(10))
declare #Table_2 table(ID INT, UnitId INT,UserId INT)
INSERT INTO #Table_1
SELECT 1,'1,2,3'
union
SELECT 2,'4,5'
union
SELECT 3,'5,6'
union
SELECT 4,'6'
INSERT INTO #Table_2
SELECT 1,1,1
union
SELECT 1,2,1
union
SELECT 1,3,1
union
SELECT 1,4,1
union
SELECT 1,5,2
union
SELECT 1,6,3
declare #UserId INT = 2
DECLARE #UnitId VARCHAR(10)
SELECT #UnitId=COALESCE(#UnitId + ',', '') + CAST(UnitId AS VARCHAR(5)) from #Table_2 WHERE UserId=#UserId
select distinct t.GetId
from #Table_1 t
CROSS APPLY [dbo].[Split](UnitId,',') AS AA
CROSS APPLY [dbo].[Split](#UnitId,',') AS BB
WHERE AA.Value=BB.Value
Split Function:
CREATE FUNCTION [dbo].Split(#input AS Varchar(4000) )
RETURNS
#Result TABLE(Value BIGINT)
AS
BEGIN
DECLARE #str VARCHAR(20)
DECLARE #ind Int
IF(#input is not null)
BEGIN
SET #ind = CharIndex(',',#input)
WHILE #ind > 0
BEGIN
SET #str = SUBSTRING(#input,1,#ind-1)
SET #input = SUBSTRING(#input,#ind+1,LEN(#input)-#ind)
INSERT INTO #Result values (#str)
SET #ind = CharIndex(',',#input)
END
SET #str = #input
INSERT INTO #Result values (#str)
END
RETURN
END

Create a User defined function like SQL server 2017 STRING_AGG on earlier versions

I try to create a generic function that can be used like this example of using the new string_agg built-in function on SQL Server 2017
the inside implementation can be something like the follow
with tbl as(
select a.Id, c.Desc
from TableA a
join TableB b on b.aId = a.Id
join TableC c on c.Code = b.bCode
)
select distinct ID
, STUFF(( select ', ' + Desc from tbl t where t.ID = tbl.ID
for xml path(''),TYPE).value('.','VARCHAR(MAX)'),1,2,'') Desc
from tbl
But how to receives the field key, the field to be connected, the separator char, and the scoped select context?
Is it related to Inline or Multi-Statement Table-Valued Functions ?
Well, this is an ugly hack, I have to go and wash my hands now, but it works (in a way :-D)
CREATE FUNCTION dbo.MyStringAgg(#SelectForXmlAuto XML,#Delimiter NVARCHAR(10))
RETURNS NVARCHAR(MAX)
AS
BEGIN
RETURN STUFF((
SELECT #Delimiter + A.nd.value(N'(#*)[1]',N'nvarchar(max)')
FROM #SelectForXmlAuto.nodes(N'/*') AS A(nd)
FOR XML PATH(''),TYPE
).value(N'.',N'nvarchar(max)'),1,LEN(#Delimiter),'');
END
GO
DECLARE #tbl TABLE(GroupId INT,SomeValue NVARCHAR(10));
INSERT INTO #tbl VALUES(1,'A1'),(1,'A2'),(2,'B1'),(3,'C1'),(3,'C2'),(3,'C3');
SELECT GroupId
,dbo.MyStringAgg((SELECT SomeValue
FROM #tbl AS t2
WHERE t2.GroupId=t.GroupId
FOR XML AUTO), N', ')
FROM #tbl AS t
GROUP BY GroupId;
GO
DROP FUNCTION dbo.MyStringAgg;
The result
1 A1, A2
2 B1
3 C1, C2, C3
The parameter is a FOR XML sub-select within paranthesis. This will implicitly pass the sub-selects result as an XML into the function.
To be honest: I would not use this myself...
A query like this
SELECT GroupId
,STUFF((SELECT N', ' + SomeValue
FROM #tbl AS t2
WHERE t2.GroupId=t.GroupId
FOR XML PATH,TYPE).value(N'.','nvarchar(max)'),1,2,'')
FROM #tbl AS t
GROUP BY GroupId;
produces the same result and is almost the same amount of typing - but should be faster then calling a slow UDF...
Ok.. so with the first comment of #MichaƂTurczyn I run into this Microsoft article about CLR User-Defined Aggregate - Invoking Functions
Once I compile the code into SrAggFunc.dll, I was trying to register the aggregate in SQL Server as follows:
CREATE ASSEMBLY [STR_AGG] FROM 'C:\tmp\STR_AGG.dll';
GO
But I got the following error.
Msg 6501, Level 16, State 7, Line 1
CREATE ASSEMBLY failed because it could not open the physical file 'C:\tmp\SrAggFunc.dll': 3(The system cannot find the path specified.).
So I used this excellant part of #SanderRijken code and then change the command to
CREATE ASSEMBLY [STR_AGG]
FROM 0x4D5A90000300000004000000FF......000; --from GetHexString function
GO
and then,
CREATE AGGREGATE [STR_AGG] (#input nvarchar(200)) RETURNS nvarchar(max)
EXTERNAL NAME [STR_AGG].C_STRING_AGG;`
Now it's done.
You can see it under your Database -> Programmability on SSMS
and used like :
SELECT a.Id, [dbo].[STR_AGG](c.Desc) cDesc
FROM TableA a
JOIN TableB b on b.aId = a.Id
JOIN TableC c on c.Code = b.bCode
GROUP BY a.Id
Thanks all =)

Dynamic SQL Pivot Query with Union All

I have a static pivot query that tracks historical pricing (see below); it is union all of one table of current data, one table of historical data, and one related table.
I would like to convert it to a dynamic SQL pivot query so that I don't manually have to update the column heading dates all of the time, but I am having problems doing so. The data comes out of an accounting system (Microsoft Dynamics) running on SQL Server, and due to system limitations, I am unable to use ansi nulls/ paddings/ warnings and also unable to use quoted identifiers. Is there another way to create a dynamic sql pivot query?
Here is my current query. I want to replace the month-end dates with dynamic sql. I have view access to the data only.
SELECT ACTDESCR AS "ACCT NAME", concat(right(actnumbr_2,2), actnumbr_3) as PLU, [3/31/2015], [2/28/2015], [1/31/2015], [12/31/2015], [11/30/2014], [10/31/2014], [9/30/2014], [8/31/2014], [7/31/2014], [6/30/2014], [5/31/2014], [4/30/2014], [3/31/2014], [2/28/2014], [1/31/2014]
FROM
(SELECT A.ACTDESCR, EOMONTH(DATEFROMPARTS(B.YEAR1,B.PERIODID,1),0) AS DATE1, A.actnumbr_2, a.actnumbr_3, B.PERDBLNC, A.ACTNUMBR_1
FROM TEST.dbo.table1 AS A
LEFT OUTER JOIN TEST.dbo.TABLE2 AS b
ON (a.ACTINDX = b.ACTINDX)
UNION ALL
SELECT A.ACTDESCR, EOMONTH(DATEFROMPARTS(C.YEAR1,C.PERIODID,1),0) AS DATE1, A.actnumbr_2, a.actnumbr_3, C.PERDBLNC, A.ACTNUMBR_1
FROM TEST.dbo.TABLE1 AS A
LEFT OUTER JOIN TEST.dbo.TABLE3 AS C
ON (a.ACTINDX = C.ACTINDX)
) AS ST
PIVOT
(
SUM(PERDBLNC)
FOR DATE1 IN ([3/31/2015], [2/28/2015], [1/31/2015], [12/31/2015], [11/30/2014], [10/31/2014], [9/30/2014], [8/31/2014], [7/31/2014], [6/30/2014], [5/31/2014], [4/30/2014], [3/31/2014], [2/28/2014], [1/31/2014])
) AS PVT
WHERE ACTNUMBR_1 = ?
You can use T-SQL with dynamic SQL for this:
--VARIABLE TO HOLD DATES--
DECLARE #DATES NVARCHAR(500)
--VARIABLE TO HOLD CODE--
DECLARE #SQL NVARCHAR(MAX)
--TEMP TABLE TO FIND DISTINCT DATES--
CREATE #DATES (COLUMNVALS NVARCHAR(10))
--INSERT DISTINCT DATES INTO TEMP TABLE--
INSERT INTO #DATES
SELECT DISTINCT DATE1 FROM (
SELECT EOMONTH(DATEFROMPARTS(YEAR1,PERIODID,1),0) AS DATE1
FROM TEST.DBO.TABLE2
UNION ALL
SELECT EOMONTH(DATEFROMPARTS(YEAR1,PERIODID,1),0) AS DATE1
FROM TEST.DBO.TABLE3)
--CONCAT DATES INTO SELECT LIST--
SET #DATES = COALESCE(#DATES+', ','') + '[' + DATE1 + ']' FROM #DATES
--CREATE THE SELECT STATEMENT--
SELECT #SQL = '
;WITH ST AS (
SELECT A.ACTDESCR, EOMONTH(DATEFROMPARTS(B.YEAR1,B.PERIODID,1),0) AS DATE1, A.ACTNUMBR_2, A.ACTNUMBR_3, B.PERDBLNC, A.ACTNUMBR_1
FROM TEST.DBO.TABLE1 AS A
LEFT OUTER JOIN TEST.DBO.TABLE2 AS B
ON A.ACTINDX = B.ACTINDX
UNION ALL
SELECT A.ACTDESCR, EOMONTH(DATEFROMPARTS(C.YEAR1,C.PERIODID,1),0) AS DATE1, A.ACTNUMBR_2, A.ACTNUMBR_3, C.PERDBLNC, A.ACTNUMBR_1
FROM TEST.DBO.TABLE1 AS A
LEFT OUTER JOIN TEST.DBO.TABLE3 AS C
ON A.ACTINDX = C.ACTINDX)
SELECT ACTDESCR AS "ACCT NAME", CONCAT(RIGHT(ACTNUMBR_2,2), ACTNUMBR_3) AS PLU, '+#DATES+'
FROM ST
PIVOT
(
SUM(PERDBLNC)
FOR DATE1 IN ('+#DATES+')
) AS PVT
WHERE ACTNUMBR_1 = ?'
--PRINT IT TO SEE WHAT IT'S DONE--
PRINT #SQL
--EXECUTE IT--
EXEC (#SQL)

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