Increment a Counter within a SQL SELECT Case Statement Query - sql

I have some sample data that gives the following results:
**Name**
NULL
Bob
Steven
Jane
Susan
What should happen is the query should convert the NULLs and empty strings to a value of Anom[Counter], so it would end up like this:
**Name**
Anom1
Bob
Steven
Jane
Anom2
Susan
The basic query I have is the below, but how can I auto-increment the number?
select
CASE WHEN Name is NULL or Name = '' THEN 'Anom1' Else Name END
from
Names
Sample data
Create Table Names
(
Name varchar(50) NULL
)
insert into Names
(
Name
)
select
NULL
union all
select
'Bob'
union all
select
'Steven'
union all
select
'Jane'
union all
select
''
union all
select
'Susan'

use row_number() to generate the running number and concat to concatenate it with the string Anom
SELECT CASE WHEN [Name] IS NULL
OR [Name] = ''
THEN CONCAT('Anom', ROW_NUMBER() OVER (PARTITION BY NULLIF([Name], '')
ORDER BY [Name]))
ELSE [Name]
END
FROM [Names]

You can achieve this with a row_number like so
select IsNull(NullIf(Name,''),Concat('Anon', Row_Number() over(partition by IsNull(NullIf(Name,''),'Anon') order by Name) ) ) Names
from names
order by [name]

Related

Put numbers for each rows in STRING_AGG in SQL Server

I have to put some columns and group them together by STRING_AGG also I want to put number first of each rows
What I have:
Name
Cake
Coca
ice-cream
Same
one
five
six
Sara
one
one
NULL
John
two
two
NULL
I want the output be something like this:
Name
Description
Sam
1.two 2.five 3. six
Sara
1.one 2.one
John
1.two 2.two
My Code:
SELECT Name, STRIN_AGG(CONCAT(Cake, ' ,', Coca,' ,', ice-cream))
FROM FoodTable
but I do not know how to consider numbers first of each rows in STRING_AGG
You don't need string_agg():
select name,
concat('1.' + cake, ' 2.' + coca, ' 3.' + ice_cream)
from t;
Note that + returns NULL if any value is NULL. However, concat() simply ignores NULL values.
If you really, really wanted to use string_agg() you could:
select t.name, v.all_together
from t cross apply
(select string_agg(v.n + v.val, ' ') within group (order by v.n) as all_together
from (values ('1.', t.cake),
('2.', t.coca),
('3.', t.ice_cream)
) v(n, val)
) v;
Here is a complete dynamic sql approach. No need to serialize and deserialize the data using XML or JSON. In this case the list of food items is contained in a temporary table so it reads the column names from tempdb.sys.columns.
The query uses CROSS APPLY to unpivot the columns (of food items) and assigns a ROW_NUMBER() to each non NULL item value. Something like this
drop table if exists #FoodTable;
go
create table #FoodTable(
[Name] varchar(100) not null,
Cake varchar(100) null,
Coca varchar(100) null,
[ice-cream] varchar(100) null);
--select * from dbo.test_actuals
insert #FoodTable values
('Sam', 'one', 'five', 'six'),
('Sara', 'one', 'one', null),
('Jon', 'two', 'two', null);
;with unpvt_cte([Name], item, val, rn) as (
select f.[Name], v.*, row_number() over (partition by [Name] order by (select null))
from #FoodTable f
cross apply (values ('Cake', Cake),
('Coca', Coca),
('IceCream', [ice-cream])) v(item, val)
where v.val is not null)
select [Name], string_agg(concat(rn, '.', val), ' ') within group (order by rn) answer
from unpvt_cte
group by [Name];
Name answer
Jon 1.two 2.two
Sam 1.one 2.five 3.six
Sara 1.one 2.one
to make the query dynamic
declare #food_list nvarchar(max);
select #food_list=string_agg(quotename(concat_ws(',', quotename(sysc.[name], ''''),
quotename(sysc.[name], '[]')), '()'), ',')
from tempdb.sys.columns sysc
where object_id = Object_id('tempdb..#FoodTable')
and [name]<>'Name';
declare
#sql_prefix nvarchar(max)=N'
;with unpvt_cte([Name], item, val, rn) as (
select f.[Name], v.*, row_number() over (partition by [Name] order by (select null))
from #FoodTable f
cross apply (values ',
#sql_suffix nvarchar(max)=N'
) v(item, val)
where v.val is not null)
select [Name], string_agg(concat(rn, ''.'', val), '' '') within group (order by rn) answer
from unpvt_cte
group by [Name];';
declare
#sql nvarchar(max)=concat(#sql_prefix, #food_list, #sql_suffix);
print(#sql);
exec sp_executesql #sql;
The print statement outputs the following
;with unpvt_cte([Name], item, val, rn) as (
select f.[Name], v.*, row_number() over (partition by [Name] order by (select null))
from #FoodTable f
cross apply (values ('Cake',[Cake]),('Coca',[Coca]),('ice-cream',[ice-cream])
) v(item, val)
where v.val is not null)
select [Name], string_agg(concat(rn, '.', val), ' ') within group (order by rn) answer
from unpvt_cte
group by [Name];
You may use a union to acquire the numbers for each column. Here I've used a cte but you could have used a subquery. Each query in the union renames the food type column to food and adds a column num that will be used in the final query. In the final query the where clause filters NULL foods and a group by with the string_agg and concat is used to retrieve the data in the desired format. I've included a working fiddle below:
WITH FoodTableNums AS (
SELECT Name, Cake as food, 1 as num FROM FoodTable UNION ALL
SELECT Name, Coca as food, 2 as num FROM FoodTable UNION ALL
SELECT Name, icecream as food, 3 as num FROM FoodTable
)
SELECT
Name,
STRING_AGG(CONCAT(num,'.', food),',' ) WITHIN GROUP( ORDER BY num asc) as Description
FROM
FoodTableNums
WHERE
food IS NOT NULL
GROUP BY
Name
Name
Description
John
1.two,2.two
Sam
1.one,2.five,3.six
Sara
1.one,2.one
db<>fiddle here
Let me know if this works for you.
Here is an option that is a bit more dynamic. You only have to Exclude certain columns ... in this case NAME
We use a bit of JSON to dynamically UNPIVOT the row, and then string_agg() to consolidate.
Example or dbFiddle
Select A.Name
,B.NewValue
From YourTable A
Cross Apply (
Select NewValue=STRING_AGG(concat(Seq,'.',Value),' ') within group (order by Seq)
From (
Select [Key]
,[Value]
,[Seq] = row_number() over (order by ##spid)
From OpenJson( (Select A.* For JSON Path,Without_Array_Wrapper ) )
Where [Key] not in ('Name')
) B1
) B
Results
Name NewValue
Same 1.one 2.five 3.six
Sara 1.one 2.one
John 1.two 2.two
Here is s generic way regardless of how many columns in a table.
It is based on XML/XQuery.
No need to UNPIVOT the rows, and then STRING_AGG() to consolidate.
All data in each row stays in a row.
SQL
-- DDL and data population, start
DECLARE #tbl table (
[Name] varchar(100) not NULL PRIMARY KEY,
Cake varchar(100) null,
Coca varchar(100) null,
[ice-cream] varchar(100) null);
INSERT #tbl VALUES
('Sam', 'one', 'five', 'six'),
('Sara', 'one', 'one', null),
('Jon', 'two', 'two', null);
-- DDL and data population, end
SELECT p.[Name]
, x.query('
for $r in /root/*[local-name()!="Name"]/text()
let $pos := count(root/*[. << $r]) - 1
return concat(string($pos), ".", $r)').value('text()[1]', 'VARCHAR(MAX)') AS Result
FROM #tbl AS p
CROSS APPLY (SELECT * FROM #tbl AS c
WHERE c.[Name] = p.[Name]
FOR XML PATH(''), TYPE, ROOT('root')) AS t(x);
Output
+------+--------------------+
| Name | Result |
+------+--------------------+
| Jon | 1.two 2.two |
| Sam | 1.one 2.five 3.six |
| Sara | 1.one 2.one |
+------+--------------------+
Although I agree with those saying that it would be much better to normalise your tables, but if you can't do that then this proposal makes GGordon's solution dynamic, building an SQL statement that retrieves all the columns on your FoodTable. No matter if they are 3 or 100 food columns.
CREATE TABLE FoodTable (
Name VARCHAR(4),
Cake VARCHAR(3),
Coca VARCHAR(4),
icecream VARCHAR(4)
);
INSERT INTO FoodTable ("Name", "Cake", "Coca", "icecream")
VALUES ('Sam', 'one', 'five', 'six'),
('Sara', 'one', 'one', NULL),
('John', 'two', 'two', NULL);
declare #SQL nvarchar(max);
WITH Food As (
SELECT ORDINAL_POSITION - 1 AS Num, COLUMN_NAME AS Food
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = N'FoodTable' AND COLUMN_NAME <> 'Name'
)
SELECT #SQL = N'WITH FoodTableNums AS ( ' +
string_agg('SELECT Name, ' + Food + ' as Food, ' + convert(varchar(20), Num) + ' as Num FROM FoodTable', ' UNION ALL ') +
') SELECT Name, STRING_AGG(CONCAT(num,''.'', food),'','' ) WITHIN GROUP( ORDER BY num asc) as Description FROM FoodTableNums WHERE food IS NOT NULL GROUP BY Name'
FROM Food;
EXECUTE sp_ExecuteSQL #SQL;
You can see it working here : Fiddle

distinct and sum if like

I have a table as the following
name
-----------
1#apple#1
2#apple#2
3#apple#4
4#box#4
5#box#5
and I want to get the result as:
name
--------------
apple 3
box 2
Thanks in advance for your help
This is what you need.
select
SUBSTRING(
name,
CHARINDEX('#', name) + 1,
LEN(name) - (
CHARINDEX('#', REVERSE(name)) + CHARINDEX('#', name)
)
),
count(1)
from
tbl
group by
SUBSTRING(
name,
CHARINDEX('#', name) + 1,
LEN(name) - (
CHARINDEX('#', REVERSE(name)) + CHARINDEX('#', name)
)
)
If your data does not contain any full stops (or periods depending on your vernacular), and the length of your string is less than 128 characters, then you can use PARSENAME to effectively split your string into parts, and extract the 2nd part:
DECLARE #T TABLE (Val VARCHAR(20));
INSERT #T (Val)
VALUES ('1#apple#1'), ('2#apple#2'), ('3#apple#4'),
('4#box#4'), ('5#box#5');
SELECT Val = PARSENAME(REPLACE(t.Val, '#', '.'), 2),
[Count] = COUNT(*)
FROM #T AS t
GROUP BY PARSENAME(REPLACE(t.Val, '#', '.'), 2);
Otherwise you will need to use CHARINDEX to find the first and last occurrence of # within your string (REVERSE is also needed to get the last position), then use SUBSTRING to extract the text between these positions:
DECLARE #T TABLE (Val VARCHAR(20));
INSERT #T (Val)
VALUES ('1#apple#1'), ('2#apple#2'), ('3#apple#4'),
('4#box#4'), ('5#box#5');
SELECT Val = SUBSTRING(t.Val, x.FirstPosition + 1, x.LastPosition - x.FirstPosition),
[Count] = COUNT(*)
FROM #T AS t
CROSS APPLY
( SELECT CHARINDEX('#', t.Val) ,
LEN(t.Val) - CHARINDEX('#', REVERSE(t.Val))
) AS x (FirstPosition, LastPosition)
GROUP BY SUBSTRING(t.Val, x.FirstPosition + 1, x.LastPosition - x.FirstPosition);
use case when
select case when name like '%apple%' then 'apple'
when name like '%box%' then 'box' end item_name,
count(*)
group by cas when name like '%apple%' then 'apple'
when name like '%box%' then 'box' end
No DBMS specified, so here is a postgres variant. The query does use regexps to simplify things a bit.
with t0 as (
select '1#apple#1' as value
union all select '2#apple#2'
union all select '3#apple#4'
union all select '4#box#4'
union all select '5#box#5'
),
trimmed as (
select regexp_replace(value,'[0-9]*#(.+?)#[0-9]*','\1') as name
from t0
)
select name, count(*)
from trimmed
group by name
order by name
DB Fiddle
Update
For Oracle DMBS, the query stays basically the same:
with t0 as (
select '1#apple#1' as value from dual
union all select '2#apple#2' from dual
union all select '3#apple#4' from dual
union all select '4#box#4' from dual
union all select '5#box#5' from dual
),
trimmed as (
select regexp_replace(value,'[0-9]*#(.+?)#[0-9]*','\1') as name
from t0
)
select name, count(*)
from trimmed
group by name
order by name
NAME | COUNT(*)
:---- | -------:
apple | 3
box | 2
db<>fiddle here
Update
MySQL 8.0
with t0 as (
select '1#apple#1' as value
union all select '2#apple#2'
union all select '3#apple#4'
union all select '4#box#4'
union all select '5#box#5'
),
trimmed as (
select regexp_replace(value,'[0-9]*#(.+?)#[0-9]*','$1') as name
from t0
)
select name, count(*)
from trimmed
group by name
order by name
name | count(*)
:---- | -------:
apple | 3
box | 2
db<>fiddle here
You can use case and group by to do the same.
select new_col , count(new_col)
from
(
select case when col_name like '%apple%' then 'apple'
when col_name like '%box%' then 'box'
else 'others' end new_col
from table_name
)
group by new_col
;

MS SQL - How to order results by wildcard?

Query:
SELECT [Name]
FROM [dbo].[City]
where name like '%laus%'
Results:
How to order so records with leading wildcard (3,4) are first?
Your may try, but best way use full-text-search
SELECT [Name]
FROM [City]
where name like '%laus%'
ORDER BY
CHARINDEX('laus',name)
Try This
;WITH CTE(name )
AS
(
SELECT 'Berlin' UNION ALL
SELECT 'Laura' UNION ALL
SELECT 'Losangels' UNION ALL
SELECT 'Lausanne' UNION ALL
SELECT 'Lausen' UNION ALL
SELECT 'Roamanel' UNION ALL
SELECT 'Sankt Niklaus' UNION ALL
SELECT 'Vennes sur-Lausanne'
)
SELECT * FROM CTE
ORDER BY (CASE WHEN name like 'Laus%' THEN 1 END ) DESC
Result
name
--------
Lausanne
Lausen
Losangels
Laura
Roamanel
Sankt Niklaus
Vennes sur-Lausanne
Berlin
DECLARE #City TABLE(Name VARCHAR(32))
INSERT #City VALUES
('Belmont-sur-Lausanne'),
('Lausanne'),
('Lausen'),
('Le Mont-sur-Lausanne'),
('Berlin')
SELECT [Name]
FROM #City
--where name like '%laus%'
order by CASE WHEN PATINDEX('%laus%', name) = 0
THEN LEN(name)
ELSE PATINDEX('%laus%', name)
END
,name

SQL select statement with a quotename removing the last character on the last row

I have a select statement that uses QUOTNAME to add single quotes and a comma to each of the results.
SELECT QUOTENAME(field1,'''')+',' AS [1]
Which changes the results from this.
1
11111
22222
33333
44444
To this
1
'11111',
'22222',
'33333',
'44444',
However I would like to know if it is possible to remove the comma from the very last row? Too look like this.
1
'11111',
'22222',
'33333',
'44444'
edit: I should have mentioned this is a View
SELECT QUOTENAME(field1,'''')+
case when row_number() over(order by (select 1))=
count(*) over () then '' else ',' end AS [1]
FROM <table>
Try something like this
DECLARE #count int
SELECT #count = COUNT(*) FROM my_table
SELECT QUOTENAME(field1, '''') + CASE WHEN ROW_NUMBER() OVER (ORDER BY field1) < #count THEN ',' ELSE '' END AS [1]
FROM my_table
I'd like to say that SQL is the wrong place to be formatting your data and that it should be done in your application or client.
But as you've asked it is possible and this is one way of doing it:
WITH
MyData AS (
SELECT
field1,
ROW_NUMBER() OVER(ORDER BY field1 DESC) AS rowNo
FROM Data
)
SELECT
CONCAT(
QUOTENAME( field1, '''' ),
(CASE WHEN rowNo <> 1 THEN ',' END)
) AS [1]
FROM MyData
ORDER BY rowNo DESC;
SQL Fiddle

Tricky SQL query requiring search for contains

I have data such as this:
Inventors column in my table
Hundley; Edward; Ana
Isler; Hunsberger
Hunsberger;Hundley
Names are separated by ;. I want to write a SQL query which sums up the count.
Eg. The result should be:
Hundley 2
Isler 1
Hunsberger 2
Edward 1
Ana 1
I could do a group by but this is not a simple group by as you can see. Any ideas/thoughts on how to get this output?
Edit: Changed results so it doesn't create any confusion that a row only contains 2 names.
You can take a look at this. I certainly do not recommend this way if you have lots of data, BUT you can do some modifications and use it and it works like a charm!
This is the new code for supporting unlimited splits:
Declare #Table Table (
Name Nvarchar(50)
);
Insert #Table (
Name
) Select 'Hundley; Edward; Anna'
Union Select 'Isler; Hunsberger'
Union Select 'Hunsberger; Hundley'
Union Select 'Anna'
;
With Result (
Part
, Remained
, [Index]
, Level
) As (
Select Case When CharIndex(';', Name, 1) = 0
Then Name
Else Left(Name, CharIndex(';', Name, 1) - 1)
End
, Right(Name, Len(Name) - CharIndex(';', Name, 1))
, CharIndex(';', Name, 1)
, 1
From #Table
Union All
Select LTrim(
Case When CharIndex(';', Remained, 1) = 0
Then Remained
Else Left(Remained, CharIndex(';', Remained, 1) - 1)
End
)
, Right(Remained, Len(Remained) - CharIndex(';', Remained, 1))
, CharIndex(';', Remained, 1)
, Level
+ 1
From Result
Where [Index] <> 0
) Select Part
, Count(*)
From Result
Group By Part
Cheers
;with cte as
(
select 1 as Item, 1 as Start, CHARINDEX(';',inventors, 1) as Split, Inventors from YourInventorsTable
union all
select cte.Item+1, cte.Split+1, nullif(CHARINDEX(';',inventors, cte.Split+1),0), inventors as Split
from cte
where cte.Split<>0
)
select rTRIM(lTRIM(SUBSTRING(inventors, start,isnull(split,len(inventors)+1)-start))), count(*)
from cte
group by rTRIM(lTRIM(SUBSTRING(inventors, start,isnull(split,len(inventors)+1)-start)))
You can create a split function to split the col values
select splittedValues.items,count(splittedValues) from table1
cross apply dbo.split(col1,';') splittedValues
group by splittedValues.items
DEMO in Sql fiddle
first make one function who take your comma or any other operator(;) separated string into one table and by using that temp table, apply GROUP function on that table.
So you will get count for separate value.
"select d.number,count(*) from (select number from dbo.CommaseparedListToTable('Hundley;Edward;Ana;Isler;Hunsberger;Hunsberger;Hundley',';'))d
group by d.number"
declare #text nvarchar(255) = 'Edward; Hundley; AnaIsler; Hunsberger; Hunsberger; Hundley ';
declare #table table(id int identity,name varchar(50));
while #text like '%;%'
Begin
insert into #table (name)
select SUBSTRING(#text,1,charindex(';',#text)-1)
set #text = SUBSTRING(#text, charindex(';',#text)+1,LEN(#text))
end
insert into #table (name)
select #text
select name , count(name ) counts from #table group by name
Output
name count
AnaIsler 1
Hundley 2
Hunsberger 2
Edward 1