Convert semi-colon separated codes in a column to their values SQL - sql

I know this goes against all DB normalization principals but I cannot change the design at this point.
I have a column that has values stored like this (SQL Server database):
5;26;31;49
There's another table that has translation for this values which looks like this:
Code Value
-------------------
5 Some Value 1
26 Some Value 2
31 Some Value 3
49 Some Value 4
I need to convert the semi-colon delimited codes into their corresponding values and present these values as part of just 1 row, so what I want to see as a result is:
Some Value 1; Some Value 2; Some Value 3; Some Value 4
Does anyone have a solution for this puzzle?
Thanks.

Use string_split if you are on SQLSERVER,that transpose row to a column.The result set can be used as a table and joined with any table in the DB for desired result.
declare #str VARCHAR(20)= '5;26;31;49'
(SELECT VALUE FROM string_split(#str,';'));
When two tables are joined the result would look like the below,
select tablB.value from
(SELECT VALUE as code FROM string_split(tableA.col,';')) transposed_table,
tableB
where transposed_table.code = tableB.code
You may use cursor to loop through to transport the output to a string as below,
DECLARE #codevalues VARCHAR(max) = '';
DECLARE #codevalue VARCHAR(20)= '';
DECLARE db_cursor CURSOR FOR
select tablB.value+';' from
(SELECT VALUE as code FROM string_split(tableA.col,';')) transposed_table,
tableB
where transposed_table.code = tableB.code;
OPEN db_cursor
FETCH NEXT FROM db_cursor INTO #codevalue
WHILE ##FETCH_STATUS = 0
BEGIN
set #codevalues = #codevalues+#codevalue;
FETCH NEXT FROM db_cursor INTO #codevalue
END
CLOSE db_cursor
PRINT #codevalues;
DEALLOCATE db_cursor

In the most recent versions of SQL Server, you can use string_split() and string_agg():
select t.*, v.expanded
from t cross apply
(select string_agg(c.value, ';') within group (order by charindex(c.code, t.codes) as expanded
from string_split(t.codes, ';') join
codes c
on c.code = s.value
) v;

Related

Multiple tables in the where clause that start with the same string

I'm using sql-server 2014 and am trying to select ID values from a table where the ID values do not exist in hundreds of other tables which all start with the same string (e.g. MyTable1, MyTable2, MyTable3, etc.)
My current query is something like this...
select ID from want w
where not exists (select 'x' from MyTable1 m1 where m1.ID = w.id)
and not exists (select 'x' from MyTable2 m2 where m2.ID = w.id)
I'm looking for something like
select ID from want w
where not exists (select 'x' from 'MyTable%' m where m.ID = w.id)
Or
select ID from want w
where ID not in
(select ID from 'MyTable%')
Thank you!
You do mention that there are hundreds of other tables, is the number of tables increasing? Using INFORMATION_SCHEMA.TABLES as already mentioned in the comments in your question along with dynamic SQL would be a start but also look up cursors for looping through the table names with the result-set returned from INFORMATION_SCHEMA.TABLES. The code fragment below will loop through the table listing from information_schema on the default database. You'll have to add your own code depending on what you want to do where the 'if' statement is. The variable #Table_Name has the name of the table, so you would need to include this in a dynamic SQL string and execute this to get your unused ids.
declare #table_name varchar(256);
DECLARE db_cursor CURSOR FOR
SELECT TABLE_NAME
FROM [Datasets].INFORMATION_SCHEMA.TABLES
;
OPEN db_cursor
FETCH NEXT FROM db_cursor INTO #TABLE_NAME
WHILE ##FETCH_STATUS = 0
BEGIN
if(#Table_Name like 'Airline%')
print('Have table name to work with: ' + #Table_Name);
else
print('Ignoring table: ' + #Table_name);
FETCH NEXT FROM db_cursor INTO #TABLE_NAME
END
CLOSE db_cursor
DEALLOCATE db_cursor

SQL Server - how to select while skipping other data

I have data with following data
In this round 1 and 2 is related with course 231 and 776.
But I need to select output as
means if round 1 selected with field 231, it must skip other row of round with 1 and also skip the row with field with 231.
But round 3 and 4 has single record, then it should be included in output.
How can I achieve this result?
thanks
EDIT:
in case of round 3 has multiple record, then it table would be like:
and needed output is:
I wouldn't prefer to use cursor but for this situation, I couldn't provide a single query to retrieve desired output. So, you can try like this;
declare #round int
declare #field int
declare #selectedFields table (field int)
declare #expectedRounds table (Round int, field int)
DECLARE db_cursor CURSOR FOR
select * from (
select Round,min(Field) as Field from SampleTable group by Round having count(*) > 1) Records order by Round
insert into #expectedRounds
select Round,max(field) from SampleTable group by Round having count(*) = 1
OPEN db_cursor
FETCH NEXT FROM db_cursor INTO #round, #field
WHILE ##FETCH_STATUS = 0
BEGIN
declare #selectedField int
declare #selectedRound int
select top 1 #selectedRound = Round, #selectedField = field from SampleTable where Round = #round and field not in (select field from #selectedFields) order by Field asc
insert into #expectedRounds (Round, field) VALUES (#selectedRound, #selectedField)
insert into #selectedFields (field) VALUES (#selectedField)
FETCH NEXT FROM db_cursor INTO #round, #field
END
CLOSE db_cursor
DEALLOCATE db_cursor
select * from #expectedRounds order by Round asc
You could use something like this:
SELECT [year]
,[round]
,MIN([field])
FROM TABLE
GROUP BY [year]
,[round]
ORDER BY [year]
,[round]

How to iterate an int column for each distinct value in SQL?

I've been trying a query to update my current database but I can't figure out how to do it. I've been trying with cursors but I can't find a way to isolate each row and not set all the rows of the distinct column to the same value.
Here is what I have in my database:
RecoNumber Item
ABIBAQC-01 1
ABIBAQC-01 1
ABIBAQC-01 1
ABIBAQC-02 1
ABIBAQC-03 1
ABIBAQC-03 1
And I would like it to become:
RecoNumber Item
ABIBAQC-01 1
ABIBAQC-01 2
ABIBAQC-01 3
ABIBAQC-02 1
ABIBAQC-03 1
ABIBAQC-03 2
Like I said, I've tried with a cursor but I am missing something to make it work properly.
DECLARE #NUMBER INT
DECLARE #RECO NVARCHAR(10)
DECLARE #RECO_OLD NVARCHAR(10)
DECLARE db_cursor CURSOR FOR
SELECT DISTINCT RecoNumber
FROM Workbook2014_Test.dbo.Reco
OPEN db_cursor
FETCH NEXT FROM db_cursor INTO #RECO
SET #NUMBER = 1
SET #RECO_OLD = #RECO
WHILE ##FETCH_STATUS = 0
BEGIN
UPDATE Workbook2014_Test.dbo.Reco
SET Item = #NUMBER
WHERE RecoNumber = #RECO
FETCH NEXT FROM db_cursor INTO #RECO
IF(#RECO != #RECO_OLD)
SET #NUMBER = 1
ELSE
SET #NUMBER = #NUMBER + 1
END
CLOSE db_cursor
DEALLOCATE db_cursor
Thanks in advance for any tips.
You can use the ROW_NUMBER() function for this:
;WITH cte AS (SELECT *,ROW_NUMBER() OVER(PARTITION BY RecoNumber ORDER BY RecoNumber) AS UPD_Item
FROM YourTable
)
UPDATE cte
SET Item = UPD_Item
The ROW_NUMBER() function assigns a number to each row. PARTITION BY is optional, but used to start the numbering over for each value in a given field or group of fields, ie: if you PARTITION BY Some_Date then for each unique date value the numbering would start over at 1. ORDER BY of course is used to define how the counting should go, and is required in the ROW_NUMBER() function.
You can SELECT * FROM cte first to observe the new values before running the UPDATE, you may have other field values you'd prefer to ORDER BY.

SQL omit columns with all same value

Is there a way to write a SQL query to omit columns that have all of the same values? For example,
row A B
1 9 0
2 7 0
3 5 0
4 2 0
I'd like to return just
row A
1 9
2 7
3 5
4 2
Although it is possible to use SQL to find if all rows in a column have identical values, there is no way to make a fixed SQL statement not return a column based on the content of the query.
Here is how to find out if all items in a column have identical values:
SELECT COUNT(row)=COUNT(DISTINCT B) FROM my_table
You can run a preliminary query to see if a column needs to be displayed, and then form the query dynamically, including the column only when you need it.
The only way to change what columns are returned is by executing separate queries. So you'd have to do something like:
IF EXISTS(SELECT null FROM myTable WHERE B <> 0)
BEGIN
SELECT row, A, B FROM myTable
END
ELSE
BEGIN
SELECT row, A FROM myTable
END
However it's generally bad practice to return different columns based on the data - otherwise you make the client determine if a particular column is in the result set first before trying to access the data.
This sort of requirement is more commonly done when displaying the data, e.g. in a web page, in a report, etc.
There is no way to write a query statement that will only return columns that have disparate values. You can however use some conditonnal statements to execute different queries based on your needs.
You could also insert your query result into a temporary table, loop over the columns, build a new select statement that includes only the columns that have different values and finally execute the statement.
Note: You should probably just include bit columns to indicate wheter columns are all duplicates or not. The application could then just discard any column that has been indicated as all duplicates.
Anyway, here's an example solution for SQL SERVER
-- insert results into a temp table
SELECT *
INTO #data
FROM (
SELECT 1 AS col1, 1 AS col2, 2 AS col3
UNION ALL
SELECT 2, 1, 3
) d;
DECLARE
#column sysname,
#sql nvarchar(max) = '',
#finalSql nvarchar(500) = 'SELECT ',
#allDuplicates bit;
DECLARE colsCur CURSOR
FOR
SELECT name
FROM tempdb.sys.columns
WHERE object_id = OBJECT_ID('tempdb..#data');
OPEN colsCur;
FETCH NEXT FROM colsCur INTO #column;
WHILE ##FETCH_STATUS = 0
BEGIN
SET #sql = N'SELECT #allDuplicates = CASE COUNT(DISTINCT ' + #column + ') WHEN 1 THEN 1 ELSE 0 END FROM #data';
EXEC sp_executesql #sql, N'#allDuplicates int OUT', #allDuplicates = #allDuplicates OUT;
IF #allDuplicates = 0 SET #finalSql = #finalSql + #column + ',';
FETCH NEXT FROM colsCur INTO #column;
END
CLOSE colsCur;
DEALLOCATE colsCur;
SET #finalSql = LEFT(#finalSql, LEN(#finalSql) - 1) + ' FROM #data';
EXEC sp_executesql #finalSql;
DROP TABLE #data;

SQL Server 2000 - Breaking out of a loop

I am not good at SQL Server 2000. I have a comma-delimited list of ids. I need to see if that ID exists in a table. If it does, I want to break out of the loop with that ID saved in a variable that I can use in my stored procedure. This is what I am trying right now:
DECLARE #coreID INT
SET #coreID=NULL
DECLARE #itemID NVARCHAR(50)
DECLARE itemCursor CURSOR LOCAL FAST_FORWARD FOR
SELECT [String] AS 'itemID' FROM dbo.SplitListIntoTable(#myIDs)
OPEN itemCursor
FETCH NEXT FROM itemCursor INTO #itemID
WHILE ##FETCH_STATUS = 0 BEGIN
-- If #itemID EXISTS IN MyTable set #coreID=#itemID and Break. How do I do this?
FETCH NEXT FROM itemCursor INTO #itemID
END
CLOSE itemCursor
DEALLOCATE itemCursor
Thank you!
Ideally, you shouldn't use a cursor as performance won't be great. If you can do it as a set-based statement, do that instead, maybe like this:
SELECT TOP 1 #CoreID = [String]
FROM dbo.SplitListIntoTable(#myIDs) x
JOIN MyTable t ON x.[String] = t.ID
However, if you have a real reason to use a cursor, you can use the BREAK statement to break out of a WHILE loop
e.g.
WHILE ##FETCH_STATUS = 0
BEGIN
IF EXISTS(SELECT * FROM MyTable WHERE Id = #ItemID)
BEGIN
SET #CoreId = #ItemId
BREAK
END
FETCH NEXT FROM itemCursor INTO #itemID
END
I don't know how to do this using a cursor, but I supect you can do this much better (faster) with a a join. If the output of dbo.SplitListIntoTable(#myIDs) is actually an odered table, then you can output a table with another column what is say the string numer, 1, 2, 3, etc...
(I don't have sql in front of me to test this but something like)
create table t(itemNum int identity, itemId nvarchar(max))
insert into t (item id) select 1 from dbo.SplitListIntoTable(#myIDs)
Then join the two and take the top one
set #coreID =
select top 1 #itemID
from MyTable m
inner join t t.itemid = m.itemid
order by m.itemNum asc
of course you could use a CTE, table var or temp table too.