Select SUM then use it as the parameter for SELECT TOP - tsql

I want these two SQL statements in one SQL command. Help :)
Statement #1:
SELECT SUM(nrofitems) as totItems
FROM tblSets
WHERE moduleexamID = 20
Statement #2:
SELECT TOP (cast(totItems as int)) questions
FROM tblQuestions
WHERE moduleexamID = 20
ORDER BY NEWID()

create table #tblSets (
moduleexamID int,
moduleId int,
nrofitems int
)
go
create table #tblQuestions (
moduleexamID int,
body varchar(1024)
)
go
insert into #tblQuestions values
(20,'aaaaaa'),
(20,'bbbbbb'),
(20,'cccccc'),
(20,'dddddd'),
(21,'eeeeee'),
(21,'ffffff'),
(20,'gggggg')
go
insert into #tblSets values
(20,1,1),
(20,2,2),
(21,1,1),
(22,1,3)
go
select top (
select sum(s.nrofitems)
from #tblSets s
where s.moduleexamID=20
) *, newid() as id
from #tblQuestions q
where q.moduleexamID=20
order by id
go

Just use ROW_NUMBER().
Something like this:
SELECT * FROM
(
SELECT tblQuestions.*,
ROW_NUMBER() OVER (ORDER BY NEWID()) as RN
FROM tblQuestions
WHERE moduleexamID = 20
) as T1
WHERE RN<=
ISNULL(
(SELECT SUM(nrofitems) as totItems
FROM tblSets
WHERE moduleexamID = 20
),0);

You may try following also:
;WITH a AS (
SELECT moduleexamID, SUM(nrofitems) as totItems
FROM tblSets
GROUP BY moduleexamID
)
SELECT b.questions
FROM a
CROSS APPLY (
SELECT TOP (cast(a.totItems as int)) questions
FROM tblQuestions
WHERE moduleexamID = a.moduleexamID
ORDER BY CHECKSUM(NEWID())
) b
WHERE a.moduleexamID = 20

Related

Delete duplicate rows with different values in columns

I didn't find my case on the Internet. Tell me how i can delete duplicates if the values are in different columns.
I have a table with a lot of values, for example:
|Id1|Id2|
|89417980|89417978|
|89417980|89417979|
|89417978|89417980|
|89417979|89417980|
I need to exclude duplicates and leave in the answer only:
|Id1|Id2|
|89417980|89417978|
|89417980|89417979|
min/max does not work here, as the values may be different.
I tried to union/join tables on a table/exclude results with temporary tables, but in the end I come to the beginning.
Assuming id1 and id2 are primary keys columns you could try this
DECLARE #tbl table (id1 int, id2 int )
INSERT INTO #tbl
SELECT 89417980, 89417978
UNION SELECT 89417980, 89417979
UNION SELECT 89417978, 89417980
UNION SELECT 89417979, 89417980
SELECT * FROM #tbl
;WITH CTE AS (--Get comparable value as "cs"
SELECT
IIF(id1 > id2, CHECKSUM(id1, id2), CHECKSUM(id2,id1)) as cs
, id1
, id2
, ROW_NUMBER() OVER (order by id1, id2) as rn
FROM #tbl
)
, CTE2 AS ( --Get rows to keep
SELECT MAX (rn) as rn
FROM CTE
GROUP BY cs
HAVING COUNT(*) > 1
)
DELETE tbl -- Delete all except the rows to keep
FROM #tbl tbl
WHERE NOT EXISTS(SELECT 1
FROM CTE2
JOIN CTE ON CTE.rn = CTE2.rn
WHERE CTE.id1 = tbl.id1
AND CTE.id2 = tbl.id2
)
SELECT * FROM #tbl

sql compare columns to get result

I have the following issue. I have products with 3 different states. Parent, Child and products which are orphans. I am setting Parents as 1, Children as 2 and Orphans as 0. I am struggling to get the Orphan to set to 0. I realise that counting the amount of Parent PLU's is where I am going wrong but I do not know how to resolve this issue. Any help would be appreciated. (As you maybe able to tell, I am a noob and constructive criticism would be appreciated)
Kind Regards,
Jason.
Picture of results from query
declare #OrderID int = 1635
declare #Store char(3) = '001'
declare #SortedBy smallint = 2
DECLARE #tbl TABLE (DetailID int, OrderID int, PLU nvarchar(35), ParentPLU nvarchar(35))
INSERT INTO #tbl (DetailID, OrderID, PLU, ParentPLU)
SELECT DetailID, OrderDetails.OrderID, OrderDetails.PLU, OrderDetails.ParentPLU
FROM OrderDetails
INNER JOIN PLU
ON PLU.PLU = OrderDetails.PLU
WHERE OrderDetails.OrderID = #OrderID
AND OrderDetails.OrderStore = #Store
SELECT DetailID, OrderID, PLU, ParentPLU,
CASE WHEN ( SELECT COUNT(DISTINCT ParentPLU)
FROM #tbl
WHERE ParentPLU IN (SELECT PLU FROM #tbl)
) > 0 AND ParentPLU = '' THEN 1
WHEN ( SELECT COUNT(DISTINCT ParentPLU)
FROM #tbl
WHERE ParentPLU IN (SELECT PLU FROM #tbl)
) > 0 THEN 2
ELSE
0
END AS ParentChild,
ROW_NUMBER() OVER (ORDER BY
CASE WHEN #SortedBy = 1 THEN OrderID END ASC,
CASE WHEN #SortedBy = 2 THEN DetailID END ASC
) AS ID
FROM #tbl
You can use coalesce to get your desired result. First subquery checks for parent state, second for children. If both are null, then it is orphan
select
DetailID, OrderID, PLU, ParentPLU
, coalesce((
select
distinct 1
from
#tbl b
where
a.PLU = b.ParentPlu
)
, (
select
distinct 2
from
#tbl b
where
b.PLU = a.ParentPlu
), 0)
from
#tbl a

sql recursion: find tree given middle node

I need to get a tree of related nodes given a certain node, but not necessary top node. I've got a solution using two CTEs, since I am struggling to squeeze it all into one CTE :). Might somebody have a sleek solution to avoid using two CTEs? Here is some code that I was playing with:
DECLARE #temp AS TABLE (ID INT, ParentID INT)
INSERT INTO #temp
SELECT 1 ID, NULL AS ParentID
UNION ALL
SELECT 2, 1
UNION ALL
SELECT 3, 2
UNION ALL
SELECT 4, 3
UNION ALL
SELECT 5, 4
UNION ALL
SELECT 6, NULL
UNION ALL
SELECT 7, 6
UNION ALL
SELECT 8, 7
DECLARE #startNode INT = 4
;WITH TheTree (ID,ParentID)
AS (
SELECT ID, ParentID
FROM #temp
WHERE ID = #startNode
UNION ALL
SELECT t.id, t.ParentID
FROM #temp t
JOIN TheTree tr ON t.ParentID = tr.ID
)
SELECT * FROM TheTree
;WITH Up(ID,ParentID)
AS (
SELECT t.id, t.ParentID
FROM #temp t
WHERE t.ID = #startNode
UNION ALL
SELECT t.id, t.ParentID
FROM #temp t
JOIN Up c ON t.id = c.ParentID
)
--SELECT * FROM Up
,TheTree (ID,ParentID)
AS (
SELECT ID, ParentID
FROM Up
WHERE ParentID is null
UNION ALL
SELECT t.id, t.ParentID
FROM #temp t
JOIN TheTree tr ON t.ParentID = tr.ID
)
SELECT * FROM TheTree
thanks
Meh. This avoids using two CTEs, but the result is a brute force kludge that hardly qualifies as "sleek" as it won’t be efficient if your table is at all sizeable. It will:
Recursively build all possible hierarchies
As you build them, flag the target NodeId as you find it
Return only the targeted tree
I threw in column “TreeNumber” on the off-chance the TargetId appears in multiple hierarchies, or if you’d ever have multiple values to check in one pass. “Depth” was added to make the output a bit more legible.
A more complex solution like #John’s might do, and more and subtler tricks could be done with more detailed table sturctures.
DECLARE #startNode INT = 4
;WITH cteAllTrees (TreeNumber, Depth, ID, ParentID, ContainsTarget)
AS (
SELECT
row_number() over (order by ID) TreeNumber
,1
,ID
,ParentID
,case
when ID = #startNode then 1
else 0
end ContainsTarget
FROM #temp
WHERE ParentId is null
UNION ALL
SELECT
tr.TreeNumber
,tr.Depth + 1
,t.id
,t.ParentID
,case
when tr.ContainsTarget = 1 then 1
when t.ID = #startNode then 1
else 0
end ContainsTarget
FROM #temp t
INNER JOIN cteAllTrees tr
ON t.ParentID = tr.ID
)
SELECT
TreeNumber
,Depth
,ID
,ParentId
from cteAllTrees
where TreeNumber in (select TreeNumber from cteAllTrees where ContainsTarget = 1)
order by
TreeNumber
,Depth
,ID
Here is a technique where you can select the entire hierarchy, a specific node with all its children, and even a filtered list and how they roll.
Note: See the comments next to the DECLAREs
Declare #YourTable table (id int,pt int,name varchar(50))
Insert into #YourTable values
(1,null,'1'),(2,1,'2'),(3,1,'3'),(4,2,'4'),(5,2,'5'),(6,3,'6'),(7,null,'7'),(8,7,'8')
Declare #Top int = null --<< Sets top of Hier Try 2
Declare #Nest varchar(25) = '|-----' --<< Optional: Added for readability
Declare #Filter varchar(25) = '' --<< Empty for All or try 4,6
;with cteP as (
Select Seq = cast(1000+Row_Number() over (Order by name) as varchar(500))
,ID
,pt
,Lvl=1
,name
From #YourTable
Where IsNull(#Top,-1) = case when #Top is null then isnull(pt,-1) else ID end
Union All
Select Seq = cast(concat(p.Seq,'.',1000+Row_Number() over (Order by r.name)) as varchar(500))
,r.ID
,r.pt
,p.Lvl+1
,r.name
From #YourTable r
Join cteP p on r.pt = p.ID)
,cteR1 as (Select *,R1=Row_Number() over (Order By Seq) From cteP)
,cteR2 as (Select A.Seq,A.ID,R2=Max(B.R1) From cteR1 A Join cteR1 B on (B.Seq like A.Seq+'%') Group By A.Seq,A.ID )
Select Distinct
A.R1
,B.R2
,A.ID
,A.pt
,A.Lvl
,name = Replicate(#Nest,A.Lvl-1) + A.name
From cteR1 A
Join cteR2 B on A.ID=B.ID
Join (Select R1 From cteR1 where IIF(#Filter='',1,0)+CharIndex(concat(',',ID,','),concat(',',#Filter+','))>0) F on F.R1 between A.R1 and B.R2
Order By A.R1

SQL Server : Split string to row

How to turn data from below:
CODE COMBINATION USER
1111.111.11.0 KEN; JIMMY
666.778.0.99 KEN
888.66.77.99 LIM(JIM); JIMMY
To
CODE COMBINATION USER
1111.111.11.0 KEN
1111.111.11.0 JIMMY
666.778.0.99 KEN
888.66.77.99 LIM(JIM)
888.66.77.99 JIMMY
I know in SQL Server 2016 this can be done by split string function, but my production is SQL Server 2014.
With this TVF, you can supply the string to be split and delimiter. Furthermore, you get the sequence number which can be very useful for secondary processing.
Select [CODE COMBINATION]
,[USER] = B.RetVal
From YourTable A
Cross Apply [dbo].[udf-Str-Parse](A.[USER],';') B
Returns
The Parse UDF
CREATE FUNCTION [dbo].[udf-Str-Parse] (#String varchar(max),#Delimiter varchar(10))
Returns Table
As
Return (
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(#String,#Delimiter,'</x><x>')+'</x>' as xml).query('.')) as A
Cross Apply x.nodes('x') AS B(i)
);
--Select * from [dbo].[udf-Str-Parse]('Dog,Cat,House,Car',',')
--Select * from [dbo].[udf-Str-Parse]('John Cappelletti was here',' ')
Now, another option is the Parse-Row UDF. Notice we return the parsed string in one row. Currently 9 positions, but it is easy to expand or contract.
Select [CODE COMBINATION]
,B.*
From YourTable A
Cross Apply [dbo].[udf-Str-Parse-Row](A.[USER],';') B
Returns
The Parse Row UDF
CREATE FUNCTION [dbo].[udf-Str-Parse-Row] (#String varchar(max),#Delimiter varchar(10))
Returns Table
As
Return (
Select Pos1 = xDim.value('/x[1]','varchar(max)')
,Pos2 = xDim.value('/x[2]','varchar(max)')
,Pos3 = xDim.value('/x[3]','varchar(max)')
,Pos4 = xDim.value('/x[4]','varchar(max)')
,Pos5 = xDim.value('/x[5]','varchar(max)')
,Pos6 = xDim.value('/x[6]','varchar(max)')
,Pos7 = xDim.value('/x[7]','varchar(max)')
,Pos8 = xDim.value('/x[8]','varchar(max)')
,Pos9 = xDim.value('/x[9]','varchar(max)')
From (Select Cast('<x>' + Replace(#String,#Delimiter,'</x><x>')+'</x>' as XML) as xDim) A
)
--Select * from [dbo].[udf-Str-Parse-Row]('Dog,Cat,House,Car',',')
--Select * from [dbo].[udf-Str-Parse-Row]('John Cappelletti',' ')
You need to use a UDF for splitting it on each row
CREATE FUNCTION [DBO].[FN_SPLIT_STR_TO_COL] (#T AS VARCHAR(4000) )
RETURNS
#RESULT TABLE(VALUE VARCHAR(250))
AS
BEGIN
SET #T= #T+';'
;WITH MYCTE(START,[END]) AS(
SELECT 1 AS START,CHARINDEX(';',#T,1) AS [END]
UNION ALL
SELECT [END]+1 AS START,CHARINDEX(';',#T,[END]+1)AS [END]
FROM MYCTE WHERE [END]<LEN(#T)
)
INSERT INTO #RESULT
SELECT SUBSTRING(#T,START,[END]-START) NAME FROM MYCTE;
RETURN
END
Now query on your table by calling above function with CROSS APPLY
SELECT [CodeCombination],FN_RS.VALUE FROM TABLE1
CROSS APPLY
(SELECT * FROM [DBO].[FN_SPLIT_STR_TO_COL] (User))
AS FN_RS
If your [USER] column only has one semicolon you don't need a "split string" function at all; you could use CROSS APPLY like this:
-- Your Sample data
DECLARE #table TABLE (CODE_COMBINATION varchar(30), [USER] varchar(100));
INSERT #table
VALUES ('1111.111.11.0', 'KEN; JIMMY'), ('666.778.0.99', 'XKEN'),
('888.66.77.99','LIM(JIM); JIMMY');
-- Solution using only CROSS APPLY
SELECT CODE_COMBINATION, [USER] = LTRIM(s.s)
FROM #table t
CROSS APPLY (VALUES (CHARINDEX(';',t.[USER]))) d(d)
CROSS APPLY
(
SELECT SUBSTRING(t.[USER], 1, ISNULL(NULLIF(d.d,0),1001)-1)
UNION ALL
SELECT SUBSTRING(t.[USER], d.d+1, 1000)
WHERE d.d > 0
) s(s);
If you do need a pre SQL Server 2016 "split string" function I would strongly suggest using Jeff Moden's DelimitedSplit8k or Eirikur Eiriksson's DelimitedSplit8K_LEAD. Both of these will outperform an XML-based or recursice CTE "split string" function.

t-sql WITH on WITH

I have to make query on WITH query, something like
; WITH #table1
(
SELECT id, x from ... WHERE....
UNION ALL
SELECT id, x from ... WHERE...
)
WITH #table2
(
SELECT DISTINCT tbl_x.*,ROW_NUMBER() OVER (order by id) as RowNumber
WHERE id in ( SELECT id from #table1)
)
SELECT * FROM #table2 WHERE RowNumber > ... and ...
So I have to use WITH on WITH and then SELECT on second WITH, How I can do that?
You can define multiple CTEs after the WITH keyword by separating each CTE with a comma.
WITH T1 AS
(
SELECT id, x from ... WHERE....
UNION ALL
SELECT id, x from ... WHERE...
)
, T2 AS
(
SELECT DISTINCT tbl_x.*, ROW_NUMBER() OVER (order by id) as RowNumber
WHERE id in ( SELECT id from T1 )
)
SELECT * FROM T2 WHERE RowNumber > ... and ...
https://web.archive.org/web/20210927200924/http://www.4guysfromrolla.com/webtech/071906-1.shtml