I have the following table:
I want to convert the matrix to this format:
where the new mapping table represents the compatibility between a set of options and a set of models that use those options, and the numeric values represent the price of an option for that specific model.
Bare in mind that this is just a small sample from a much bigger table, so the query needs to be more or less dynamic and not hardcoded based on the number of options or models provided in this example.
Does anyone know how I can achieve this?
Best regards,
UnPivot or Gordon's answer would be more performant, but here is an option that will "dynamically" unpivot your data or query without actually using dynamic SQL.
Example
Select A.OptionID
,ModelName = C.Item
,Price = C.Value
From YourTable A
Cross Apply ( values (cast((Select A.* for XML RAW) as xml))) B(XMLData)
Cross Apply (
Select Item = a.value('local-name(.)','varchar(100)')
,Value = a.value('.','varchar(max)') --<< Change to desired datatype
From B.XMLData.nodes('/row') as C1(n)
Cross Apply C1.n.nodes('./#*') as C2(a)
Where a.value('local-name(.)','varchar(100)') not in ('OptionID','OtherFieldsToExclude')
) C
Returns
I like to do this using outer apply:
select v.*
from t outer apply
(values (option_id, 'model1', model1),
(option_id, 'model2', model2),
(option_id, 'model3', model3)
) v(option_id, model_name, price);
You would just add more rows to the values list for each item you want in the final table.
You can also do this using union all, cross join/case, and unpivot.
However, this method uses lateral joins, and these are very powerful for other purposes as well. This is worthwhile syntax to master.
EDIT:
I'm not really sure what "dynamic" means in this context. You table has columns or it doesn't. You can use dynamic SQL to generate the code based on the input parameters or the layout of a single table. It would follow the same logic as above.
Related
I'm trying to flatten a set of data from a SQL query (using MS SQL Server) and I need to do it twice.
This is the example data I have.
This is the data I would like to show
I managed to get one height and one area for each building using PIVOT but what I need is to pivot again in order to have one row for every building that contains all the related data.
I think the solution requires the use of both PIVOT and CROSS APPLY but I cannot find the right way to use them.
Thanks for your help!
No need for a CROSS APPLY, a simple PIVOT or Conditional Aggregation would do. Just remember to "FEED" your pivot with the minimum number of required columns.
Select *
From (
Select Building
,Item = concat([Floor],MeasureName)
,Value = MeasureValue
From YourTable
) src
Pivot ( max( Value ) for Item in ( [floor1height]
,[floor1area]
,[floor2height]
,[floor2area]
) ) Pvt
I would like to unpivot a SQL table around multiple columns.
I have tried a normal UNPIVOT statement but that only ppivots around one value.
See this link for example: https://codingsight.com/understanding-pivot-unpivot-and-reverse-pivot-statements.
I have tried to illustrate my data as well as my desired outcome in the picture below.
The top table is a sample of the data in the SQL table. I have used 3 materials but in reality there are 20.
The bottom table is my desired outcome.
The data is on a SQL 2008-r2 server.
Any pointers on how to go about this task?
Consider using cross apply, like so:
select t.date, t.product, x.*
from mytable t
cross apply (values
(container1material, container1amount),
(container2material, container2amount),
(container3material, container3amount)
) x(material, amount)
Use apply for unpivoting:
select t.date, t.product, v.*
from t cross apply
(values (container1amount, container1material),
(container2amount, container2material),
(container3amount, container3material)
) v(containeramount, containermaterial);
unpivot is bespoke syntax (not-standard) and it only does one thing. By contrast, lateral joins are very powerful and unpivoting is only one thing that you can do with them. Apply is worth learning about.
I have a table that looks like this:
Title01 Title02 Title03 Title04 Title05
Number Title Division Department IFC
And I am wanting to turn the columns into rows so it loos like this:
Field
Number
Title
Division
Department
IFC
Is it possible to do this using the PIVOT function in SQL?
I like to use CROSS APPLY for this:
select v.field
from t cross apply
(values (title01), (title02), (title03), (title04), (title05)
) v(field);
CROSS APPLY implements the lateral join. You can think of it as an extension of correlated subqueries -- but the subquery can return multiple columns and multiple rows. Unpivoting data happens to be a simple introduction to the concept.
Say that I have a table that looks like this (sorry about using a picture, but I can't figure out how to get a nicely-formatted table on SO...):
I wanted to make a query to sort the ingredients by the frequency of recipes they appear in. So in this example, we'd want to see the following output:
I was thinking that LIKE and IN might be potentially helpful to make this search, but I'm not sure how to go from there.
I really can't resist a "you can't" quote. Your solution follows.
SELECT Item
FROM
(
SELECT LTRIM(x.XmlCol.value('.','varchar(100)')) 'Item'
FROM
(
SELECT CAST('<A>'+REPLACE(ingredients,',','</A><A>')+'</A>' AS XML) 'Ingredient'
FROM #recipes ) Mytab
CROSS APPLY Mytab.Ingredient.nodes('/A') x(xmlcol)
) Listing
GROUP BY ITEM
ORDER BY Count(1) DESC
Try it for yourself. In short you start by replacing the commas with XML seperators, Then for efficiency invoke Microsofts own XML methods to convert the list of values into a tabular output. You then simply drop the lot into a FROM statement and group with an order by.
I know that this question has been asked several times and I've read all the answer but none of them seem to completely solve my problem.
I'm switching from a mySQL database to a MS Access database. In both of the case I use a php script to connect to the database and perform SQL queries.
I need to find a suitable replacement for a query I used to perform on mySQL.
I want to:
perform a first query and order records alphabetically based on one of the columns
construct a list of IDs which reflects the previous alphabetical order
perform a second query with the IN clause applied with the IDs' list and ordered by this list.
In mySQL I used to perform the last query this way:
SELECT name FROM users WHERE id IN ($name_ids) ORDER BY FIND_IN_SET(id,'$name_ids')
Since FIND_IN_SET is available only in mySQL and CHARINDEX and PATINDEX are not available from my php script, how can I achieve this?
I know that I could write something like:
SELECT name
FROM users
WHERE id IN ($name_ids)
ORDER BY CASE id
WHEN ... THEN 1
WHEN ... THEN 2
WHEN ... THEN 3
WHEN ... THEN 4
END
but you have to consider that:
IDs' list has variable length and elements because it depends on the first query
that list can easily contains thousands of elements
Have you got any hint on this?
Is there a way to programmatically construct the ORDER BY CASE ... WHEN ... statement?
Is there a better approach since my list of IDs can be big?
UPDATE: I perform two separated query because I need to access two different tables.
The databse it's not very simple so I try to make an example:
Suppose I have a table which contains a list of users and a table which contains all the books that every user have in their bookshelf.
Since the dabase was designed in mySQL, for every book record I store the user_id in the books table in order to have a relationship between the user and the book.
Suppose now that I want to obtain a list of all the user that have books with a title starting with letter 'a' and I want to order the user based on the alphabetical oder of the books.
This is what I do:
perform a first query to find all the books which start with letter 'a' and sort the alphabetically
create a list of user_id which should reflect the alphabetical order of the book
perform a query in the users table to find out the users names and sort them with the user_id list to have the required sorting by book
Hope this clarify what I need.
If I understand correctly, you're trying to get a set of information in the same order that you specify the ID values. There is a hack that can convert a list into a table using XML and CROSS APPLY. This can be combined with the ROW_NUMBER function to generate your sort order. See the code below:
CREATE FUNCTION [dbo].[GetNvarcharsFromXmlArray]
(
#Strings xml = N'<ArrayOfStrings/>'
)
RETURNS TABLE
AS
RETURN
(
SELECT ROW_NUMBER() OVER (ORDER BY (SELECT 1)) AS RowNumber, Strings.String.value('.', 'nvarchar(MAX)') AS String
FROM #Strings.nodes('/ArrayOfStrings/string/text()') AS Strings(String)
)
Which functions with the following structure:
<ArrayOfStrings>
<string>myvalue1</string>
<string>myvalue2</string>
</ArrayOfStrings>
It's also the same format .NET xml serializes string arrays.
If you want to pass a comma separated list, you can simply use:
CREATE FUNCTION [dbo].[GetNvarcharsCSV]
(
#CommaSeparatedStrings nvarchar(MAX) = N''
)
RETURNS TABLE
AS
RETURN
(
DECLARE #Strings xml
SET #Strings = CONVERT(xml, N'<ArrayOfStrings><string>' + REPLACE(#CommaSeperatedStrings, ',', N'</string><string>') + N'</string></ArrayOfStrings>')
SELECT ROW_NUMBER() OVER (ORDER BY (SELECT 1)) AS RowNumber, Strings.String.value('.', 'nvarchar(MAX)') AS String
FROM #Strings.nodes('/ArrayOfStrings/string/text()') AS Strings(String)
)
This makes your query:
SELECT name
FROM users
INNER JOIN dbo.GetNvarcharsCSV(#name_ids) AS IDList ON users.ID = IDList.String
ORDER BY RowNumber
Note that it's a pretty simple rewrite to make the function return a table of integers if that's what you need.
You can see xml Data Type Methods to get a better understanding of what you can do with XML in SQL queries. Also, see ROW_NUMBER (Transact-SQL).
It sounds like you need a JOIN...
This should work, although it may need to be translated to Access syntax (which is apparently subtly different):
SELECT b.name, a.title
FROM book as a
JOIN user as b
ON b.id = a.userId
WHERE SUBSTRING(LOWER(a.title), 1, 1) = 'a'
ORDER by a.title
I don't know why you're switching to Access, although I have heard it's been improving in recent years. I think I'd prefer almost any other RDBMS, though. And your schema could probably stand some tweaking, from the sound of things.
You would have to use a user-defined function that maintains the order, and then order by that column. For example:
CREATE FUNCTION dbo.SplitList
(
#List VARCHAR(8000)
)
RETURNS TABLE
AS
RETURN
(
SELECT DISTINCT
[Rank],
[Value] = CONVERT(INT, LTRIM(RTRIM(SUBSTRING(#List, [Rank],
CHARINDEX(',', #List + ',', [Rank]) - [Rank]))))
FROM
(
SELECT TOP (8000) [Rank] = ROW_NUMBER()
OVER (ORDER BY s1.[object_id])
FROM sys.all_objects AS s1
CROSS JOIN sys.all_objects AS s2
) AS n
WHERE [Rank] <= LEN(#List)
AND SUBSTRING(',' + #List, [Rank], 1) = ','
);
GO
Now your query can look something like this:
SELECT u.name
FROM dbo.users AS u
INNER JOIN dbo.SplitList($name_ids) AS s
ON u.id = s.Value
ORDER BY s.[Rank];
You may have to surround $name_ids with single quotes (dbo.SplitList('$name_ids')) depending on how the SQL statement is constructed. You may want to consider using a stored procedure instead of building this query in PHP.
You might also consider skipping MS-Access as a hopping point altogether. Why not just have PHP communicate directly with SQL Server?