SQL - Prepend a value if missing - sql

Code/data:
DECLARE #T TABLE
(
[Col1] VARCHAR(20)
, [RowNum] INT
) ;
INSERT INTO #T
VALUES
( N'second', 1 )
, ( N'fifth', 4 )
, ( N'fourth', 3 )
--, ( N'zzz', 1 )
, ( N'third', 2 )
---- OR when "zzz" is part of this list
--VALUES
-- ( N'second', 2 )
-- , ( N'fifth', 5 )
-- , ( N'fourth', 4 )
-- , ( N'zzz', 1 )
-- , ( N'third', 3 )
SELECT STUFF ((
SELECT ',' + [SQ].[Col1]
FROM
(
SELECT N'zzz' AS [Col1]
, 1 AS [RowNum]
UNION
SELECT [Col1]
, [RowNum]
FROM #T
) AS [SQ]
FOR XML PATH ( '' ), TYPE
).[value] ( '.', 'varchar(MAX)' ), 1, 1, ''
) ;
Current output:
fifth,fourth,second,third,zzz
Goal:
Prepend "zzz," in the output string if missing in the 2nd part of the union AND the values should be in ASC ordered based on the values specified in [rownum] field defined in the 2nd part of the union. If "zzz" exists in the 2nd part of the input already (it will always be RowNum 1 in that case), it should return it only once as the first value.
Expected output:
zzz,second,third,fourth,fifth
UPDATED the requirement due to an error on my part when creating this post. Updated code/data represents more accurate scenario. Please note the RowNum seq in the 2nd part of the UNION, it also starts with 1, but this time, it might or might not be associated to "zzz" Basically, I want to prepend "zzz" in the comma-delimited & ordered output if it doesn't exist.

Hope the below one will help you.
SELECT ',' + [SQ].[Col1]
FROM
(
SELECT N'first' AS [Col1],1 AS [RowNum]
UNION
SELECT [ABC].[Col1],[ABC].[RowNum]
FROM
(
VALUES
( N'second', 2 )
, ( N'fifth', 5 )
, ( N'fourth', 4 )
--, ( N'first', 1 )
, ( N'third', 3 )
) AS [ABC] ( [Col1], [RowNum] )
) AS [SQ]
ORDER BY [RowNum]
FOR XML PATH ( '' ), TYPE
).[value] ( '.', 'varchar(MAX)' ), 1, 1, ''
) ;
Returns an output
first,second,third,fourth,fifth

Attached the Answer for the updated Scenario-
DECLARE #T TABLE
(
[Col1] VARCHAR(20)
, [RowNum] INT
) ;
INSERT INTO #T
VALUES
( N'second', 1 )
, ( N'fifth', 4 )
, ( N'fourth', 3 )
--, ( N'zzz', 1 )
, ( N'third', 2 )
---- OR when "zzz" is part of this list
--VALUES
-- ( N'second', 2 )
-- , ( N'fifth', 5 )
-- , ( N'fourth', 4 )
-- , ( N'zzz', 1 )
-- , ( N'third', 3 )
SELECT STUFF ((
SELECT ',' + [SQ].[Col1]
FROM
(
SELECT N'zzz' AS [Col1]
, 0 AS [RowNum]
UNION
SELECT [Col1]
, [RowNum]
FROM #T
) AS [SQ]
ORDER BY [RowNum]
FOR XML PATH ( '' ), TYPE
).[value] ( '.', 'varchar(MAX)' ), 1, 1, ''
) ;
Returns
zzz,second,third,fourth,fifth

Common Table Expressions (CTEs) provide a handy way of breaking queries down into simpler steps. Note that you can view the results of each step by switching out the last select statement.
with
Assortment as (
-- Start with the "input" rows.
select Col1, RowNum
from ( values ( N'second', 2 ), ( N'fifth', 5 ), ( N'fourth', 4 ),
-- ( N'first', 1 ),
( N'third', 3 ) ) ABC ( Col1, RowNum ) ),
ExtendedAssortment as (
-- Conditionally add "first".
select Col1, RowNum
from Assortment
union all -- Do not remove duplicate rows.
select N'first', 1
where not exists ( select 42 from Assortment where Col1 = N'first' ) )
-- Output the result.
-- Intermediate results may be seen by uncommenting one of the alternate select statements.
-- select * from Assortment;
-- select * from ExtendedAssortment;
select Stuff(
( select N',' + Col1 from ExtendedAssortment order by RowNum for XML path(N''), type).value( N'.[1]', 'NVarChar(max)' ),
1, 1, N'' ) as List;
The same logic can be performed using tables for input:
-- Rows to be included in the comma delimited string.
declare #Input as Table ( Col1 NVarChar(20), RowNum Int );
insert into #Input ( Col1, RowNum ) values
( N'second', 2 ), ( N'fifth', 5 ),
--( N'ZZZ', 17 ), -- Test row.
( N'fourth', 4 ), ( N'third', 3 );
select * from #Input;
-- Mandatory value that must appear in the result. One row only.
declare #Mandatory as Table ( Col1 NVarChar(20), RowNum Int );
-- By using the maximum negative value for an Int this value will be prepended
-- (unless other rows happen to have the same RowNum value).
insert into #Mandatory ( Col1, RowNum ) values ( N'ZZZ', -2147483648 );
select * from #Mandatory;
-- Process the data.
with
AllRows as (
select Col1, RowNum
from #Input
union all
select Col1, RowNum
from #Mandatory
where not exists ( select 42 from #Mandatory as M inner join #Input as I on M.Col1 = I.Col1 ) )
-- Output the result.
-- Intermediate results may be seen by uncommenting the alternate select statement.
--select * from AllRows;
select Stuff(
( select N',' + Col1 from AllRows order by RowNum for XML path(N''), type).value( N'.[1]', 'NVarChar(max)' ),
1, 1, N'' ) as List;

Related

Return random value for each row from different table

I'm trying to get random name for each row, but it just shows same random value for every row. What am I missing?
SELECT TOP (1000) v.id,v.[Name], RandomName
FROM [V3_Priva].[dbo].[Vehicle] v
cross join
(Select top 1 ISNULL([Description_cs-CZ], [Description]) RandomName
from crm.Enumeration e
join crm.EnumerationType et on e.EnumerationType_FK = et.Id
where EnumerationType_FK = 12
order by NEWID()) RandomName
Result table
Try using something like the following to drive your lookup.
DECLARE #Rows AS TABLE ( I INT NOT NULL )
DECLARE #Vals AS TABLE ( ID INT IDENTITY, Val NVARCHAR(255) NOT NULL )
INSERT INTO #Rows
VALUES ( 0 )
, ( 1 )
, ( 2 )
, ( 3 )
, ( 4 )
, ( 5 )
, ( 6 )
, ( 7 )
, ( 8 )
, ( 9 )
INSERT INTO #Vals
VALUES ( 'Apple' )
, ( 'Banana' )
, ( 'Peach' )
, ( 'Plum' )
, ( 'Pear' )
WITH cte AS ( SELECT *, ABS(CHECKSUM(NEWID())) % 5 ID FROM #Rows )
SELECT cte.I
, cte.ID
, V.ID
, V.Val
FROM cte
JOIN #Vals V ON V.ID = cte.ID + 1
ORDER BY I
This way new ID is generated for each row, rather than once for the lookup.

TSQL - Mapping Parent/Child Hierarchy

Sample Data:
DECLARE #Hierarchy TABLE
(
[ParentId] INT
, [ChildId] INT
) ;
INSERT INTO #Hierarchy
VALUES
( 1, 2 )
, ( 1, 3 )
, ( 2, 4 )
, ( 3, 5 )
, ( 4, 3 )
, ( 4, 6 )
, ( 5, 6 )
, ( 7, 3 ) ;
Current Query:
; WITH CTE AS
(
SELECT [ParentId]
, [ChildId]
, 1 AS [Level]
, CONCAT ( CAST ( [ParentId] AS VARCHAR(MAX) ), '.', CAST ( [ChildId] AS VARCHAR(MAX) ) ) AS [Path]
FROM #Hierarchy
UNION ALL
SELECT [C].[ParentId]
, [T].[ChildId]
, [C].[Level] + 1
, CAST ( [C].[Path] + '.' + CAST([T].[ChildId] AS VARCHAR(MAX) ) AS VARCHAR(MAX) )
FROM CTE AS [C]
JOIN #Hierarchy AS [T]
ON [C].[ChildId] = [T].[ParentId]
)
SELECT *
FROM CTE
ORDER BY [ParentId]
, [Level]
, [ChildId] ;
Goal:
distinctly group levels of shared "path" together
find the shallowest and the deepest levels of the shared "path"
Expected Output:
NOTICE: the records with Orange highlight at the end are manually inserted to show what I'm expecting, but haven't figure out yet.
Group: Basically a "dense rank" of each "groups" of nodes that follow the same path. I think if you look at the values of Group in the above image and relate it to Level and Path field's data, it'll make more sense.
IsShallowest: 1st level (I can see that now that someone brought it up). Just need to figure out how to derive those missing (repeating) records
IsDeepest: max level within the group.
Think IsShallowest and IsDeepest is easy to figure out once "Group" logic is figured out and adding missing (repeated) records.
Please check this solution. It provide the requested solution except adding the extra row which more information is needed
;WITH CTE AS (
SELECT
[ParentId]
, [ChildId]
, 1 AS [Level]
, CONCAT ( CAST ( [ParentId] AS VARCHAR(MAX) ), '.', CAST ( [ChildId] AS VARCHAR(MAX) ) ) AS [Path]
, MyGroup1 = ROW_NUMBER() OVER(ORDER BY [ParentId])
, MyGroup2 = ROW_NUMBER() OVER(ORDER BY [ParentId])
FROM Hierarchy
UNION ALL
SELECT
[C].[ParentId]
, [T].[ChildId]
, [C].[Level] + 1
, CAST ( [C].[Path] + '.' + CAST([T].[ChildId] AS VARCHAR(MAX) ) AS VARCHAR(MAX) )
, MyGroup1 = C.MyGroup1
, MyGroup2 = [C].[MyGroup1] + ROW_NUMBER() OVER(ORDER BY [T].[ParentId]) - 1
FROM CTE AS [C]
JOIN Hierarchy AS [T] ON [C].[ChildId] = [T].[ParentId]
)
, MyCTE2 as (
SELECT
[ParentId]
, [ChildId]
, [Level]
, [Path]
-- un-comment bellow 2 rows to see the logic
--, MyGroup1
--, MyGroup2
, MyGroup = DENSE_RANK() OVER (ORDER BY MyGroup1, MyGroup2)
FROM CTE
),
MyCTE3 as (
SELECT
[ParentId]
, [ChildId]
, [Level]
, [Path]
, MyGroup
, shallowest = ROW_NUMBER() OVER(PARTITION BY MyGroup ORDER BY [Path])
, deepest = ROW_NUMBER() OVER(PARTITION BY MyGroup ORDER BY [Path] DESC)
FROM MyCTE2
)
SELECT
[ParentId]
, [ChildId]
, [Level]
, [Path]
, MyGroup
, ISshallowest = CASE WHEN shallowest = 1 then 1 else 0 END
, Isdeepest = CASE WHEN deepest = 1 then 1 else 0 END
FROM MyCTE3
ORDER BY
--[Path]
[ParentId]
, [Level]
, [ChildId];

Performing a custom sort which included order by clause in derived table

I am facing a complex situation where I am aware of the approach which can solve the problem but the order by clause in my derived table is messing up the custom sort. Here are my input and output details and what I have tried.
Schema :-
Input :-
CREATE TABLE Test( Rowname VARCHAR(10), Col1 DATETIME, Col2 DATETIME, Col3 DATETIME, Col4 DATETIME );
INSERT INTO Test VALUES( 'Row1', '2016-01-14', '2016-01-08', '2016-01-30', '2016-01-01' );
INSERT INTO Test VALUES( 'Row2', '2016-01-02', '2016-01-01', '2016-01-18', '2016-01-15' );
Expected Output :-
RowName Result
Row1 Col4,Col2,Col1,Col3
Row2 Col2,Col1,Col4,Col3
What I have tried?
WITH CTE(RowName,Colmn,RN) AS
(
SELECT RowName,Colmn,ROW_NUMBER() OVER( PARTITION BY RowName ORDER BY RowName ) AS RN
FROM
(
( SELECT RowName,Col1 AS Col,'Col1' AS Colmn FROM Test )
UNION ALL
( SELECT RowName,Col2 AS Col,'Col2' AS Colmn FROM Test )
UNION ALL
( SELECT RowName,Col3 AS Col,'Col3' AS Colmn FROM Test )
UNION ALL
( SELECT RowName,Col4 AS Col,'Col4' AS Colmn FROM Test )
) Z
ORDER BY RowName,Col
)
SELECT RowName,
MAX( ( CASE WHEN RN = 1 THEN Colmn END ) ) + ',' +
MAX( ( CASE WHEN RN = 2 THEN Colmn END ) ) + ',' +
MAX( ( CASE WHEN RN = 3 THEN Colmn END ) ) + ',' +
MAX( ( CASE WHEN RN = 4 THEN Colmn END ) ) AS Result
FROM CTE
GROUP BY RowName;
Note :-
The ORDER BY RowName,Col Clause in the inner query/derived table is failing as it is not allowed in SQL Server. If I don't use this ORDER BY then how can I perform custom sort without using ORDER BY clause?
Your order by should be defined in the over clause:
WITH CTE(RowName,Colmn,RN) AS
(
SELECT
RowName,Colmn,ROW_NUMBER() OVER(
PARTITION BY RowName
ORDER BY RowName, Col -- add Col here
) AS RN
FROM
(
( SELECT RowName,Col1 AS Col,'Col1' AS Colmn FROM Test )
UNION ALL
( SELECT RowName,Col2 AS Col,'Col2' AS Colmn FROM Test )
UNION ALL
( SELECT RowName,Col3 AS Col,'Col3' AS Colmn FROM Test )
UNION ALL
( SELECT RowName,Col4 AS Col,'Col4' AS Colmn FROM Test )
) Z
-- ORDER BY RowName,Col -- remove this line
)
SELECT RowName,
MAX( ( CASE WHEN RN = 1 THEN Colmn END ) ) + ',' +
MAX( ( CASE WHEN RN = 2 THEN Colmn END ) ) + ',' +
MAX( ( CASE WHEN RN = 3 THEN Colmn END ) ) + ',' +
MAX( ( CASE WHEN RN = 4 THEN Colmn END ) ) AS Result
FROM CTE
GROUP BY RowName;
if you add col into to the order by in the row_number it will order the way you want.
ROW_NUMBER() OVER( PARTITION BY RowName ORDER BY RowName, col )
declare #test TABLE( Rowname VARCHAR(10), Col1 DATETIME, Col2 DATETIME, Col3 DATETIME, Col4 DATETIME );
INSERT INTO #test VALUES( 'Row1', '2016-01-14', '2016-01-08', '2016-01-30', '2016-01-01' );
INSERT INTO #test VALUES( 'Row2', '2016-01-02', '2016-01-01', '2016-01-18', '2016-01-15' );
;with cte as(
select a.Rowname, 'Col' + b.ID as ColName, b.Col from #test as a
outer apply (select * from (values ('1', Col1), ('2', Col2), ('3', Col3), ('4', Col4)) as t(ID, Col)) as b
)
select Distinct Rowname,
(Select ColName + ', ' From cte as b
Where b.Rowname=a.Rowname
order by b.Col for xml path(''), type).value('.', 'varchar(30)') as ColList
from cte as a

SQL How can I order the data using distinct?

I have the following Table 'tbl1'.
declare #tbl1 table ( col1 varchar(32))
insert into #tbl1
values ( 'C1' )
, ( 'B1' )
, ( 'X1' )
, ( 'A1' )
, ( 'B1' )
, ( 'C1' )
, ( 'B1' )
, ( 'A1' )
, ( 'X1' )
, ( 'C1' )
, ( 'D1' )
I tried the following query
select distinct col1
from #tbl1
order by col1
The output should come in the following order, and remove all the duplicate value
C1
B1
X1
A1
D1
You need to specify the order for your items, as others have noted.
Also you need a subquery and grouping to be able to sort over the minimal order column.
declare #source table (id int not null identity primary key, name nvarchar(3));
insert into #source
values ( 'C1' )
, ( 'B1' )
, ( 'X1' )
, ( 'A1' )
, ( 'B1' )
, ( 'C1' )
, ( 'B1' )
, ( 'A1' )
, ( 'X1' )
, ( 'C1' )
, ( 'D1' )
;
with grouped as
(
select min(id) as minId, name from #source
group by name
)
select name from grouped order by minId;
The query could be rewritten without CTE:
select grouped.name from
(select min(id) as minId, name from #source group by name) grouped
order by grouped.minId;
This yields exactly the result you requested.
To repeat what others have pointed out in comments: if you specify no ORDER, then the order of results is not guaranteed. The fact that you get your results in a certain order currently should be treated as coincidental. If you want a certain ordering in your results, you have to be explicit about it!
As an interesting note, in my experience this is especially important if you're doing a DISTINCT in your query, because depending on the statistics for your tables, the engine may or may not decide that ordering the data to execute the DISTINCT is in fact the best possible plan.
Given that you mention a very explicit ordering requirement...
The output should come in the following order, and remove all the duplicate value
C1
B1
X1
A1
D1
...you should make that explicit in your query:
SELECT DISTINCT
*,
CASE
WHEN Col1 = 'C1' THEN 0
WHEN Col1 = 'B1' THEN 1
WHEN Col1 = 'X1' THEN 2
WHEN Col1 = 'A1' THEN 3
WHEN Col1 = 'D1' THEN 4
ELSE 5
END AS SortColumn
FROM tbl1
ORDER BY SortColumn
(extension of Jeroen response)
If your "key" values are dynamic, you normally should have a separate table with the order of the keys...
declare #sort table (id varchar(10), ord int)
insert into #sort
values ( 'C1', 1 )
, ( 'X1', 2 )
, ( 'B1', 3 )
, ( 'A1', 4 )
, ( 'D1', 5 )
then you join/subquery on that table to calculate the SortColumn
-- join
SELECT DISTINCT
t.*,
s.ord SortColumn
FROM #tbl1 t
LEFT JOIN #sort s ON S.id = t.col1
ORDER BY SortColumn
-- subquery
SELECT DISTINCT
t.*,
(SELECT s.ord FROM #sort s WHERE s.id = t.col1) SortColumn
FROM #tbl1 t
ORDER BY SortColumn
declare #tbl1 table ( col1 varchar(32))
insert into #tbl1
values ( 'Basic Salary' )
, ( 'HRA' )
, ( 'OtherAllowance' )
, ( 'PF' )
, ( 'Basic Salary' )
, ( 'HRA' )
, ( 'Other Allowance' )
, ( 'PF' )
, ( 'ESIC' )
, ( 'Basic Salary' )
, ( 'HRA' )
, ( 'Other Allowance' )
, ( 'PF' )
, ( 'Basic Salary' )
select distinct col1
from #tbl1
order by col1

Sorting comments with nested replies

I am trying to come up with a query that will return comments following their replies and its replies.
Something like
comment 1
reply 1.1
reply 1.1.1
reply 1.2
comment 2
comment 3
comment 3.1
etc
I have this so far
SELECT [CommentID]
,[ParentID]
,[Message]
, ROW_NUMBER() over(partition by ParentID order by CommentID ) as rn
,[CreatedBy]
,[CreatedDate]
FROM [DBNAME].[dbo].[Commenttable]
GROUP BY [CommentID],[ParentID],[CreatedDate],[Message],[CreatedBy]
but what I get is
comment 1
comment 2
comment 3
reply 1.1
reply 1.2
reply 3.1
reply 1.1.1
Basic Structure is just a table with Comment ID, Parent ID, and the message.
The comments and replies are just to help explain what I am trying to achieve
Give this a try:
declare #CommentTable as Table ( CommentId Int Identity, ParentId Int Null, Message VarChar(16) )
insert into #CommentTable ( ParentId, Message ) values
( null, '1' ),
( null, '2' ), ( 1, '1.1' ),
( null, '3' ), ( 4, '3.1' ), ( 3, '1.1.1' ), ( 1, '1.2' )
select * from #CommentTable
; with Cindy as (
-- Start with the base comments.
select CommentId, ParentId, Message, Row_Number() over ( order by CommentId ) as Number,
Cast( Row_Number() over ( order by CommentId ) as VarChar(1000) ) as Path,
Cast( Right( '0000' + Cast( Row_Number() over ( order by CommentId ) as VarChar(4) ), 5 ) as VarChar(1000) ) as OrderPath
from #CommentTable
where ParentId is NULL
union all
-- Add replies on layer at a time.
select CT.CommentId, CT.ParentId, CT.Message, Row_Number() over ( order by CT.CommentId ),
Cast( C.Path + '.' + Cast( Row_Number() over ( order by CT.CommentId ) as VarChar(4) ) as VarChar(1000) ),
Cast( C.OrderPath + Right( '0000' + Cast( Row_Number() over ( order by CT.CommentId ) as VarChar(4) ), 5 ) as VarChar(1000) )
from #CommentTable as CT inner join
Cindy as C on C.CommentId = CT.ParentId
)
select *
from Cindy
order by OrderPath