Turn columns into separate table with column name as values - sql

I'm working on an extraction and I have an issue I can't quite solve.
My extraction looks like this :
+--------+-------+----------+----------+-----+------------+
| ID | Info1 | Product1 | Product2 | ... | Product300 |
+--------+-------+----------+----------+-----+------------+
| 1 | Paul | 2 | | | |
+--------+-------+----------+----------+-----+------------+
| 2 | Steve | | 1 | | |
+--------+-------+----------+----------+-----+------------+
| 3 | Mark | 2 | | | |
+--------+-------+----------+----------+-----+------------+
| ... | | | | | |
+--------+-------+----------+----------+-----+------------+
| 150000 | Felix | 1 | | | 2 |
+--------+-------+----------+----------+-----+------------+
Products appear as columns. So it's not very easy to use.
I want to split my table with product as an external table like :
+--------+----------+-------+
| ID | Product | Value |
+--------+----------+-------+
| 1 | Product1 | 2 |
+--------+----------+-------+
| 2 | Product2 | 1 |
+--------+----------+-------+
| ... | | |
+--------+----------+-------+
| 150000 | Product1 | 1 |
+--------+----------+-------+
Initially this extraction is in Excel but I moved to Access for this purpose.
I have basic knowledge of SQL but not enough to figure out a solution.

As jarlh wrote, the only SQL solution is this:
INSERT INTO TargetTable (ID, Product, Value)
SELECT ID, 'Product1' AS Product, Product1 AS Value FROM SrcTable WHERE Product1 IS NOT NULL
UNION ALL
SELECT ID, 'Product2' AS Product, Product2 AS Value FROM SrcTable WHERE Product2 IS NOT NULL
UNION ALL
etc.
Maybe split it up into chunks, I'm not sure how well 300 UNION ALLs will work in one query.

You need to unpivot your table:
https://technet.microsoft.com/en-us/library/ms177410(v=sql.105).aspx
SELECT ID, Product, Value
FROM
(SELECT ID, Product1, Product2, Product3, Product4, Product5
FROM products) p
UNPIVOT
(Value FOR Product IN
(Product1, Product2, Product3, Product4, Product5)
)AS unpvt;

Related

How can I update every fields data when remove rows

I'm looking for an way to update some rows right after I deleted some rows on MariaDB.
For examples, my talbes are look like:
--------------------------------------
| main_id | name | value | sub_id |
--------------------------------------
| 1 | DRINKS | 1000 | COKE |
| 1 | DRINKS | 2000 | BEER |
| 1 | DRINKS | 0600 | WATER |
| 2 | SALAD | 2000 | Peanut |
| 3 | BREADS | 1500 | FLAT |
| 3 | BREADS | 1000 | TOAST |
| 4 | BEEF | 3000 | SAUSAGE|
...
When I remove '2' SALAD, I want to update every rows main_id to main_id-1 like
--------------------------------------
| main_id | name | value | sub_id |
--------------------------------------
| 1 | DRINKS | 1000 | COKE |
| 1 | DRINKS | 2000 | BEER |
| 1 | DRINKS | 0600 | WATER |
| 2 | BREADS | 1500 | FLAT |
| 2 | BREADS | 1000 | TOAST |
| 3 | BEEF | 3000 | SAUSAGE|
...
// | 2 | SALAD | 2000 | Peanut | has removed so every main_id updated.
I cannot use PRIMARY KEY, because the things are so many duplicated.
If I have to join every rows, then I'm worried about performance, so I cannot find the way to solve the problem.
Thanks for your help.
After the Delete statement :
DELETE FROM t WHERE main_id = 2;
an Update statement containing Analytic Functions might be issued provided your DB version is 10.2+ :
UPDATE t
JOIN (WITH t2 AS
(
SELECT LAG(main_id,1) OVER (ORDER BY main_id) AS lg, t.*
FROM t
)
SELECT t2.*,
1 + SUM(CASE WHEN COALESCE(lg,main_id) = main_id THEN 0 ELSE 1 END )
OVER (ORDER BY main_id) AS new_id
FROM t2 ) t2
ON t.main_id = t2.main_id
SET t.main_id = t2.new_id;
to get main_id - 1 is updated for main_id column for main_id >2 for this individual case.
A Delete Trigger containing an Update cannot be applied on the same because of mutating trigger problem.
Demo

Having Groups based on distinct count of another column

I have a table as follow :
+-------------+-----------+------+
| GroupNumber | TeamName | Goal |
+-------------+-----------+------+
| 1 | Sales | ABC |
| 1 | Sales | ABC |
| 1 | Sales | ABC |
| 1 | Design | XYZ |
| 2 | Design | XYZ |
| 2 | Sales | XYZ |
| 2 | technical | XYZ |
| 2 | Support | XYZ |
| 3 | Sales | XYZ |
| 3 | Sales | XYZ |
| 3 | Sales | XYZ |
+-------------+-----------+------+
I want to output only the groups that have unique teams greater than 3.
Only group 2 has this condition so the output is :
Expected Output:
+-------------+-----------+------+
| GroupNumber | TeamName | Goal |
+-------------+-----------+------+
| 2 | Design | XYZ |
| 2 | Sales | XYZ |
| 2 | technical | XYZ |
| 2 | Support | XYZ |
+-------------+-----------+------+
not sure how to utilize this in subquery
SELECT count(Distinct(TeamName))
FROM mytable
group by [GroupNumber]
HAVING COUNT(Distinct[TeamName])>3
Simply put it in a Subquery:
select *
from mytable
where [GroupNumber] in
(
SELECT [GroupNumber]
FROM mytable
group by [GroupNumber]
HAVING COUNT(Distinct[TeamName])>3
)
Please try
SELECT *
FROM mytable where GroupNumber in (select GroupNumber
FROM mytable group by TeamName
HAVING COUNT(TeamName)>3)

postgresql - Change single row to multiple rows

I have a table named payment_info, with the following records.
paymentid | customercode | previousbalance | paymentamount | remainingbalance
-----------------------------------------------------------------------------
PID0001 | CUST024 | 10000 | 2500 | 7500
PID0002 | CUST031 | 8500 | 3500 | 5000
PID0003 | CUST005 | 12000 | 1500 | 10500
Then what I want is to create a 3 rows per row of the above table.
I want my results to look like this.
Payment Group | Payment Line Item | Payment ID | Customer Code | Type | Amount
--------------------------------------------------------------------------------------------------
1 | 1 | PID0001 | CUST024 | PREVIOUS BALANCE | 10000.00
1 | 2 | | | PAYMENT AMOUNT | 2500.00
1 | 3 | | | REMAINING BALANCE | 7500.00
2 | 1 | PID0002 | CUST031 | PREVIOUS BALANCE | 8500.00
2 | 2 | | | PAYMENT AMOUNT | 3500.00
2 | 3 | | | REMAINING BALANCE | 5000.00
3 | 1 | PID0003 | CUST005 | PREVIOUS BALANCE | 12000.00
3 | 2 | | | PAYMENT AMOUNT | 1500.00
3 | 3 | | | REMAINING BALANCE | 10500.00
Here is the query I've started. But it did not return results same as above.
select row_number() over() as id,paymentid,customercode,'PREVIOUS BALANCE' as type,previousbalance from payment_info
union
select row_number() over() as id,'','','PAYMENT AMOUNT' as type,paymentamount from payment_info
union
select row_number() over() as id,'','','REMAINING BALANCE' as type,remainingbalance from payment_info
Is there other ways, where I will not use UNION Keyword? Cause in the real table, I will be using 30+ columns, querying thousands of records.
I also don't know how to create auto generated number (id) from payment group (per payment id) and Payment Line Item (per group).
thanks
version with whitespace (empty text)
The unnest function can do this for you.
And if you want the empty text then you can use this
SELECT ROW_NUMBER() OVER (ORDER BY paymentid) AS "group",
unnest(array[1, 2, 3]) AS "line item",
unnest(array[paymentid, '', '']) AS "paymentid",
unnest(array[customercode, '', '']) AS "customercode",
unnest(array['PREVIOUS BALANCE', 'PAYMENT AMOUNT', 'REMAINING BALANCE']) AS "type",
unnest(array[previousbalance, paymentamount, remainingbalance]) AS "amount"
FROM payment_info
ORDER BY 1, 2 ;
To get this
group | line item | paymentid | customercode | type | amount
-------+-----------+-----------+--------------+-------------------+--------
1 | 1 | PID0001 | CUST024 | PREVIOUS BALANCE | 10000
1 | 2 | | | PAYMENT AMOUNT | 2500
1 | 3 | | | REMAINING BALANCE | 7500
2 | 1 | PID0002 | CUST031 | PREVIOUS BALANCE | 8500
2 | 2 | | | PAYMENT AMOUNT | 3500
2 | 3 | | | REMAINING BALANCE | 5000
3 | 1 | PID0003 | CUST005 | PREVIOUS BALANCE | 12000
3 | 2 | | | PAYMENT AMOUNT | 1500
3 | 3 | | | REMAINING BALANCE | 10500
If you want to have, for example points or other text, or arrows in the empty text columns, you can do this easily with unnest.
You can control the 4 empty text values individually.
SELECT ROW_NUMBER() OVER (ORDER BY paymentid) AS "group",
unnest(array[1, 2, 3]) AS "line item",
unnest(array[paymentid, ' a', ' c']) AS "paymentid",
unnest(array[customercode, ' b', ' d']) AS "customercode",
unnest(array['PREVIOUS BALANCE', 'PAYMENT AMOUNT', 'REMAINING BALANCE']) AS "type",
unnest(array[previousbalance, paymentamount, remainingbalance]) AS "amount"
FROM payment_info
ORDER BY 1, 2 ;
to generate
group | line item | paymentid | customercode | type | amount
-------+-----------+-----------+--------------+-------------------+--------
1 | 1 | PID0001 | CUST024 | PREVIOUS BALANCE | 10000
1 | 2 | a | b | PAYMENT AMOUNT | 2500
1 | 3 | c | d | REMAINING BALANCE | 7500
2 | 1 | PID0002 | CUST031 | PREVIOUS BALANCE | 8500
2 | 2 | a | b | PAYMENT AMOUNT | 3500
2 | 3 | c | d | REMAINING BALANCE | 5000
3 | 1 | PID0003 | CUST005 | PREVIOUS BALANCE | 12000
3 | 2 | a | b | PAYMENT AMOUNT | 1500
3 | 3 | c | d | REMAINING BALANCE | 10500
It's a very flexible solution, you know.
It isn't necessary to always use union queries. Here for example you can use 3 rows and a cross join instead. This has the advantage of only a single pass over the source table.
drop table if exists Table1;
CREATE TABLE Table1
("paymentid" varchar(7), "customercode" varchar(7)
, "previousbalance" int, "paymentamount" int, "remainingbalance" int)
;
INSERT INTO Table1
("paymentid", "customercode", "previousbalance", "paymentamount", "remainingbalance")
VALUES
('PID0001', 'CUST024', 10000, 2500, 7500),
('PID0002', 'CUST031', 8500, 3500, 5000),
('PID0003', 'CUST005', 12000, 1500, 10500)
;
select
paymentid
, customercode
, rn
, typeof
, case when rn = 1 then previousbalance
when rn = 2 then paymentamount
when rn = 3 then remainingbalance
end as Amount
from Table1
cross join (select 1 rn , 'previousbalance' typeof
union all
select 2 , 'paymentamount'
union all
select 3, 'remainingbalance'
) rns
That data/query produces this result:
+----+-----------+--------------+----+------------------+--------+
| | paymentid | customercode | rn | typeof | amount |
+----+-----------+--------------+----+------------------+--------+
| 1 | PID0001 | CUST024 | 1 | previousbalance | 10000 |
| 2 | PID0001 | CUST024 | 2 | paymentamount | 2500 |
| 3 | PID0001 | CUST024 | 3 | remainingbalance | 7500 |
| 4 | PID0002 | CUST031 | 1 | previousbalance | 8500 |
| 5 | PID0002 | CUST031 | 2 | paymentamount | 3500 |
| 6 | PID0002 | CUST031 | 3 | remainingbalance | 5000 |
| 7 | PID0003 | CUST005 | 1 | previousbalance | 12000 |
| 8 | PID0003 | CUST005 | 2 | paymentamount | 1500 |
| 9 | PID0003 | CUST005 | 3 | remainingbalance | 10500 |
+----+-----------+--------------+----+------------------+--------+
Please then note that SQL isn't a "report writer" so blanks in columns for "layout" are not a good fit for SQL which wants to repeat information (like you see above in the result) so that you can sort and filter as needed.

count distinct value of field on every row

table: item_tbl
|item_id |serial_code|item_name|
------------------------------
| 1 | x35 | bullet |
| 2 | 6ox | cord |
| 3 | 0hg | cord |
| 4 | a73 | tv |
| 5 | lo5 | bullet |
I tried to use SELECT serial_code, item_name, COUNT(item_name) FROM item_tbl but not what i expected of course. How can I count the distinct values of item_name to have something like:
|serial_code|item_name| count |
-----------------------------
| x35 | bullet | 2 |
| 6ox | cord | 2 |
| 0hg | cord | 2 |
| a73 | tv | 1 |
| lo5 | bullet | 2 |
SELECT Serial_Code, count.Item_Name, Count
FROM
item_tbl i
INNER JOIN
(
SELECT Item_Name, COUNT(1) Count
FROM item_tbl
GROUP BY item_Name
) count ON i.item_Name = count.Item_Name
Lets see if that works.

SQL to list data in tabular structure

MSSQL Table1 has following data:
AttributeID | ProductID | Attribute | Value |
--------------------------------------------------
1 | 1111 | Attribute1 | Prod1_Val1 |
2 | 1111 | Attribute2 | Prod1_Val2 |
3 | 1111 | Attribute3 | Prod1_Val3 |
4 | 2222 | Attribute1 | Prod2_Val1 |
5 | 2222 | Attribute2 | Prod2_Val2 |
6 | 2222 | Attribute3 | Prod2_Val3 |
7 | 3333 | Attribute1 | Prod3_Val1 |
8 | 3333 | Attribute2 | Prod3_Val2 |
9 | 3333 | Attribute3 | Prod3_Val3 |
10 | 4444 | Attribute1 | Prod4_Val1 |
11 | 4444 | Attribute2 | Prod4_Val2 |
12 | 5555 | Attribute4 | Prod5_Val1 |
MSSQL Table2 has following Data:
ProductID | ProductName |
--------------------------------------------------
1111 | Product1 |
2222 | Product2 |
3333 | Product3 |
4444 | Product4 |
5555 | Product5 |
The result I would require is:
Product | Product1 | Product3 | Product3 | Product4 | Product5 |
Attribute1 | Prod1_Val1 | Prod2_Val1 | Prod3_Val1 | Prod4_Val1 | -- |
Attribute2 | Prod1_Val2 | Prod2_Val2 | Prod3_Val2 | Prod4_Val2 | -- |
Attribute3 | Prod1_Val3 | Prod2_Val3 | Prod3_Val3 | -- | -- |
Attribute4 | -- | -- | -- | -- | Prod5_Val1 |
Attribute5 | -- | -- | -- | -- | -- |
I would like to know whether this desired result can be achieved with SQL itself using Table1 and Table2. If yes, please assist me by providing the SQL. Thanks for your timely assistance in advance.
As alluded to by TomTom, you will need to use the SQL Server's PIVOT operator:
SELECT Attribute, [Product1], [Product2], [Product3], [Product4], [Product5]
FROM (
SELECT ProductName, Attribute, Value
FROM Table1
INNER JOIN Table2 ON ( Table2.ProductId = Table1.ProductId )
) AS SourceTable
PIVOT (
Min(Value)
FOR ProductName IN ([Product1], [Product2], [Product3], [Product4], [Product5])
) AS PivotTable
Be aware that you will need to decide up front how many columns (that is, [Product1], [Product2], ..., [ProductN]) you would like to output when writing your SQL query.
Also, since PIVOT aggregates data, the example above uses the Min() function. (You could just as easily use Max(), or a different aggregation function, based upon your needs.) Your output will be NULL for all elements that do not have values.