SQL - How to use Comma separated column values in a where clause - sql

I have a table called Configuration. It contains the values like below,
Id SourceColumns TargetColumns SourceTable TargetTable
1 Name, Age CName, CAge STable TTable
2 EId EmplId EmpTable TTable
In a stored procedure, I have to get the column names from the above table and I have to compare the source table and target table.
I am able to do that easily for the 2nd record as it has only one column name, so in the where clause I can write sourcecolumn = targetcolumn like,
SELECT
EId
, EmplId
FROM
EmpTable E
JOIN TTable T ON E.Eid = T.EmplId
The first record in the table has 2 columns separated by comma (,).
I have to compare like this,
SELECT
Name
, Age
FROM
STable S
JOIN TTable T ON S.Name = T.CName AND S.Age = T.CAge
In some cases the source columns and target columns may have more column names separated by comma(,)
Please help me on this.

As I don't know whether you have completely understood the data model I suggested in the request comments and in order to properly answer the question:
Your table is not normalized, as the data in the columns SourceColumns and TargetColumns is not atomic. And one even has to interpret the data (the separator is the comma and the nth element in one column relates to the nth element in the other column).
This is how your tables should look like instead (the create statements are pseudo code):
create table configuration_tables
(
id_configuration_tables int,
source_table text,
target_table text,
primary key (id_configuration_tables),
unique key (source_table),
unique key (target_table) -- or not? in your sample two souce table map to the same target table
);
create table configuration_columns
(
id_configuration_columns int,
id_configuration_tables int,
source_column text,
target_column text,
primary key (id_configuration_columns),
foreign key (id_configuration_tables) references configuration_tables (id_configuration_tables)
);
Your sample data would then become
configuration_tables
id_configuration_tables | source_table | target_table
------------------------+--------------+-------------
1 | STable | TTable
2 | EmpTable | TTable
configuration_columns
id_configuration_columns | id_configuration_tables | source_column | target_column
-------------------------+-------------------------+---------------+--------------
1 | 1 | Name | CName
2 | 1 | Age | CAge
3 | 2 | EId | EmplId
As of SQL Server 2017 you can use STRING_AGG to create your queries is. In earlier versions this was also possible with some STRING_AGG emulation you will easily find wit Google or SO.
select
'select s.' + string_agg (c.source_column + ', t.' + c.target_column, ', ') +
' from ' + t.source_table + ' s' +
' join ' + t.target_table + ' t' +
' on ' + string_agg('t.' + c.target_column + ' = s.' + c.source_column, ' and ') +
';' as query
from configuration_tables t
join configuration_columns c on c.id_configuration_tables = t.id_configuration_tables
group by t.source_table, t.target_table
order by t.source_table, t.target_table;
Demo: https://dbfiddle.uk/?rdbms=sqlserver_2019&fiddle=8866b2485ba9bba92c2391c67bb8cae0

Related

SQL Query to Fetch only column with value equals to current date

Need SQL logic
I have table with Column name EID, Emp_name, 1,2,3,4,5,6,7....till 31 (Column 1 to 31 are calendar dates)
now i m trying to fetch only 3 column EID, EMP_name and date which is equals to sys date.
Example
Todays SYS_date is 2nd-Jan-2019
and i need column with value = 2 like this...
EID Emp_name 2 |
123 James SCOTT P |
133 Mark M A |
133 Mark Man P |
Try like this:
declare #sql nvarchar(max)
set #sql = 'select EID, Emp_name, [' + convert(nvarchar(2),day(getdate())) + '] as d from tableName '
exec(#sql)
One method to approach this is to unpivot the data. Here is one method:
select t.eid, t.emp_name, v.n
from t cross apply
(values ([1]), ([2]), ([3]), . . ., ([4])
) v(n)
where v.n = day(getdate());
I will caution that the column name is a fixed name, not 2. SQL queries have fixed column names. You would need to use dynamic sql to get a variable column name.

Combining duplicate records in SQL Server

I have a table in SQL Server 2012 that holds a list of parts, location of the parts and the quantity on hand. The problem I have is someone put a space in front of the location when they added it to the database. This allowed there to be two records.
I need to create a job that will find the parts with spaces before the location and add those parts to the identical parts without spaces in front of the location. I'm not quite sure where to even start with this.
This is the before:
Partno | PartRev | Location | OnHand | Identity_Column
--------------------------------------------------------------------
0D6591D 000 MV3 55.000 103939
0D6591D 000 MV3 -55.000 104618
This is what I would like to have after the job ran:
Partno | PartRev | Location | OnHand | Identity_Column
--------------------------------------------------------------------
0D6591D 000 MV3 0 104618
Two steps: 1. update the records with the correct locations, 2. delete the records with the wrong locations.
update mytable
set onhand = onhand +
(
select coalesce(sum(wrong.onhand), 0)
from mytable wrong
where wrong.location like ' %'
and trim(wrong.location) = mytable.location
)
where location not like ' %';
delete from mytable where location like ' %';
You can do some grouping with a HAVING clause on to identify the records. I've used REPLACE to replace spaces with empty strings in the location column, you could also use LTRIM and RTRIM:
CREATE TABLE #Sample
(
[Partno] VARCHAR(7) ,
[PartRev] INT ,
[Location] VARCHAR(5) ,
[OnHand] INT ,
[Identity_Column] INT
);
INSERT INTO #Sample
([Partno], [PartRev], [Location], [OnHand], [Identity_Column])
VALUES
('0D6591D', 000, ' MV3', 55.000, 103939),
('0D6591D', 000, 'MV3', -55.000, 104618)
;
SELECT Partno ,
PartRev ,
REPLACE( Location, ' ', '') Location,
SUM(OnHand) [OnHand]
FROM #Sample
GROUP BY REPLACE(Location, ' ', '') ,
Partno ,
PartRev
HAVING COUNT(Identity_Column) > 1;
DROP TABLE #Sample;
Produces:
Partno PartRev Location OnHand
0D6591D 0 MV3 0

Append "_Repeat" to Ambiguous column names

I have a query that joins a table back onto itself in order to display orders that generated a repeat within a certain window.
The table returns something like the following:
id | value | note | id | value | note
------------------------------------------------------
01 | abcde | .... | 03 | zyxxx | ....
06 | 12345 | .... | 09 | 54321 | ....
In actuality, the table returns over 150 columns, so when the join occurs, I end up with 300 columns. I end up having to manually rename 150 columns to "id_Repeat","value_Repeat","note_Repeat" etc...
I'm looking for some way of automatically appending "_Repeat" to the ambiguous columns. Is this possible in T-SQL, (Using SQL Server 2008) or will I have to manually map out each column using:
SELECT [value] AS [value_Repeat]
The only way I can see this working is to construct some dynamic SQL (ugh!). I put together a quick example of how this might work:
CREATE TABLE test1 (id INT, note VARCHAR(50));
CREATE TABLE test2 (id INT, note VARCHAR(20));
INSERT INTO test1 SELECT 1, 'hello';
INSERT INTO test2 SELECT 1, 'world';
DECLARE #SQL VARCHAR(4096);
SELECT #SQL = 'SELECT ';
SELECT #SQL = #SQL + t.name + '.' + c.name + CASE WHEN t.name LIKE '%test2%' THEN ' AS ' + c.name + '_repeat' ELSE '' END + ','
FROM sys.columns c INNER JOIN sys.tables t ON t.object_id = c.object_id WHERE t.name IN ('test1', 'test2');
SELECT #SQL = LEFT(#SQL, LEN(#SQL) - 1);
SELECT #SQL = #SQL + ' FROM test1 INNER JOIN test2 ON test1.id = test2.id;';
EXEC(#SQL);
SELECT #SQL;
DROP TABLE test1;
DROP TABLE test2;
Output is:
id note id_repeat note_repeat
1 hello 1 world
This isn't possible in T-SQL. A column will have the name it had in its source table, or any alias name you specify, but there is no way to systematically rename them.
For cases like this, it pays off to take it one level higher: write some code (using sys.columns) that generates the query you're after, including renames. Why do something manually for 150 columns when you have a computer at your disposal?

Select data from one table & then rename the columns based on another table in SQL server

I have two tables. TableOne which contains two columns (name & value). TableTwo can contain N no. of columns. Number of rows in TableOne will be equal to number of columns in TableTwo.
see the below image for more information.
What I want:
When I run select query on TableTwo, the result-set should pick the column names based on value column of TableOne. We need to match column name of TableTwo with rows available in TableOne and perform transform.
So the output should look like this:
ColumnOne | ColumnTwo | columnThree | ColumnFour
1 1 1 2015-05-08 15:28:22.630
2 2 2 2015-05-07 15:28:22.630
................
................
You can use dynamic sql to generate the query to execute based on the values in the first table. Here is an example:
Declare #dynamicSQL nvarchar(200)
SET #dynamicSQL = 'SELECT ' + (SELECT stuff((select ',' + name + ' AS ' + value
from Table1
for xml path('')),1,1,'')) + ' FROM Table2'
EXECUTE sp_executesql #dynamicSQL
SQL Fiddle: http://sqlfiddle.com/#!6/768f9/10

Add character to selected duplicate value in SQL

I need to add character / number to selected duplicate values.
This is what I need:
SELECT Name -- Here I need to add for example 1 if It have duplicates
-- If It is hard way to code, how to add 1 to all selected values?
FROM Example
WHERE Id BETWEEN 25 AND 285
If there are 2 equal names Peter It should select Peter and second Peter1
If there is no easy way to make It, how to add 1 to all selected lines? Should select Peter1 instead of Peter
I've tried this:
SELECT Name + ' 1' AS Name -- in this case selecting wrong column
FROM Example
WHERE Id BETWEEN 25 AND 285
EDIT
SELECT #cols += ([Name]) + ','
FROM (SELECT Name --I neeed to integrate It here
FROM FormFields
WHERE ID BETWEEN 50 AND 82
) a
If I use this:
SELECT #cols += ([Name]) + ',' -- here throws error
FROM (SELECT Name + CASE WHEN RowNum = 1 THEN '' ELSE CONVERT(NVARCHAR(100), RowNum-1) END AS [UpdatedName]
FROM (
SELECT Name AS Name,
ROW_NUMBER() OVER (PARTITION BY Name ORDER BY Name) AS "RowNum"
FROM FormFields
WHERE Id Between 50 And 82) x
) a
It throws error: Invalid column name 'Name'.
EDIT 2
It's different tests but some of them have the same criteria. That's why I need It to rename.
You can do this via getting the Row_Number and using a Case. Here's an example for SQL Server:
;With Cte As
(
Select Name, Row_Number() Over (Partition By Name Order By Name) RN
From Example
Where Id Between 25 And 285
)
Select Case When RN = 1 Then Name Else Name + Cast((RN - 1) As Varchar (3)) End As Name
From Cte
You could use the ROW_NUMBER function built into SQL server.
select Name + case when RowNum = 1 then '' else CONVERT(varchar(100), RowNum-1) end as "UpdatedName"
from (
select name as "Name",
ROW_NUMBER() over (partition by name order by name) as "RowNum"
from Example
Where Id Between 25 And 285) x
Please note that this still doesn't guarantee you unique names. Afterall, someone could already have a name of "MyName1", so if you had 2 people with names "MyName" you'd still get 2 "MyName1" with this select statement.
This is very unusual request, it looks like you are trying to "make car run with wheels on the roof" :)
The root problem is almost sure wrong database design... Pivot is usually used for data summaries. If you have in the same column "Peter" and "Peter" with different meanings, it looks that there is something wrong. Or do you need to differentiate both Peters for any other reason?
I do not understand what are you trying to achieve. If Peter is always Peter, and you just want to avoid duplicities, you can simply use "group by Name". But this is what pivot does automatically... If Peter and Peter have two different meanings (like Peter1 and Peter2), you should think about changing database structure, if possible.
Or try to explain more deeply what are you trying to achieve.
EDIT:
OK, now I understand the desired output. And what is the structure of your source data table(s)? From your schema it is clear that you need to make PIVOT columns based on
Testname+groupId
or
Testname+convert(varchar(100),groupId)
if groupId is number. That is your Peter1,Peter2 composition. It will create columns that you need. But I dont't know where testname and groupId are located in your datatables. Do test names correspond to column NAMES or to VALUES stored in DB? Is groupId something like TestId? Again column or value? Provide more info about source data structure, if you need more help, your problem is not so complicated.
Since the columns have group IDs, concatenate the Column name with an Underscore and GroupID as a key value and when you display it, strip the underscore and trailing characters.
Like This:
SELECT #cols += ([Name]) + ','
FROM (SELECT Name + '_' + CAST(GroupId AS varchar)
FROM FormFields
WHERE ID BETWEEN 50 AND 82
) a
I assume you are using this to build a dynamic SQL statement. I'm not sure what the schema of your FormFields Table is, but if it includes something like the test name you could append an AS [Name] + ' - ' +[TestName] to have the column header be something more useful. I would say try a PIVOT, but that could get pretty ungainly if the tests don't have the majority of the fields in common...
I also assume you are storing responses to these prompts in a table that looks something like this:
CREATE TABLE [Responses]
(
RespID int IDENTITY NOT NULL,
UserID int NOT NULL,
FieldID int NOT NULL,
RespVal int/varchar/whatever NOT NULL
)
Then perhaps you have a [Test] table with some test metadata that acts as the primary key for your GroupID Foreign key in your FormFields table.
In your example you show responses across all columns, but I'm not sure how that would work since (unless I'm missing something in your explanation and the inferences I've made to your design) one set of responses would only be populated for one of the groups per row, unless you are aggregating responses, but then by what criteria? Perhaps the rows correspond to respondents and all respondents are required to answer across all form types. In that case, your output would work as a PIVOT like this:
DECLARE #sql varchar(4000) = ''
DECLARE #colList varchar(1000)
DECLARE #selList varchar(1000)
;WITH NameBase
AS
(
SELECT t.Name [TestName], f.Name [FieldName], f.GroupId
FROM [FormFields] f
INNER JOIN [Tests] t ON f.GroupID = t.ID
)
SELECT #colList = COALESCE(#colList + ',','') + QUOTENAME([FieldName] + '_' + [GroupId])
, #selList = COALESCE(#selList + ',','') + QUOTENAME([FieldName] + '_' + [GroupId]) + ' AS ' + QUOTENAME([FieldName] + ' - ' + [TestName])
FROM NameBase
SELECT #sql = 'SELECT [UserName],' + #selList + ' FROM (
SELECT u.Name [UserName], f.Name + '_' + f.GroupId [FieldName], r.RespVal [Response]
FROM Responses r
INNER JOIN [TestUsers] u ON r.UserID = u.ID
INNER JOIN [FormFields] f ON r.FieldID = f.ID) t
PIVOT (MAX([Response]) FOR [FieldName] IN (' + #colList + ')) pvt'
EXECUTE(#sql);
I haven't tested that yet, but it should at least point you in the right direction. I'll try to build a SqlFiddle to test it in a little bit.