I have several tables all holding small amounts of data on a batch of product. For example, I have a table (called 'Tests') that holds a test number, a test name and the description. This is referenced by my batch table, which holds a test number, test result (as a real) and the batch number itself.
Some batches may have 50 tests, some may have 30, some may have as little as 1.
I was hoping to create a view that converts something like these tables;
BatchNumber TestNum TestResult | TestNumber TestName TestDesc
----------- -------- ----------- | ----------- --------- ---------
1000 1 1.20 | 1 Thickness How thick the product is
1001 1 1.30 | 2 Colour What colour the product is
1001 2 45.1 | 3 Weight How heavy the product is
...
to the following;
BatchNumber Thickness Colour Weight
------------ --------- ------ -------
1000 1.20 NULL NULL
1001 1.30 45.1 NULL
...
Though the 'null' could just be blank, it would probably be better that way, I just used that to better show my requirement.
I've found many articles online on the benefit of PIVOTing, UNPIVOTing, UNIONing but none show the direct benefit, or indeed provide a clear and succinct way of using the data without copying data into a new table, which isn't really useful for my need. I was hoping that a view would be possible so that end-user applications can just call that instead of doing the joins locally.
I hope that makes sense, and thank you!
You need cross tab query for this.
http://www.mssqltips.com/sqlservertip/1019/crosstab-queries-using-pivot-in-sql-server/
DECLARE #Tests Table (TestNumber int, TestName VARCHAR(100),TestDesc varchar(100))
INSERT INTO #Tests
SELECT 1, 'Thickness', '' UNION ALL
SELECT 2, 'Color', '' UNION ALL
SELECT 3, 'Weight', ''
DECLARE #BTests Table (BatchNum int, TestNumber int, TestResult float)
INSERT INTO #BTests
SELECT 1000, 1, 1.20 UNION ALL
SELECT 1001, 1, 1.30 UNION ALL
SELECT 1001, 2, 45.1
;with cte as (
select b.*, t.TestName
from #BTests b
inner join #Tests t on b.TestNumber = t.TestNumber
)
SELECT Batchnum, [Thickness] AS Thickness, [Color] AS Color, [Weight] as [Weight]
FROM
(SELECT Batchnum, TestName, testResult
FROM Cte ) ps
PIVOT
(
SUM (testResult)
FOR testname IN
( [Thickness],[Color], [Weight])
) AS pvt
CREATE VIEW?
e.g.
CREATE VIEW myview AS
SELECT somecolumns from sometable
Related
I've made schema changes/improvements to a table, but I need to ensure that I don't lose any existing data and it is 'migrated' across to the new schema and conforms to its design.
The existing schema is designed as follows:
ID FK_ID ShowChartX ShowChartY ShowChartZ
-- ----- ---------- ---------- ----------
1 2 1 0 1
The columns of ShowChartX, ShowChartY, and ShowChartZ are of type BIT (boolean).
I've now created a standalone table that keeps a record/reference of each chart. Each Chart record has a Chart_ID - the aim here is to use an ID for each type of chart instead of horizontally scaling a 'ShowChart' column for each type of chart going forward. Essentially, I would like to map all columns of 'ShowChart' to their actual Chart_ID key in the table I mention below:
The new schema would look like this:
ID FK_ID Chart_ID
-- ----- --------
1 2 1
2 2 2
I've started looking at Pivot/Unpivot, but I'm not sure if it's the correct operation. Could anyone please point me in the right direction here? Thanks in advance!
This will UNPIVOT the data. You can also, join the charts table by name in order to get the chart_id and check for differences with the new table:
DECLARE #DataSource TABLE
(
[ID] INT
,[FK_ID] INT
,[ShowChartX] BIT
,[ShowChartY] BIT
,[ShowChartZ] BIT
);
INSERT INTO #DataSource ([ID], [FK_ID], [ShowChartX], [ShowChartY], [ShowChartZ])
VALUES (1, 2, 1, 0, 1);
SELECT [ID]
,[FK_ID]
,[column] AS [chart_name]
FROM #DataSource DS
UNPIVOT
(
[value] FOR [column] IN ([ShowChartX], [ShowChartY], [ShowChartZ])
) UNPVT
WHERE [value] = 1;
For checking for differences it's pretty easy to use EXCEPT - for example:
SELECT *
FROM T1
EXCEPT
SELECT *
FROM T2;
to get records that are not including in T2 but in T1 and then the reverse:
SELECT *
FROM T2
EXCEPT
SELECT *
FROM T1;
Thanks to #gotqn for the table definition and values.
The same result can be achieved using CROSS APPLY. Here, I am deriving Chart_Id based on ChartType, as I don't have the table reference for ChartTypes. Ideally, You can join with ChartTypes to get the corresponding Chart_Id.
DECLARE #DataSource TABLE
(
[ID] INT
,[FK_ID] INT
,[ShowChartX] BIT
,[ShowChartY] BIT
,[ShowChartZ] BIT
);
INSERT INTO #DataSource ([ID], [FK_ID], [ShowChartX], [ShowChartY], [ShowChartZ])
VALUES (1, 2, 1, 0, 1);
SELECT id,
fk_id,
CASE charttype
WHEN 'ChartX' THEN 1
WHEN 'ChartY' THEN 3
WHEN 'ChartZ' THEN 2
END AS Chart_ID
FROM #DataSource
CROSS apply (VALUES('ChartX', showchartx),
('ChartY', showcharty),
('ChartZ', showchartz)) AS t(charttype, isavailable)
WHERE isavailable <> 0;
Result set
+----+-------+----------+
| ID | FK_ID | Chart_ID |
+----+-------+----------+
| 1 | 2 | 1 |
| 1 | 2 | 2 |
+----+-------+----------+
I've looked at some answers but none of them seem to be applicable to me.
Basically I have this result set:
RowNo | Id | OrderNo |
1 101 1
2 101 10
I just want to convert this to
| Id | OrderNo_0 | OrderNo_1 |
101 1 10
I know I should probably use PIVOT. But the syntax is just not clear to me.
The order numbers are always two. To make things clearer
And if you want to use PIVOT then the following works with the data provided:
declare #Orders table (RowNo int, Id int, OrderNo int)
insert into #Orders (RowNo, Id, OrderNo)
select 1, 101, 1 union all select 2, 101, 10
select Id, [1] OrderNo_0, [2] OrderNo_1
from (
select RowNo, Id, OrderNo
from #Orders
) SourceTable
pivot (
sum(OrderNo)
for RowNo in ([1],[2])
) as PivotTable
Reference: https://learn.microsoft.com/en-us/sql/t-sql/queries/from-using-pivot-and-unpivot?view=sql-server-2017
Note: To build each row in the result set the pivot function is grouping by the columns not begin pivoted. Therefore you need an aggregate function on the column that is being pivoted. You won't notice it in this instance because you have unique rows to start with - but if you had multiple rows with the RowNo and Id you would then find the aggregation comes into play.
As you say there are only ever two order numbers per ID, you could join the results set to itself on the ID column. For the purposes of the example below, I'm assuming your results set is merely selecting from a single Orders table, but it should be easy enough to replace this with your existing query.
SELECT o1.ID, o1.OrderNo AS [OrderNo_0], o2.OrderNo AS [OrderNo_1]
FROM Orders AS o1
INNER JOIN Orders AS o2
ON (o1.ID = o2.ID AND o1.OrderNo <> o2.OrderNo)
From your sample data, simplest you can try to use min and MAX function.
SELECT Id,min(OrderNo) OrderNo_0,MAX(OrderNo) OrderNo_1
FROM T
GROUP BY Id
I have a simple table from a select query that looks like this
CATEGORY | EQUAL | LESS | GREATER
VALUE | 60 | 100 | 20
I want to be able to transpose it so it looks like this
CATEGORY | VALUE
EQUAL | 60
LESS | 100
GREATER | 20
I tried using the pivot function in oracle but I can't seem to get it to work.
I've tried looking all over online but I can't find anything that will help me.
Any help is much appreciated thank you!
Using unpivot -
CREATE TABLE Table1
(CATEGORY varchar2(5), EQUAL int, LESS int, GREATER int)
;
INSERT ALL
INTO Table1 (CATEGORY, EQUAL, LESS, GREATER)
VALUES ('VALUE', 60, 100, 20)
SELECT * FROM dual
;
Query -
select COL AS CATEGORY,VALUE from table1
unpivot (value for COL in (EQUAL, LESS, GREATER));
Result -
CATEGORY VALUE
EQUAL 60
LESS 100
GREATER 20
You can use union all:
select 'EQUAL' as category, equal as value from t union all
select 'LESS' as category, less from t union all
select 'GREATER' as category, greater from t;
If you had a large table, you might want to try some other method (such as a lateral join in Oracle 12c). But for a small table, this is fine.
You may unpivot your values by contribution of DECODE and CONNECT BY clauses :
select decode(myId, 1, 'EQUAL',
2, 'LESS',
3, 'GREATER') as category,
decode(myId, 1, EQUAL,
2, LESS,
3, GREATER) as value
from mytable
cross join (select level as myId from dual connect by level <= 3);
CATEGORY VALUE
-------- -----
EQUAL 60
LESS 100
GREATER 20
SQL Fiddle Demo
I have an odd requirement which ideally should be solved in SQL, not the surrounding app.
I need to select exactly 5 rows regardless of how many are actually available. In practice the number of rows available will usually be less than 5 and on some rare occasions it will be more than 5. The "extra" rows should have null in every column.
The app is written in a technology that isn't Turing Complete. This requirement is much more difficult to solve in the app's code than you might imagine! To describe it, the app is effectively a transformer: It takes in a bunch of queries and spits out a report. So please understand the app is NOT written in a "programming language" in the traditional sense.
So for example, if I have a table:
A | B
-----
1 | X
2 | Y
3 | Z
Then a valid result would be
A | B
-----------
2 | Y
1 | X
3 | Z
null | null
null | null
I know this is an unusual requirement. Sadly it can't be solved in the application due to the technology being used.
Ideally this shouldn't require changes to the database but if there is no other way that changes can be arranged.
Any suggestions?
You can do something like this:
select top 5 a, b
from (select a, b, 1 as priority from t union all
select null, null, 2 cross join
(values(1, 2, 3, 4, 5)) v(5)
) x
order by priority;
That is, create dummy rows, append them, and then choose the first five.
I do think that this work should be done in the app, but you can do it in SQL.
Create Table #Test (A int, B int)
Insert #Test Values (1,1)
Insert #Test Values (2,1)
Insert #Test Values (3,1)
Select Top 5 * From
(
Select A, B From #Test
Union All
Select Null, Null
Union All
Select Null, Null
Union All
Select Null, Null
Union All
Select Null, Null
Union All
Select Null, Null
) A
Wrap this in a stored proc..
declare #rowcount int
select top 5* from dbo.test
set #rowcount=##rowcount
if #rowcount<5
Begin
select * from dbo.test
union all
select null from dbo.numbers where n<=5-#rowcount
End
If you use some sort of tally table (although the numbers themselves do not matter, only that the table has enough records), you can use it to create the dummy rows. e.g. using sys.columns:
select top 5 a,b from
(
select a, b, 0 ord from yourTable
union all
select null a,null b, 1 from sys.columns
) t
order by ord
The advantage of the tally would be that if you need another number of rows in the future, you only need to change the top x (provided the tally table has enough rows)
Get those 3 records from your table.
Take a counter variable.
and then from your code add the NULL content until your counter gets 5.
I've got a set of data that looks something like this:-
product May_Qty June_Qty
--------- --- -----------
p1 2 44
p2 1 54
p3 5 55
i want the result like: (using pivot) or other methods
product Month Qty
--------- --- -----------
p1 May 2
p1 June 44
p2 May 1
p2 June 54
p3 May 5
p3 June 55
I must admit: The whole design smells a bit. Normally it is the other way round: Data is in a list and people want it as pivoted (wide) list for displaying purpose. So consider to work with a table with the design you want as target at the moment...
If you really want to stick with this, here are two approaches which produce the same output:
DECLARE #tbl TABLE(product VARCHAR(100),May_Qty INT,June_Qty INT);
INSERT INTO #tbl VALUES
('p1',2,44)
,('p2',1,54)
,('p3',5,55);
With UNION ALL you create a derived table with the values as list and then you sort it
SELECT product,[Month],Qty
FROM
(
SELECT product,5 AS MonthIndex,'May' AS [Month],May_Qty As Qty
FROM #tbl
UNION ALL
SELECT product,6,'June',June_Qty
FROM #tbl
) AS parted
ORDER BY product,MonthIndex;
UNPIVOT does roughly the same:
SELECT up.*
FROM
(SELECT product, May_Qty AS May, June_Qty AS June FROM #tbl) AS tbl
UNPIVOT
(Qty FOR [Month] IN(May,June)) AS up