How to replace column names with a value of another table - sql

I have table A like below:
ID QuestionCode Question
1 Q1 What is your name
2 Q2 How old are you
3 Q3 Where are you from
Also, I have table B like below:
UserId Q1 Q2 Q3
1 Ahmad 22 Egypt
2 John 31 USA
3 Daniel 32 Mexico
What I want is to create a view using the question itself instead of the its code in the column name like:
UserId What Is Your Name How old are you Where are you from
1 Ahmad 22 Egypt
2 John 31 USA
3 Daniel 32 Mexico
What is the best way to do it? Any help would be appreciated.
THanks.

Updated ... Missed the 2016 tag
SQL Server is declarative by design and does not support macro substitution.
This leaves Dynamic SQL.
Here is a working example of the Dynamic SQL
Declare #tsql nvarchar(max) = N'Select top 1 * from Answers
Select #tsql = 'Select '+stuff((Select ',' +expr From (
select expr=concat(coalesce(quotename(Question),Name),'=',Name)
From sys.dm_exec_describe_first_result_set(#tsql,null,null ) A
Left Join Questions B on B.QuestionCode=A.name
) src For XML Path ('')),1,1,'')+' From Answers'
Exec(#tsql)
Results

The pivot you're describing isn't easy to do in straight SQL; you could write some dynamic SQL to create column names and subqueries for the second table based on the first, but to do this but can be rather fiddly and is certainly an easy way to break things if you get it wrong.
There's an alternative approach highlighted at https://www.red-gate.com/simple-talk/sql/t-sql-programming/easier-way-pivoting-data-sql-server/; whether that's simpler than the dynamic SQL option very much depends on your experience level.

Related

SELECT from 50 columns

I have a table that has many columns around 50 columns that have datetime data that represent steps user takes when he/she do a procedure
SELECT UserID, Intro_Req_DateTime, Intro_Onset_DateTime, Intro_Comp_DateTime, Info_Req_DateTime, Info_Onset_DateTime, Info_Comp_DateTime,
Start_Req_DateTime, Start_Onset_DateTime, Start_Comp_DateTime,
Check_Req_DateTime, Check_Onset_DateTime, Check_Comp_DateTime,
Validate_Req_DateTime, Validate_Onset_DateTime, Validate_Comp_DateTime,
....
FROM MyTable
I want to find the Step the user did after certain datetime
example I want to find user ABC what the first step he did after 2 May 2019 17:25:36
I cannot use case to check this will take ages to code
is there an easier way to do that?
P.S. Thanks for everyone suggested redesigning the database.. not all databases can be redesigned, this database is for one of the big systems we have and it is been used for more than 20 years. redesigning is out of the equation.
You can use CROSS APPLY to unpivot the values. The syntax for UNPIVOT is rather cumbersome.
The actual query text should be rather manageable. No need for complicated CASE statements. Yes, you will have to explicitly list all 50 column names in the query text, you can't avoid that, but it will be only once.
SELECT TOP(1)
A.StepName
,A.dt
FROM
MyTable
CROSS APPLY
(
VALUES
('Intro_Req', Intro_Req_DateTime)
,('Intro_Onset', Intro_Onset_DateTime)
,('Intro_Comp', Intro_Comp_DateTime)
.........
) AS A (StepName, dt)
WHERE
MyTable.UserID = 'ABC'
AND A.dt > '2019-05-02T17:25:36'
ORDER BY dt DESC;
See also How to unpivot columns using CROSS APPLY in SQL Server 2012
The best way is to design your table with your action type and datetime that action was done. Then you can use a simple where clause to find what you want. The table should be like the table below:
ID ActionType ActionDatetime
----------- ----------- -------------------
1492 1 2019-05-13 10:10:10
1494 2 2019-05-13 11:10:10
1496 3 2019-05-13 12:10:10
1498 4 2019-05-13 13:10:10
1500 5 2019-05-13 14:10:10
But in your current solution, you should use UNPIVOT to get what you want. You can find more information in this LINK.

Set based approach to SQL Server insert where one column is calculated as Max from same column

I'm wondering if this is one of those situations where I'm forced to use a cursor or if I can use a set based approach. I've searched for several hours and also tried to come up with a solution myself to no avail.
I've got a table, SuperSupplierCodes, that contains two columns: SuperSupplierCode INT, and SupplierName NVARCHAR(50).
SuperSupplierID SupplierName
1 21ST CENTURY GRAPHIC TECHNOLOGIES LLC
2 3D SYSTEMS
3 3G
4 A A ABRASIVOS ARGENTINOS SAIC
5 A AND F DRUCKLUFTTECHNIK GMBH
6 A BAY STATIONERS
7 A C T TOOL AND ENGINEERING LLC
8 A HERZOG AG
9 A LI T DI MONTANARI MARCO AND CO SAS
11 A RAYMOND GMBH AND CO KG
I've got a second table with millions of rows in it containing financial data as well as the SupplierName column.
LocalSupplierName
23 JAN HOFMEYER ROAD
303 TAXICAB, LLC
3D MECA SARL
3D SYSTEMS
3E CO ENVIRONMENTAL, ECO. & EN
3E COMPANY
What I need to do is insert into the SuperSupplierCodes table such that each row gets the MAX(SuperSupplierCode) from the previous row, increments it by one, and inserts that into the SuperSupplierCode column along with the SupplierName from the second table.
I've tried the following, just as a test, that I might be able to use for the insert, but of course it will only do the increment once and try to use that same value for SuperSupplierCode for every row:
SELECT s.SuperSupplierID,
s.SupplierName,
s.SupplierAddress,
s.DateCreated,
s.DateModified,
s.SupplierCode,
s.PlantName,
s.id,
x.MaxSSC
FROM SuperSupplierCodes AS s
CROSS APPLY (SELECT MAX(SuperSupplierID)+1 AS MaxSSC FROM dbo.SuperSupplierCodes) x;
I don't like using cursors unless I absolutely have to. Is there a way to do this with T-SQL in a set based manner versus using a cursor?
Create the column as an identity and insert the existing records once using SET IDENTITY_INSERT ON option. Then switch it off for adding new Ids and they will be incremented.
https://learn.microsoft.com/en-us/sql/t-sql/statements/set-identity-insert-transact-sql?view=sql-server-2017
Why not something like this?
SELECT (SELECT MAX(SuperSupplierID) FROM dbo.SuperSupplierCodes) + ROW_NUMBER() OVER (ORDER BY s.DateCreated) AS SuperSupplierID,
s.SupplierName,
s.SupplierAddress,
s.DateCreated,
s.DateModified,
s.SupplierCode,
s.PlantName,
s.id
FROM SuperSupplierCodes AS s;
We use the above technique at my work all the time when inserting rows. If some have existing values, you can insert them all into the table and then change the above to only update values that are currently null.

Exclude entire row based on based on values from another query

I am using MS Access and I have a rather complex situation.
I have Respondents who are linked to varying numbers of different Companies via 2 connecting tables. I want to be able to create a list of distinct customers which excludes any customer associated with Company X.
Here is a pic of the relationships that are involved with the query.
And here is an example of what I'm trying to achieve.
RespondentRef | Respondent Name
8 Joe Bloggs
.
RespondentRef | GroupRef
8 2
.
GroupRef | CompanyRef
2 10
.
CompanyRef | CompanyName
10 Ball of String
I want a query where I enter in 'Ball of String' for the company name, and then it produces a list of all the Respondents (taken from Tbl_Respondent) which completely excludes Respondent 8 (as he is linked to CompanyName: Ball of String).
Tbl_Respondent
RespondentRef | Respondent Name
... ...
7 Bob Carlyle
9 Anton Boyle
I have tried many combinations of subqueries with <> and NOT EXISTS and NOT IN and nothing seems to work. I suspect the way these tables are linked may have something to do with it.
Any help you could offer would be very much appreciated. If you have any questions let me know. (I have made best efforts, but please accept my apologies for any formatting conventions or etiquette faux-pas I may have committed.)
Thank you very much.
EDIT:
My formatted version of Frazz's code is still turning resulting in a syntax error. Any help would be appreciated.
SELECT *
FROM Tbl_Respondent
WHERE RespondentRef NOT IN (
SELECT tbl_Group_Details_Respondents.RespondentRef
FROM tbl_Group_Details_Respondents
JOIN tbl_Group_Details ON tbl_Group_Details.GroupReference = tbl_Group_Details_Respondents.GroupReference
JOIN tbl_Company_Details ON tbl_Company_Details.CompanyReference = tbl_Group_Details.CompanyReference
WHERE tbl_Company_Details.CompanyName = "Ball of String"
)
This should do what you need:
SELECT *
FROM Tbl_Respondent
WHERE RespondentRef NOT IN (
SELECT gdr.RespondentRef
FROM Tbl_Group_Details_Respondent gdr
JOIN Tbl_Group_Details gd ON gd.GroupRef=gdr.GroupRef
JOIN Tbl_Company_Details cd ON cd.CompanyRef=gd.CompanyRef
WHERE cd.CompanyName='Ball of String'
)

Self join/update of table with data in same table

Due to combining old and new system data in a table I have a list of data like this:
Work no Work name
========= =========
123456 James
123456 James, (123456)
And I want to update to:
Work_no Work_name
========= =========
123456 James
123456 James
I tried building an update statement, wasn't too confident in it so ran it as an equivalent select statement to see what returned and it seems to be running in an infinite loop (there's about 200k records and when I stopped it it was at somewhere in 2 Million returned!) although what it was returning at the start looked fine it just seemed to be duplicating or something:
UPDATE c1
set c1.Work_name = c.Work_name
FROM table c1
INNER JOIN table c ON c1.Work_no = c.Work_no
where charindex(',',c1.Work_name) > 0
Only got experience doing the simplest update statements - a bit stuck with this one if anyone could suggest what I am doing wrong and best way to rectify it?
Thanks
Andrew
Do you have an index built on work_no?
Did you try:
UPDATE #table set Work_name = (select top 1 c.Work_name FROM #table c where #table.Work_no = c.Work_no and charindex(',',c.Work_name) = 0) where charindex(',',c.Work_name) > 0

How do you concat multiple rows into one column in SQL Server?

I've searched high and low for the answer to this, but I can't figure it out. I'm relatively new to SQL Server and don't quite have the syntax down yet. I have this datastructure (simplified):
Table "Users" | Table "Tags":
UserID UserName | TagID UserID PhotoID
1 Bob | 1 1 1
2 Bill | 2 2 1
3 Jane | 3 3 1
4 Sam | 4 2 2
-----------------------------------------------------
Table "Photos": | Table "Albums":
PhotoID UserID AlbumID | AlbumID UserID
1 1 1 | 1 1
2 1 1 | 2 3
3 1 1 | 3 2
4 3 2 |
5 3 2 |
I'm looking for a way to get the all the photo info (easy) plus all the tags for that photo concatenated like CONCAT(username, ', ') AS Tags of course with the last comma removed. I'm having a bear of a time trying to do this. I've tried the method in this article but I get an error when I try to run the query saying that I can't use DECLARE statements... do you guys have any idea how this can be done? I'm using VS08 and whatever DB is installed in it (I normally use MySQL so I don't know what flavor of DB this really is... it's an .mdf file?)
Ok, I feel like I need to jump in to comment about How do you concat multiple rows into one column in SQL Server? and provide a more preferred answer.
I'm really sorry, but using scalar-valued functions like this will kill performance. Just open SQL Profiler and have a look at what's going on when you use a scalar-function that calls a table.
Also, the "update a variable" technique for concatenation is not encouraged, as that functionality might not continue in future versions.
The preferred way of doing string concatenation to use FOR XML PATH instead.
select
stuff((select ', ' + t.tag from tags t where t.photoid = p.photoid order by tag for xml path('')),1,2,'') as taglist
,*
from photos
order by photoid;
For examples of how FOR XML PATH works, consider the following, imagining that you have a table with two fields called 'id' and 'name'
SELECT id, name
FROM table
order by name
FOR XML PATH('item'),root('itemlist')
;
Gives:
<itemlist><item><id>2</id><name>Aardvark</a></item><item><id>1</id><name>Zebra</name></item></itemlist>
But if you leave out the ROOT, you get something slightly different:
SELECT id, name
FROM table
order by name
FOR XML PATH('item')
;
<item><id>2</id><name>Aardvark</a></item><item><id>1</id><name>Zebra</name></item>
And if you put an empty PATH string, you get even closer to ordinary string concatenation:
SELECT id, name
FROM table
order by name
FOR XML PATH('')
;
<id>2</id><name>Aardvark</a><id>1</id><name>Zebra</name>
Now comes the really tricky bit... If you name a column starting with an # sign, it becomes an attribute, and if a column doesn't have a name (or you call it [*]), then it leaves out that tag too:
SELECT ',' + name
FROM table
order by name
FOR XML PATH('')
;
,Aardvark,Zebra
Now finally, to strip the leading comma, the STUFF command comes in. STUFF(s,x,n,s2) pulls out n characters of s, starting at position x. In their place, it puts s2. So:
SELECT STUFF('abcde',2,3,'123456');
gives:
a123456e
So now have a look at my query above for your taglist.
select
stuff((select ', ' + t.tag from tags t where t.photoid = p.photoid order by tag for xml path('')),1,2,'') as taglist
,*
from photos
order by photoid;
For each photo, I have a subquery which grabs the tags and concatenates them (in order) with a commma and a space. Then I surround that subquery in a stuff command to strip the leading comma and space.
I apologise for any typos - I haven't actually created the tables on my own machine to test this.
Rob
I'd create a UDF:
create function GetTags(PhotoID int) returns #tags varchar(max)
as
begin
declare #mytags varchar(max)
set #mytags = ''
select #mytags = #mytags + ', ' + tag from tags where photoid = #photoid
return substring(#mytags, 3, 8000)
end
Then, all you have to do is:
select GetTags(photoID) as tagList from photos
Street_Name ; Street_Code
west | 14
east | 7
west+east | 714
If want to show two different row concat itself , how can do it?
(I mean last row i want to show from select result. My table had first and secord record)