SQL Server 2017 - Concatenate values based on order number - sql

I am doing concatenation of values where there is grouping value greater than 1. This works fine, but now I am trying to figure out how to utilize an sequence/order number in which to concatenate the values in that order. Is there a way I can do this?
So for example, I have a table which has the following:
I need the ability to concatenate Field1 and Field4 since the StagingCol is the same name and I also need to be able to concatenate in the order provided in the ConcatenateOrder column. I can't have it out of sequence i.e. Field4 + Field1
This is a snippet of the code I have so far which is working fine, it concatenates the two LandingZoneCol values...
--DECLARATION OF LANDING ZONE FIELD NAMES CONCATENATED TOGETHER AND DELMITED BY A COMMA WHERE VALUE NEEDS TO BE CONCATENATED (I.E. SUBSCRIBER + SEQ# = MEMBER_ID)
SELECT #ConcatLandingZoneFieldNames = ISNULL(#ConcatLandingZoneFieldNames,'') + ISNULL(LandZoneMapping.LandingZoneFieldName,'') + ', '
FROM #LandingZoneMapping AS LandZoneMapping
WHERE LandZoneMapping.StagingColumnName <> 'BreadCrumb'
AND LandZoneMapping.StagingColumnName IN (SELECT StagingColumnName
FROM #TEST
WHERE Total > 1)
--DECLARATION OF VARIABLES
SET #ConcatLandingZoneFieldNames = CONCAT('CONCAT(',SUBSTRING(#ConcatLandingZoneFieldNames,1,LEN(#ConcatLandingZoneFieldNames)-1),')')
Current Results
CONCAT(Field1, Field4)
Expected Results
CONCAT(Field1, Field4)
Although both Current and Expected are the same right now, I want to ensure that the concatenation of the values are concatenated in the correct order. If I was to flip the ConcatenateOrder numbers in the above table then the outcome would be different. The "Current" Results would end up being CONCAT(Field1, Field4) but the "Expected Results" should be CONCAT(Field4, Field1)
Any help would be greatly appreciated.

Your code looks like SQL Server. You an use string_agg():
select string_agg(lzm.landingzonecol, ',') within group (order by lzm.concatenateorder)
from #LandingZoneMapping lzm
where lzm.stagingcol = 'ID';
You can control the ordering with the order by clause.

As mention by Gordon you can use string_agg (Doc) function if you are using SQL Server version 2017 or above.
In case if you need same functionality for SQL Server version below 2017, use this:
SELECT STUFF((
SELECT CONCAT (
','
,LandingZoneCol
)
FROM #LandingZoneMapping LZM
WHERE StagingCol = 'ID'
ORDER BY ConcatenateOrder
FOR XML PATH('')
), 1, 1, '') AS Result

Related

Order by generated column

I have following data structure:
CREATE TABLE test(
id INTEGER PRIMARY KEY,
data TEXT NOT NULL
);
INSERT INTO test (data) VALUES ('10.20.3.40'), ('10.100.3'), ('10.20.20.40')
The problem is that i need to order by data column using integer logic (dot-separated string as array of integers).
Using order by it returns data sorted as text:
SELECT data FROM test ORDER BY data
10.100.3
10.20.20.40
10.20.3.40
Result I need to achieve:
10.20.3.40
10.20.20.40
10.100.3
The simplest method to sort it properly without reimplementing arrays in SQLite which I've found is to add zero-padding to each part of data.
So basically I need to:
Select all data from table;
Split value of data column;
Add zero-padding and join it back;
Join newly generated column with reformatted data
Order by this column
What have I already done:
WITH RECURSIVE split_str(source, part) AS (
SELECT '10.20.3.40' || '.', NULL
UNION ALL
SELECT
substr(source, instr(source, '.') + 1),
substr('000000' || source, instr(source, '.'), 6)
FROM split_str WHERE source != ''
)
SELECT group_concat(part, '.') AS new_data FROM split_str WHERE part IS NOT NULL
It splits constant string '10.20.3.40' by dot, add leading zeros to each part and join it back using group_concat(). It returns:
000010.000020.000003.000040
Now I need to apply such a modification to values of data column from test table and somehow use this values for sorting. That's result I'm trying to get:
I'm not an expert in SQL (obviously) and don't understand how to apply expression in WITH clause on each data column separately.
As you can't use GROUP_CONCAT() if you want to preserve the order, just build up another string instead.
Then, only take the records where there's no more 'unpadded' string still to be processed.
WITH RECURSIVE
test_set(original)
AS
(
SELECT '10.20.3.40'
UNION ALL
SELECT '10.100.3'
UNION ALL
SELECT '10.20.20.40'
),
split_str(original, remaining, padded)
AS
(
SELECT original, original || '.', '' FROM test_set
---------
UNION ALL
---------
SELECT
original,
substr(remaining, instr(remaining, '.') + 1),
padded || '.' || substr('000000' || remaining, instr(remaining, '.'), 6)
FROM
split_str
WHERE
remaining != ''
)
SELECT
original,
padded
FROM
split_str
WHERE
remaining = ''
ORDER BY
padded
Demo: db<>fiddle.uk
(You may or may not want to strip the leading ., depending on your needs.)

Listagg delimeter is geting printed is there are no values selected

I am using LISTAGG() function in my select statement in a procedure such as
SELECT DISTINCT REPLACE(LISTAGG(a.student1||':'||a.score||'+')
WITHIN GROUP ( ORDER BY a.roll_no) OVER (PARTITION BY a.class)
If my select statement has not null values then i get
for eg: ABC:100
But if the select is empty then i get
for eg: :
I wanted it to be null if there are no rows selected.
The problem is that the expression you're aggregating returns a non-NULL value whether or not the values you're concatenating are NULL. My guess is you want something like
listagg(case when a.student1 is not null
then a.student1||':'||a.score||'+'
else null
end)
If you can have a null value for student1 but a non-NULL value for score, you'd need to adjust the case statement to specify what you want to happen in that case (do you want the ":" and the "+"? Just the "+"?)
Well, your REPLACE is incomplete. Also, is '+' really supposed to be concatenated? What string separates values, then?
REPLACE (
LISTAGG (a.student1 || ':' || a.score, '+') --> removed || for the + sign
WITHIN GROUP (ORDER BY a.roll_no)
OVER (PARTITION BY a.class),
':', '') --> missing arguments for REPLACE
If that's still not what you asked, please, provide sample test data and desired output.

How to easily remove count=1 on aliased field in SQL?

I have the following data in a table:
GROUP1|FIELD
Z_12TXT|111
Z_2TXT|222
Z_31TBT|333
Z_4TXT|444
Z_52TNT|555
Z_6TNT|666
And I engineer in a field that removes the leading numbers after the '_'
GROUP1|GROUP_ALIAS|FIELD
Z_12TXT|Z_TXT|111
Z_2TXT|Z_TXT|222
Z_31TBT|Z_TBT|333 <- to be removed
Z_4TXT|Z_TXT|444
Z_52TNT|Z_TNT|555
Z_6TNT|Z_TNT|666
How can I easily query the original table for only GROUP's that correspond to GROUP_ALIASES with only one Distinct FIELD in it?
Desired result:
GROUP1|GROUP_ALIAS|FIELD
Z_12TXT|Z_TXT|111
Z_2TXT|Z_TXT|222
Z_4TXT|Z_TXT|444
Z_52TNT|Z_TNT|555
Z_6TNT|Z_TNT|666
This is how I get all the GROUP_ALIAS's I don't want:
SELECT GROUP_ALIAS
FROM
(SELECT
GROUP1,FIELD,
case when instr(GROUP1, '_') = 2
then
substr(GROUP1, 1, 2) ||
ltrim(substr(GROUP1, 3), '0123456789')
else
substr(GROUP1 , 1, 1) ||
ltrim(substr(GROUP1, 2), '0123456789')
end GROUP_ALIAS
FROM MY_TABLE
GROUP BY GROUP_ALIAS
HAVING COUNT(FIELD)=1
Probably I could make the engineered field a second time simply on the original table and check that it isn't in the result from the latter, but want to avoid so much nesting. I don't know how to partition or do anything more sophisticated on my case statement making this engineered field, though.
UPDATE
Thanks for all the great replies below. Something about the SQL used must differ from what I thought because I'm getting info like:
GROUP1|GROUP_ALIAS|FIELD
111,222|,111|111
111,222|,222|222
etc.
Not sure why since the solutions work on my unabstracted data in db-fiddle. If anyone can spot what db it's actually using that would help but I'll also check on my end.
Here is one way, using analytic count. If you are not familiar with the with clause, read up on it - it's a very neat way to make your code readable. The way I declare column names in the with clause works since Oracle 11.2; if your version is older than that, the code needs to be re-written just slightly.
I also computed the "engineered field" in a more compact way. Use whatever you need to.
I used sample_data for the table name; adapt as needed.
with
add_alias (group1, group_alias, field) as (
select group1,
substr(group1, 1, instr(group1, '_')) ||
ltrim(substr(group1, instr(group1, '_') + 1), '0123456789'),
field
from sample_data
)
, add_counts (group1, group_alias, field, ct) as (
select group1, group_alias, field, count(*) over (partition by group_alias)
from add_alias
)
select group1, group_alias, field
from add_counts
where ct > 1
;
With Oracle you can use REGEXP_REPLACE and analytic functions:
select Group1, group_alias, field
from (select group1, REGEXP_REPLACE(group1,'_\d+','_') group_alias, field,
count(*) over (PARTITION BY REGEXP_REPLACE(group1,'_\d+','_')) as count from test) a
where count > 1
db-fiddle

How to fetch only a part of string

I have a column which has inconsistent data. The column named ID and it can have values such as
0897546321
ABC,0876455321
ABC,XYZ,0873647773
ABC,
99756
test only
The SQL query should fetch only Ids which are of 10 digit in length, should begin with a 08 , should be not null and should not contain all characters. And for those values, which have both digits and characters such as ABC,XYZ,0873647773, it should only fetch the 0873647773 . In these kind of values, nothing is fixed, in place of ABC, XYZ , it can be anything and can be of any length.
The column Id is of varchar type.
My try: I tried the following query
select id
from table
where id is not null
and id not like '%[^0-9]%'
and id like '[08]%[0-9]'
and len(id)=10
I am still not sure how should I deal with values like ABC,XYZ,0873647773
P.S - I have no control over the database. I can't change its values.
SQL Server generally has poor support regular expressions, but in this case a judicious use of PATINDEX is viable:
SELECT SUBSTRING(id, PATINDEX('%,08[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9],%', ',' + id + ','), 10) AS number
FROM yourTable
WHERE ',' + id + ',' LIKE '%,08[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9],%';
Demo
If you normalise your data, and split the delimited data into parts, you can achieve this some what more easily:
SELECT SS.value
FROM dbo.YourTable YT
CROSS APPLY STRING_SPLIT(YT.YourColumn,',') SS
WHERE LEN(SS.value) = 10
AND SS.value NOT LIKE '%[^0-9]%';
If you're on an older version of SQL Server, you'll have to use an alternative String Splitter method (such as a XML splitter or user defined inline table-value function); there are plenty of examples on these already on Stack Overflow.
db<>fiddle

SQL Query Remove Part of Path/Null

So I am new the whole SQL Query business but I need some help with two issues. My goal is to have anything in the Column "EnvironmentName" that has the word "Database" in Column "NodeName" to be displayed in the query results. I did this with
FROM [Backbone_ASPIDER].[dbo].[vw_CFGsvr_Con]
WHERE NodeName = 'Database'
ORDER BY EnvironmentName asc
WHERE NodePath
Results of Query:
I am able to get my query results but would like to remove the rows with NULL. I have tried to use "IS NOT NULL" but SQL Server Management Studio labeles this as "incorrect syntax."
What I have tried:
FROM [Backbone_ASPIDER].[dbo].[vw_CFGsvr_Con]
WHERE NodeName = 'Database'
ORDER BY EnvironmentName asc IS NOT NULL
WHERE NodePath
Thank you in advance!
Your query is pretty close..
1: You have to specify a specific column to not be null while using IS NOT NULL.
So modify your query to:
FROM [Backbone_ASPIDER].[dbo].[vw_CFGsvr_Con]
WHERE NodeName = 'Database' AND EnvironmentName IS NOT NULL
ORDER BY EnvironmentName asc
WHERE NodePath
2: Check out this article about trimming parts of strings from query results
http://basitaalishan.com/2014/02/23/removing-part-of-string-before-and-after-specific-character-using-transact-sql-string-functions/
Where clause will come first and Then order by statement
Like following way
Select * FROM [Backbone_ASPIDER].[dbo].[vw_CFGsvr_Con]
WHERE [Backbone_ASPIDER].[dbo].[vw_CFGsvr_Con].[NodeName] = 'Database' AND [Backbone_ASPIDER].[dbo].[vw_CFGsvr_Con].[EnvironmentName] IS NOT NULL
ORDER BY [Backbone_ASPIDER].[dbo].[vw_CFGsvr_Con].[EnvironmentName] asc
EDIT: I just noticed you removed this from your OP, so feel free to disregard if you took care of that.
I don't think anyone addressed the substring problem yet. There's several ways you could get at this depending on how complex the strings are you have to slice up, but here's how I'd do it
-- Populating some fake data, representative of what you've got
if object_id('tempdb.dbo.#t') is not null drop table #t
create table #t
(
nPath varchar(1000)
)
insert into #t
select '/Database/Mappings/Silver/Birthday' union all
select '/Database/Connections/Blue/Happy'
-- First, get the character index of the first '/' after as many characters the word '/database/' takes up.
-- You could have hard coded this value too. Add 1 to it so that it moves PAST the slash.
;with a as
(
select
ixs = charindex('/', nPath, len('/Database/') + 1),
-- Get everything to the right of what you just determined with all the charindex() stuff
ss = right(nPath, len(nPath) - charindex('/', nPath, len('/Database/') + 1)),
nPath
from #t
)
-- Now just take the left of the now-cleaned-up string from start to the first pipe
select
ixs,
ss,
color = left(ss, charindex('/', ss) -1),
nPath
from a