MySQL Column Value Pivot - sql

I have a MySQL InnoDB table laid out like so:
id (int), run_id (int), element_name (varchar), value (text), line_order, column_order
`MyDB`.`MyTable` (
`id` bigint(20) NOT NULL,
`run_id` int(11) NOT NULL,
`element_name` varchar(255) NOT NULL,
`value` text,
`line_order` int(11) default NULL,
`column_order` int(11) default NULL
It is used to store data generated by a Java program that used to output this in CSV format, hence the line_order and column_order.
Lets say I have 4 entries (according to the table description):
1,1,'ELEMENT 1','A',0,0
2,1,'ELEMENT 2','B',0,1
3,1,'ELEMENT 1','C',1,0
4,1,'ELEMENT 2','D',1,1
I want to pivot this data in a view for reporting so that it would look like more like the CSV would, where the output would look this:
---------------------
|ELEMENT 1|ELEMENT 2| <--- Element Name
---------------------
| A | B | <--- Value From line_order = 0
---------------------
| C | D | <--- Value From line_order = 1
---------------------
The data coming in is extremely dynamic; it can be in any order, can be any of over 900 different elements, and the value could be anything. The Run ID ties them all together, and the line and column order basically let me know where the user wants that data to come back in order. I want it to sort/group by line and column order in the displayed matrix.

You can do that with a self join:
select e1.element_name, e1.value, e2.value
from MyTable e1
inner join MyTable e2
on e1.element_name = e2.element_name
and e2.line_order = 1
where e1.line_order = 0
The from selects line_order = 1, the inner join selects line_order = 2.
Switch inner join to cross join if you'd like to return elements with only one line_order.
In reply to your comment, you could write out a fixed number of elements like:
select
line_order
, max(case when element_name = 'element 1' then value end) as Element1Value
, max(case when element_name = 'element 2' then value end) as Element2Value
, max(case when element_name = 'element 3' then value end) as Element3Value
, ...
from MyTable
group by line_order
The max() construct assumes that (element_name, line_order) is unique.

Related

Compare two rows (both with different ID) & check if their column values are exactly the same. All rows & columns are in the same table

I have a table named "ROSTER" and in this table I have 22 columns.
I want to query and compare any 2 rows of that particular table with the purpose to check if each column's values of that 2 rows are exactly the same. ID column always has different values in each row so I will not include ID column for the comparing. I will just use it to refer to what rows will be used for the comparison.
If all column values are the same: Either just display nothing (I prefer this one) or just return the 2 rows as it is.
If there are some column values not the same: Either display those column names only or display both the column name and its value (I prefer this one).
Example:
ROSTER Table:
ID
NAME
TIME
1
N1
0900
2
N1
0801
Output:
ID
TIME
1
0900
2
0801
OR
Display "TIME"
Note: Actually I'm okay with whatever result or way of output as long as I can know in any way that the 2 rows are not the same.
What are the possible ways to do this in SQL Server?
I am using Microsoft SQL Server Management Studio 18, Microsoft SQL Server 2019-15.0.2080.9
Please try the following solution based on the ideas of John Cappelletti. All credit goes to him.
SQL
-- DDL and sample data population, start
DECLARE #roster TABLE (ID INT PRIMARY KEY, NAME VARCHAR(10), TIME CHAR(4));
INSERT INTO #roster (ID, NAME, TIME) VALUES
(1,'N1','0900'),
(2,'N1','0801')
-- DDL and sample data population, end
DECLARE #source INT = 1
, #target INT = 2;
SELECT id AS source_id, #target AS target_id
,[key] AS [column]
,source_Value = MAX( CASE WHEN Src=1 THEN Value END)
,target_Value = MAX( CASE WHEN Src=2 THEN Value END)
FROM (
SELECT Src=1
,id
,B.*
FROM #roster AS A
CROSS APPLY ( SELECT [Key]
,Value
FROM OpenJson( (SELECT A.* For JSON Path,Without_Array_Wrapper,INCLUDE_NULL_VALUES))
) AS B
WHERE id=#source
UNION ALL
SELECT Src=2
,id = #source
,B.*
FROM #roster AS A
CROSS APPLY ( SELECT [Key]
,Value
FROM OpenJson( (SELECT A.* For JSON Path,Without_Array_Wrapper,INCLUDE_NULL_VALUES))
) AS B
WHERE id=#target
) AS A
GROUP BY id, [key]
HAVING MAX(CASE WHEN Src=1 THEN Value END)
<> MAX(CASE WHEN Src=2 THEN Value END)
AND [key] <> 'ID' -- exclude this PK column
ORDER BY id, [key];
Output
+-----------+-----------+--------+--------------+--------------+
| source_id | target_id | column | source_Value | target_Value |
+-----------+-----------+--------+--------------+--------------+
| 1 | 2 | TIME | 0900 | 0801 |
+-----------+-----------+--------+--------------+--------------+
A general approach here might be to just aggregate over the entire table and report the state of the counts:
SELECT
CASE WHEN COUNT(DISTINCT ID) = COUNT(*) THEN 'Yes' ELSE 'No' END AS [ID same],
CASE WHEN COUNT(DISTINCT NAME) = COUNT(*) THEN 'Yes' ELSE 'No' END AS [NAME same],
CASE WHEN COUNT(DISTINCT TIME) = COUNT(*) THEN 'Yes' ELSE 'No' END AS [TIME same]
FROM yourTable;

Transform void record based on matching non void record

I have to transform 4 fields when certain conditions happen.
The VOID (type = 6) lines have to be transformed with the non VOID (type 0,2).
The Transaction is unique for each line and the VOID line has the originating Transaction in the Voidtran field.
Here's my query:
SELECT CAST(payhist.number AS CHAR) Transaction
,CAST(payhist.descr AS CHAR) TransactionDesc
,CAST(payhist.account AS CHAR) Account
,CAST(payhist.type AS CHAR) Type
,CAST(payhist.voidtran AS CHAR) Voidtran
,CAST(servdef.number AS CHAR) ServDefNum
,CAST(servdef.descr AS CHAR) ServDefDesc
,CAST(LEFT(list_charge.descr,5) AS CHAR) ChargeType
,CAST(list_charge.descr AS CHAR) ChargeTypeDesc
FROM billmax.payhist
LEFT JOIN monthlysale ON monthlysale.payhist = payhist.number
LEFT JOIN servdef ON payhist.servdef = servdef.number
LEFT JOIN lists AS list_charge ON list_charge.value = payhist.charge_type AND list_charge.list='chargetypes'
WHERE payhist.type IN (0,2,6)
AND monthlysale.bookdate BETWEEN '2020-12-01' AND NOW()
AND monthlysale.amount <>0
AND payhist.number IN(9751739,9729411)
Here is what the data looks like and how I want the outcome to look:
I think I need to build a UNION query - one part for Type 0 or 2 and one part for Type 6 but I don't know how to change the 4 fields. All help is greatly appreciated!
If I understand correctly, you want to "spread" values when one is NULL. This uses COALESCE() and window functions:
SELECT COALESCE(payhist.number, MAX(payhist.number) OVER (PARTITION BY payhist.account)) as Transaction
payhist.descras as TransactionDesc
payhist.account as Account
COALESCE(payhist.type, MAX(payhist.type) OVER (PARTITION BY payhist.account) as Type,
. . .
FROM billmax.payhist JOIN
monthlysale
ON monthlysale.payhist = payhist.number LEFT JOIN
servdef
ON payhist.servdef = servdef.number LEFT JOIN
lists AS list_charge
ON list_charge.value = payhist.charge_type AND list_charge.list = 'chargetypes'
WHERE payhist.type IN (0,2,6) AND
monthlysale.bookdate BETWEEN '2020-12-01' AND NOW() AND
monthlysale.amount <> 0 AND
payhist.number IN (9751739, 9729411) ;
I have no idea why you are casting values to strings (especially values that seem to already be strings), so I just removed that logic.
Try using a self-join like this
SELECT prev.t_id,
prev.t_type,
prev.t_voidtran,
ISNULL(prev.tr_ServDefNum, cur.tr_ServDefNum) As 'tr_ServDefNum',
ISNULL(prev.tr_ServDefDesc, cur.tr_ServDefDesc) As 'tr_ServDefDesc'
FROM Tansactions cur
INNER JOIN Tansactions prev ON cur.t_id = prev.t_voidtran
AND prev.t_id = cur.t_voidtran
WHERE prev.t_type IN (0,2,6)
Result
+---------+--------+------------+------------------+------------------+
| t_id | t_type | t_voidtran | tr_ServDefNum | tr_ServDefDesc |
+---------+--------+------------+------------------+------------------+
| 9729411 | 0 | 9751739 | 889 | Blabla |
+---------+--------+------------+------------------+------------------+
| 9751739 | 6 | 9729411 | 889 | Blabla |
+---------+--------+------------+------------------+------------------+
The script I used for the simplified version is
CREATE TABLE Tansactions (t_id int, t_type int, t_voidtran int, tr_ServDefNum int, tr_ServDefDesc VARCHAR(200) )
INSERT INTO Tansactions VALUES (9729411, 0, 9751739, 889, 'Blabla')
INSERT INTO Tansactions VALUES (9751739, 6, 9729411, NULL, NULL)

SQL SELECT column value based on value

I am using Postgresql. I would like to write a SELECT statement with a column value based on the value in the database.
For example.
| id | indicator |
| 1 | 0 |
| 2 | 1 |
indicator can be 0 or 1 only where 0 = manual and 1 = auto.
Expected output from a SELECT *
1 manual
2 auto
You can use a case expression:
select id, case indicator
when 0 then 'manual'
when 1 then 'auto'
end as indicator
from the_table;
If you need that frequently you could create a view for that.
In the long run, it might be better to create a proper lookup table and join to that:
create table indicator
(
id integer primary key,
name text not null
);
insert into indicator (id, name)
values (0, 'manua', 1, 'auto');
alter table the_table
add constraint fk_indicator
foreign key (indicator) references indicator (id);
Then join to it:
select t.id, i.name as indicator
from the_table t
join indicator i on i.id = t.indicator;

SQL Unpivot / Coalesce multiple columns into one column based on values

I am trying to unpivot / coalesce multiple columns into one value, based on values in the target columns.
Given the following sample data:
CREATE TABLE SourceData (
Id INT
,Column1Text VARCHAR(10)
,Column1Value INT
,Column2Text VARCHAR(10)
,Column2Value INT
,Column3Text VARCHAR(10)
,Column3Value INT
)
INSERT INTO SourceData
SELECT 1, NULL, NULL, NULL, NULL, NULL, NULL UNION
SELECT 2, 'Text', 1, NULL, NULL, NULL, NULL UNION
SELECT 3, 'Text', 2, 'Text 2', 1, NULL, NULL UNION
SELECT 4, NULL, NULL, NULL, 1, NULL, NULL
I am trying to produce the following result set:
Id ColumnText
----------- ----------
1 NULL
2 Text
3 Text 2
4 NULL
Where ColumnXText column values become one "ColumnText" value per row, based on the following criteria:
If all ColumnX columns are NULL, then ColumnText = NULL
If a ColumnXValue value is "1" and the ColumnXText IS NULL, then
ColumnText = NULL
If a ColumnXValue value is "1" and the ColumnXText IS NOT NULL, then
ColumnText = ColumnXText.
There are no records with more than one ColumnXValue of "1".
What I'd tried is in this SQL Fiddle # http://sqlfiddle.com/#!6/f2e18/2
I'd tried (shown in SQL fiddle):
Unpivioting with CROSS / OUTER APPLY. I fell down on this approach because I was not able to get WHERE conditions to produce the expected results.
I'd also tried using UNPIVOT, but had no luck.
I was thinking of a brute-force approach that did not seem to be correct. The real source table has 44MM rows. I do not control the schema of the source table.
Please let me know if there's a simpler approach than a brute-force tangle of CASE WHENs. Thank you.
I don't think there is much mileage in trying to be too clever with this
SELECT
Id,
CASE
WHEN COLUMN1VALUE = 1 THEN COLUMN1TEXT
WHEN COLUMN2VALUE = 1 THEN COLUMN2TEXT
WHEN COLUMN3VALUE = 1 THEN COLUMN3TEXT
End as ColumnText
From
Sourcedata
I did have them in 321 order, but considered that the right answer might be hit sooner if the checking is done in 123 order instead (fewer checks, if there are 44million rows, might be significant)
Considering you have 44 million rows, you really don't want to experiment to much to join table on itself with apply or something like that. You need just go through it once, and that's best with simple CASE, what you call "brute-force" approach:
SELECT
Id
, CASE WHEN Column1Value = 1 THEN Column1Text
WHEN Column2Value = 1 THEN Column2Text
WHEN Column3Value = 1 THEN Column3Text
END AS ColumnText
FROM SourceData
But, if you really want to get fancy and write something without case, you could use UNION to merge different columns into one, and then join on it:
wITH CTE_Union AS
(
SELECT Id, Column1Text AS ColumnText, Column1Value AS ColumnValue
FROM SourceData
UNION ALL
SELECT Id, Column2Text, Column2Value FROM SourceData
UNION ALL
SELECT Id, Column3Text, Column3Value FROM SourceData
)
SELECT s.Id, u.ColumnText
FROM SourceData s
LEFT JOIN CTE_Union u ON s.Id = u.id and u.ColumnValue = 1
But I guarantee first approach will outperform this by a margin of 4 to 1
If you do not want to use a case expression, then you can use another outer apply() on a common table expression (or subquery/derived table) of your original unpivot with outer apply():
;with cte as (
select s.Id, oa.ColumnText, oa.ColumnValue
from sourcedata s
outer apply (values
(s.Column1Text, s.Column1Value)
, (s.Column2Text, s.Column2Value)
, (s.Column3Text, s.Column3Value)
) oa (ColumnText, ColumnValue)
)
select s.Id, x.ColumnText
from sourcedata s
outer apply (
select top 1 cte.ColumnText
from cte
where cte.Id = s.Id
and cte.ColumnValue = 1
) x
rextester demo: http://rextester.com/TMBR41346
returns:
+----+------------+
| Id | ColumnText |
+----+------------+
| 1 | NULL |
| 2 | Text |
| 3 | Text 2 |
| 4 | NULL |
+----+------------+
This will give you the first non-null text value in order. It seems as if this is what you are trying to accomplish.
select ID, Coalesce(Column3Text,Column2Text,Column1Text) ColumnText
from SourceData

SQL Subquery Column Where Name Contains a Value

I am trying to figure out how to code a certain subquery. I keep getting the "Subquery returned more than 1 value" error. I need to query the description of a parent order instead of the child.
Example:
Parent Order: 100-01
Child Orders: 100-02, 100-03
Each order has a description. But in one column I need the description of the parent:
WHERE [ORDER] LIKE '%-01'
Current code is below:
SELECT NAME as [Job],
[Master Job Desc] = (SELECT TOP 1 [DESCR] FROM TABLE WHERE [NAME] LIKE '%-01'),
LEFT(or_order.descr,30) as [Description],
FROM TABLE
ORDER BY OR_ORDER.DELIVERY asc, or_op.STARTSEC asc
Any advice? Thanks
EDIT: I forgot, I've already tried adding TOP 1 to the subquery, but it just returns the same value for the entire column. I do not want that, I want to find the parent job order.
EDIT2: Here is the separate SQL for what I'm looking for.
SELECT [DESCR] FROM [Or_Order] WHERE [NAME] LIKE '%-01'
The above statements returns descriptions where the name contains the parent job number (-01). I need that in a single column.
You can use charindex() to get the parent part of the order (up to the hyphen) and concatenate that with '01'. With that, you can join to the same table to get the parent order description.
select
Job = c.Name
, [Master Job Desc] = p.Descr
, [Description] = left(c.descr,30)
from or_order as c
left join or_order as p
on left(c.[order],charindex('-',c.[Order]))+'01' = p.[Order]
order by delivery asc, or_op.startsec asc
rextester: http://rextester.com/GSN90790
create table or_order (
[order] varchar(32)
, Name varchar(32)
, Descr varchar(32)
);
insert into or_order values
('100-01','Name 1','Descr 1')
, ('100-02','Name 2','Descr 2')
, ('100-03','Name 3','Descr 3');
select
Job = c.Name
, [Master Job Desc] = p.Descr
, [Description] = left(c.descr,30)
from or_order as c
left join or_order as p
on left(c.[order],charindex('-',c.[Order]))+'01' = p.[Order]
results:
+--------+-----------------+-------------+
| Job | Master Job Desc | Description |
+--------+-----------------+-------------+
| Name 1 | Descr 1 | Descr 1 |
| Name 2 | Descr 1 | Descr 2 |
| Name 3 | Descr 1 | Descr 3 |
+--------+-----------------+-------------+