Consider below tables
Job Table
JobID AnswerID UserID
1 1,2 1
2 2,3 2
3 1,3 3
Answer Table
AnswerID Answer QuestionID
1 Clean 1
2 Install 1
3 Other 2
For this I need to get the result as below
JobID Answer UserID
1 Clean,Install 1
2 Install,Other 2
3 Clean,Other 3
Please help to write MSSQL query for this.
You are storing a list of ids as a comma separated list. This is a really bad idea for several reasons:
Storing numbers as strings is a bad idea.
You cannot define foreign key relationships.
SQL does not have great support for strings.
Any attempt to join to the original table will be inefficient, because of the type conversion.
Such a structure violates the idea that a column contains a single value.
There is a proper way to store lists in a relational database. It is called a "table". You want a junction table with one row per job and answer. I would call it JobAnswers.
With the proper data structure, your query would be trivial.
Although I agree with Gordon Linoff I do understand we have no control over what we inherit from previous developers.
here is what you require to do:
Sample data
CREATE TABLE #temp
(
JobID INT, AnswerID VARCHAR(10), UserID INT
);
INSERT INTO #temp
VALUES
(1, '1,2', 1
),
(2, '2,3', 2
),
(3, '1,3', 3
);
CREATE TABLE #temp2
(
AnswerID INT, Answer VARCHAR(10), QuestionID INT
);
INSERT INTO #temp2
VALUES
(1, 'Clean', 1
),
(2, 'Install', 1
),
(3, 'Other', 2
);
Query:
SELECT #temp.JobID,
(
SELECT #temp2.Answer
FROM #temp2
WHERE #temp2.AnswerID = SUBSTRING(#temp.AnswerID, 1, CHARINDEX(',', #temp.AnswerID)-1)
)+','+
(
SELECT #temp2.Answer
FROM #temp2
WHERE #temp2.AnswerID = SUBSTRING(#temp.AnswerID, CHARINDEX(',', #temp.AnswerID)+1, LEN(#temp.AnswerID)-1)
) AS Answer,
#temp.UserID
FROM #temp;
Result:
You can try to use subqueries like this:
SELECT job.jobID,
(SELECT answer.answer FROM answer WHERE answer.answerID IN (job.answerID)) as answers,
job.userID
Related
I'm trying to come up with a useful way to list all pages in between the first of last page of a document into new rows while maintaining the ID number as a key, or cross reference. I have a few ways of getting pages in between, but I'm not exactly sure how to maintain the key in a programmatic way.
Example Input:
First Page Last Page ID
ABC_001 ABC_004 1
ABC_005 ABC_005 2
ABC_006 ABC_010 3
End Result:
All Pages ID
ABC_001 1
ABC_002 1
ABC_003 1
ABC_004 1
ABC_005 2
ABC_006 3
ABC_007 3
ABC_008 3
ABC_009 3
ABC_010 3
Any help is much appreciated. I'm using SQL mgmt studio.
One approach would be to set up a numbers table, that contains a list of numbers that you may possibly find in the column content:
CREATE TABLE numbers( idx INTEGER);
INSERT INTO numbers VALUES(1);
INSERT INTO numbers VALUES(2);
...
INSERT INTO numbers VALUES(10);
Now, assuming that all page values have 7 characters, with the last 3 being digits, we can JOIN the original table with the numbers table to generate the missing records:
SELECT
CONCAT(
SUBSTRING(t.First_Page, 1, 4),
REPLICATE('0', 3 - LEN(n.idx)),
n.idx
) AS [ALl Pages],
t.id
FROM
mytable t
INNER JOIN numbers n
ON n.idx >= CAST(SUBSTRING(t.First_Page, 5, 3) AS int)
AND n.idx <= CAST(SUBSTRING(t.Last_Page, 5, 3) AS int)
This demo on DB Fiddle with your sample data returns:
ALl Pages | id
:-------- | -:
ABC_001 | 1
ABC_002 | 1
ABC_003 | 1
ABC_004 | 1
ABC_005 | 2
ABC_006 | 3
ABC_007 | 3
ABC_008 | 3
ABC_009 | 3
ABC_010 | 3
To find all pages from First Page to Last Page per Book ID, CAST your page numbers from STRING to INTEGER, then add +1 to each page number until you reach the Last Page.
First, turn your original table into a table variable with the Integer data types using a TRY_CAST.
DECLARE #Book TABLE (
[ID] INT
,[FirstPage] INT
,[LastPage] INT
)
INSERT INTO #Book
SELECT [ID]
,TRY_CAST(RIGHT([FirstPage], 3) AS int) AS [FirstPage]
,TRY_CAST(RIGHT([LastPage], 3) AS int) AS [LastPage]
FROM [YourOriginalTable]
Set the maximum page that your pages will increment to using a variable. This will cap out your results to the correct number of pages. Otherwise your table would have many more rows than you need.
DECLARE #LastPage INT
SELECT #LastPage = MAX([LastPage]) FROM #Book
Turning a three-column table (ID, First Page, Last Page) into a two-column table (ID, Page) will require an UNPIVOT.
We're tucking that UNPIVOT into a CTE (Common Table Expression: basically a smart version of a temporary table (like a #TempTable or #TableVariable, but which you can only use once, and is a little more efficient in certain circumstances).
In addition to the UNPIVOT of your [First Name] and [Last Name] columns into a tall table, we're going to append every other combination of page number per ID using a UNION ALL.
;WITH BookCTE AS (
SELECT [ID]
,[Page]
FROM (SELECT [ID]
,[FirstPage]
,[LastPage]
FROM #Book) AS bp
UNPIVOT
(
[Page] FOR [Pages] IN ([FirstPage], [LastPage])
) AS up
UNION ALL
SELECT [ID], [Page] + 1 FROM BookCTE WHERE [Page] + 1 < #LastPage
)
Now that your data is held in a table format using a CTE with all combinations of [ID] and [Page] up to the maximum page in your #Book table, it's time to join your CTE with the #Book table.
SELECT DISTINCT
cte.ID
,cte.Page
FROM BookCTE AS cte
INNER JOIN #Book AS bk
ON bk.ID = cte.ID
WHERE cte.Page <= bk.[LastPage]
ORDER BY
cte.ID
,cte.Page
OPTION (MAXRECURSION 10000)
See also:
How to generate a range of numbers between two numbers (I based my code off of #Jayvee's answer)
Assigning variables using SET vs SELECT
SQL Server UNPIVOT
SQL Server CTE Basics
Recursive CTEs Explained
Note: will update with re-integrating string portion of FirstPage and LastPage (which I assume is based on book title). Stand by.
Am working with PostgreSQL 8.0.2, I have table
create table rate_date (id serial, rate_name text);
and it's data is
id rate_name
--------------
1 startRate
2 MidRate
3 xlRate
4 xxlRate
After select it will show data with default order or order by applied to any column of same table. My requirement is I have separate entity from where I will get data as (xlRate, MidRate,startRate,xxlRate) so I want to use this data to sort the select on table rate_data. I have tried for values join but it's not working and no other solution am able to think will work. If any one have idea please share detail.
Output should be
xlRate
MidRate
startRate
xxlRate
my attempt/thinking.
select id, rate_name
from rate_date r
join (
VALUES (1, 'xlRate'),(2, 'MidRate')
) as x(a,b) on x.b = c.rate_name
I am not sure if this is helpful but in Oracle you could achieve that this way:
select *
from
(
select id, rate_name,
case rate_name
when 'xlRate' then 1
when 'MidRate' then 2
when 'startRate' then 3
when 'xxlRate' then 4
else 100
end my_order
from rate_date r
)
order by my_order
May be you can do something like this in PostgreSQL?
I will present a question about 'aliasing' values from a column. I will use days of the week as an intuitive example to get my question across, but I am not asking for datetime conversions.
Suppose I have the following SQL script:
SELECT DaysOfWeek
FROM [databasename].[dbo].[tablename]
Now, the column DaysOfWeek will return string values of the days' names, i.e. "Monday," "Tuesday," and so forth.
What if I wanted the query to return the integer 1 for 'Monday', 2 for 'Tuesday', and so forth? I would want to assign a particular value to each of the week's days in the SELECT statement, but I'm not sure how to go about doing that.
I'm relatively new to SQL, so I just thought I'd ask for an intuitive method to perform such a task.
Edited to add: I'm only using days of the week and their respective integer representation as an easy example; my task does not involve days of the week, but rather employee code numbers and corresponding titles.
You can do this using case:
SELECT (CASE DaysOfWeek
WHEN 'Monday' THEN 1
WHEN 'Tuesday' THEN 2
. . .
END)
Under most circumstances, it is unnecessary to store the day of the week like this. You can readily use a function, datepart() or datename(), to extract the day of the week from a date/time value.
If the column is in a table, and not part of a date, then you might want to include the above logic as a computed column:
alter table t add DayOfWeekNumber as (case DaysOfWeek when 'Monday' then 1 . . .);
Use CASE, here you have the definition and one example :
select
CASE
WHEN(DaysOfWeek="Monday") THEN 1
WHEN(DaysOfWeek="Thusday") THEN 2
....
ELSE -1
from table
Hope this help!
If you wanted to define your own corresponding value for another value, the best way is to use a table, and join that table.
For example:
create table dbo.EmployeeTitle (
id int not null identity(1,1) primary key
, title varchar(32)
);
create table dbo.Employee (
id int not null identity(1,1) primary key
, name nvarchar(128)
, title_id int references dbo.EmployeeTitle(id)
);
insert into dbo.EmployeeTitle values ('Big boss');
insert into dbo.Employee values ('daOnlyBG',1);
select e.*, et.title
from dbo.Employee e
inner join dbo.EmployeeTitle et
on e.title_id = et.id
rextester demo: http://rextester.com/FXIM78632
returns:
+----+----------+----------+----------+
| id | name | title_id | title |
+----+----------+----------+----------+
| 1 | daOnlyBG | 1 | Big boss |
+----+----------+----------+----------+
The easiest way I can think of is to have a table variable or CTE; create your lookup as rows and join to it. Something like this:
with cte as (
select 1 as emp_code, 'value1' as emp_title
union
select 2 as emp_code, 'value2' as emp_title
union
select 3 as emp_code, 'value3' as emp_title
)
select cte.emp_code, tableName.*
from tableName
inner join cte
on cte.emp_title = tableName.some_column
I have a table with following data:
ID ParentID Name
-----------------------
1 NULL OK1
2 1 OK2
3 2 OK3
5 4 BAD1
6 5 BAD2
So I need to take only those lines, which are linked to ParentID = NULL OR valid children of such lines (i.e: OK3 is valid because it's linked to OK2, which is linked to OK1, which is linked to NULL, which is valid.)
But BAD1 and BAD 2 are not valid because those are not linked to a line, which is linked to NULL.
The best solution I figured out is a procedure + function. And function is called as many times as the max number of link levels in the table.
Can anybody suggest better solution for such task?
All you need is love, and a basic recursive CTE :-)
Create and populate sample data (Please save us this step in future questions):
DECLARE #T as table
(
ID int,
ParentID int,
Name varchar(4)
)
INSERT INTO #T VALUES
(1, NULL, 'OK1'),
(2, 1, 'OK2'),
(3, 2, 'OK3'),
(5, 4, 'BAD1'),
(6, 5, 'BAD2')
The CTE and query:
;WITH CTE AS
(
SELECT ID, ParentId, Name
FROM #T
WHERE ParentId IS NULL
UNION ALL
SELECT T1.ID, T1.ParentId, T1.Name
FROM #T T1
INNER JOIN CTE T2 ON T1.ParentID = T2.ID
)
SELECT *
FROM CTE
Results:
ID ParentId Name
----------- ----------- ----
1 NULL OK1
2 1 OK2
3 2 OK3
I am trying to run below 2 queries on the same table and hoping to get results in 2 different columns.
Query 1: select ID as M from table where field = 1
returns:
1
2
3
Query 2: select ID as N from table where field = 2
returns:
4
5
6
My goal is to get
Column1 - Column2
-----------------
1 4
2 5
3 6
Any suggestions? I am using SQL Server 2008 R2
Thanks
There has to be a primary key to foreign key relationship to JOIN data between two tables.
That is the idea about relational algebra and normalization. Otherwise, the correlation of the data is meaningless.
http://en.wikipedia.org/wiki/Database_normalization
The CROSS JOIN will give you all possibilities. (1,4), (1,5), (1, 6) ... (3,6). I do not think that is what you want.
You can always use a ROW_NUMBER() OVER () function to generate a surrogate key in both tables. Order the data the way you want inside the OVER () clause. However, this is still not in any Normal form.
In short. Why do this?
Quick test database. Stores products from sporting goods and home goods using non-normal form.
The results of the SELECT do not mean anything.
-- Just play
use tempdb;
go
-- Drop table
if object_id('abnormal_form') > 0
drop table abnormal_form
go
-- Create table
create table abnormal_form
(
Id int,
Category int,
Name varchar(50)
);
-- Load store products
insert into abnormal_form values
(1, 1, 'Bike'),
(2, 1, 'Bat'),
(3, 1, 'Ball'),
(4, 2, 'Pot'),
(5, 2, 'Pan'),
(6, 2, 'Spoon');
-- Sporting Goods
select * from abnormal_form where Category = 1
-- Home Goods
select * from abnormal_form where Category = 2
-- Does not mean anything to me
select Id1, Id2 from
(select ROW_NUMBER () OVER (ORDER BY ID) AS Rid1, Id as Id1
from abnormal_form where Category = 1) as s
join
(select ROW_NUMBER () OVER (ORDER BY ID) AS Rid2, Id as Id2
from abnormal_form where Category = 2) as h
on s.Rid1 = h.Rid2
We definitely need more information from the user.