How to convert columns to rows? - sql

I have a table like this one
RowNum | TranNo | nTotalSales | nBalance
1 | 1 | 800 | 0
and I want to display it this way
RowNum | 1
cTranNo | 1
nTotalSales | 800
nBalance | 0
How can I do this?

Here is a complete working example, when you you do an UNPIVOT, which is what your are asking for, your 'value' types need to be of the same type, so cast them however you want. In my example, I have cast them all to VARCHAR(20):
DECLARE #bob TABLE
(
RowNum INT,
TranNo INT,
nTotalSales INT,
nBalance INT
);
INSERT INTO #bob(RowNum, TranNo, nTotalSales, nBalance)
VALUES(1, 1, 800, 0);
WITH T AS (
SELECT CAST(RowNum AS VARCHAR(20)) AS RowNum,
CAST(TranNo AS VARCHAR(20)) AS TranNo,
CAST(nTotalSales AS VARCHAR(20)) AS nTotalSales,
CAST(nBalance AS VARCHAR(20)) AS nBalance
FROM #bob
)
SELECT attribute, value
FROM T
UNPIVOT(value FOR attribute IN(RowNum, TranNo, nTotalSales, nBalance)) AS U;

SELECT 'RowNum' TITLE, RowNum AS [VALUE]
FROM TABLE
UNION ALL
SELECT 'TranNo', TranNo
FROM TABLE
UNION ALL
SELECT 'nTotalSales', nTotalSales
FROM TABLE
UNION ALL
SELECT 'nBalance', nBalance
FROM TABLE

It's not real fun, but here's one solution:
SELECT 'RowNum', RowNum FROM tbl
UNION
SELECT 'cTranNo', TranNo FROM tbl
UNION
SELECT 'nTotalSales', nTotalSales FROM tbl
UNION
SELECT 'nBalance', nBalance FROM tbl
That will turn the columns into rows. If you want each of the column-rows to be interlaced, you may need to introduce a record number along with some sorting.
That would look like this:
SELECT 'RowNum' AS ColName, RowNum AS [Value], RowNum FROM tbl
UNION
SELECT 'cTranNo' AS ColName, TranNo, RowNum FROM tbl
UNION
SELECT 'nTotalSales' AS ColName, nTotalSales, RowNum FROM tbl
UNION
SELECT 'nBalance' AS ColName, nBalance, RowNum FROM tbl
ORDER BY RowNum, ColName

Related

How to loop through table using while loop and create another table with values needed

I have two tables: MainTable and MyTable. MyTable has unique ControlNo and ID. I need to add very first EffDate from MainTable to MyTablebased on ID and ControlNo.
For that I need to look at PreviousID column, then see if that PreviousID is in ID column and so on.
Desired output should look like this:
The below is an example with dummy data of getting proper EffDate by supplying an ID value. It works, but how can I loop through the whole MainTable, retrieve ID's and EffDate into separate table, then join that table to MyTable?
-- function returns PreviousID based on ID
CREATE FUNCTION [dbo].[GetPriorQuoteID](#ID varchar(50))
RETURNS varchar(50)
AS
BEGIN
DECLARE #RetVal varchar(50)
SET #RetVal = NULL
SELECT TOP 1 #RetVal = MainTable.PreviousID
FROM MainTable
WHERE MainTable.ID = #ID
RETURN #RetVal
END
-- create sample table
IF OBJECT_ID('MainTable') IS NOT NULL DROP TABLE MainTable;
select 3333 as ControlNo, 'QuoteID3' as ID, 'QuoteID2' as PreviousID, '2020-08-25' as EffDate
into MainTable
union all select 2222 as COntrolNo, 'QuoteID2', 'QuoteID1', '2019-08-25'
union all select 1111 as COntrolNo, 'QuoteID1', NULL, '2018-08-25'
union all select 7777 as COntrolNo, 'QuoteID6', 'QuoteID5', '2020-02-10'
union all select 6666 as COntrolNo, 'QuoteID5', NULL, '2019-02-10'
select * from MainTable
DECLARE #PriorQuote varchar(50)
DECLARE #RetVal VARCHAR(50) = ''
DECLARE #ControlNo INT
DECLARE #ID varchar(50) = 'QuoteID3'
SELECT TOP 1 #ControlNo = MainTable.ControlNo FROM MainTable WHERE MainTable.ID = #ID
Set #PriorQuote = #ID
SELECT TOP 1 #PriorQuote = MainTable.ID FROM MainTable WHERE MainTable.ControlNo = #ControlNo
WHILE dbo.GetPriorQuoteID(#PriorQuote) IS NOT NULL AND dbo.GetPriorQuoteID(#PriorQuote)<> #PriorQuote
BEGIN
SET #PriorQuote = dbo.GetPriorQuoteID(#PriorQuote)
END
SELECT TOP 1 #RetVal = CONVERT(VARCHAR(10), MainTable.EffDate, 101)
FROM MainTable
WHERE MainTable.ID = #PriorQuote
SELECT #RetVal
-- clean up
drop table MainTable
drop function GetPriorQuoteID
UPDATE: Adding dummy data tables
-- create sample table #MainTable
IF OBJECT_ID('tempdb..#MainTable') IS NOT NULL DROP TABLE #MainTable;
create table #MainTable (ControlNo int, ID varchar(50), PreviousID varchar(50), EffDate date)
insert into #MainTable values
(3333,'QuoteID3','QuoteID2', '2020-08-25'),
(2222,'QuoteID2','QuoteID1', '2019-08-25'),
(1111,'QuoteID1',NULL, '2018-08-25'),
(7777,'QuoteID6','QuoteID5', '2020-02-10'),
(6666,'QuoteID5',NULL, '2019-02-10')
--select * from #MainTable
-- create sample table #MyTable
IF OBJECT_ID('tempdb..#MyTable') IS NOT NULL DROP TABLE #MyTable;
create table #MyTable (ControlNo int, ID varchar(50), EffDate date)
insert into #MyTable values
(3333,'QuoteID3',NULL),
(7777,'QuoteID6',NULL)
--select * from #MyTable
You can use a recursive query to traverse the hierarchy.
I would start by joining the original table with the main table, which restricts the paths to just the rows we are interested in. Then, you can recurse towards the parent. Finally, we need to filter on the top parent per path: top() and row_number() come handy for this.
Consider:
with cte as (
select t.controlno, t.id, m.previousid, m.effdate, 1 lvl
from #maintable m
inner join #mytable t on t.controlno = m.controlno and t.id = m.id
union all
select c.controlno, c.id, m.previousid, m.effdate, c.lvl + 1
from cte c
inner join #maintable m on m.id = c.previousid
)
select top(1) with ties controlno, id, effdate
from cte
order by row_number() over(partition by controlno, id order by lvl desc)
Demo on DB Fiddle:
controlno | id | effdate
--------: | :------- | :---------
3333 | QuoteID3 | 2018-08-25
7777 | QuoteID6 | 2019-02-10
using CTE like below you can get the desired results.
See live demo
Learn more about recursive CTEs here
; with cte as
(
select EffDate, ControlNo, ID, Level=1 from MainTable
where PreviousID is NULL
union all
select C.EffDate, M.ControlNo, M.ID, Level=Level+1 from MainTable AS M
join cte as C on C.ID=M.PreviousID
)
select MyTable.*,cte.EffDate from cte join MyTable on Mytable.ID=cte.ID
You can use a recursive CTE for this:
WITH cte
AS
(
SELECT m.ID,m.PreviousID
FROM MainTable m
JOIN MainTable m2
ON m.previousID = m2.ID
WHERE m2.previousID IS NULL
UNION ALL
SELECT m2.ID,cte.previousID
FROM cte
JOIN MainTable m2
ON m2.previousID = cte.ID
)
SELECT *
FROM cte;
Here is a working example of the CTE approach with the table provided
;with recur_cte(ControlNo, ID, PreviousID, EffDate, HLevel) as (
select mt.ControlNo, cast(null as varchar(100)), mt.PreviousID, mt.EffDate, 1
from MainTable mt
where not exists(select 1
from MainTable mt_in
where mt.ID=mt_in.PreviousID)
union all
select rc.ControlNo, rc.ID, mt.PreviousID, mt.EffDate, rc.HLevel+1
from recur_cte rc
join MainTable mt on rc.PreviousID=mt.ID and rc.EffDate>mt.EffDate)
select * from recur_cte;
Results
ControlNo ID PreviousID EffDate HLevel
3333 NULL QuoteID2 2020-08-25 1
7777 NULL QuoteID5 2020-02-10 1
7777 NULL NULL 2019-02-10 2
3333 NULL QuoteID1 2019-08-25 2
3333 NULL NULL 2018-08-25 3

SQL paging with non-fixed-length page size

The table is simple: It has 2 columns, id and type. The type can be 1 or 2.
Now I want the first 10 entries with type 1, in a sequence ordered by id, and if there are type 2 entries somewhere in between then they should be included in the result set too. What is the SQL query?
It's basically paging but the number of rows per page can vary so can't use OFFSET and LIMIT.
For SQL Server you can use CTE to make request for type 1 records once and then union with records of Type 2.
I selected only 5 rows to use less test data.
create table #TEST (Id int, Type int)
insert into #TEST
select 1,2 union
select 2,1 union
select 3,1 union
select 4,2 union
select 5,2 union
select 6,1 union
select 7,1 union
select 8,2 union
select 9,1 union
select 10,1 union
select 11,2 union
select 12,2 union
select 13,1 union
select 14,1 union
select 15,1
go
with list1 (Id, Type)
as
(
select *
from #Test
where Type = 1
order by Id
offset 0 rows fetch next 5 rows only
)
select *
from list1
union all
select *
from #Test
where Type = 2 and Id > (select min(Id) from list1) and Id < (select max(Id) from list1)
order by Id
Should look something like this:
DECLARE #limit int = 25, #offset int = 10
DECLARE #ret TABLE (
ID INT,
TYPEID INT)
INSERT INTO #ret
SELECT Id, TypeId
FROM Logs
WHERE TypeId = 1
ORDER BY id
LIMIT #limit OFFSET #offset;
INSERT INTO #ret
SELECT id, TypeId
FROM Logs
WHERE TypeId = 2
AND ID BETWEEN (SELECT MAX(ID) FROM #ret) AND (SELECT MIN(ID) FROM #ret)
SELECT *
FROM #ret
ORDER BY ID
You could collapse this into a single SQL statement using a UNION, but it would have to query for the same data (limit and offset) more than once.

Unique Count - TSQL

CODE
CREATE TABLE #TEMP (ID INT, AVAIL BIT, FK INT, DT DATETIME);
INSERT INTO #TEMP (ID,AVAIL,FK,DT)
SELECT 1,1,1,GETDATE()
UNION ALL
SELECT 2,0,2,GETDATE()
UNION ALL
SELECT 3,1,3,GETDATE()
UNION ALL
SELECT 1,1,4,GETDATE()
UNION ALL
SELECT 4,0,5,GETDATE()
UNION ALL
SELECT 5,1,6,GETDATE();
CREATE TABLE #FK (FK INT, DT2 DATETIME)
INSERT INTO #FK (FK, DT2)
SELECT 1,NULL
UNION
SELECT 2,DATEADD(DAY,1,GETDATE())
UNION
SELECT 3,DATEADD(DAY,1,GETDATE())
UNION
SELECT 4,NULL
UNION
SELECT 5,NULL
UNION
SELECT 6,DATEADD(DAY,1,GETDATE())
UNION
SELECT 7,DATEADD(DAY,1,GETDATE())
SELECT
[TotalIds] = COUNT(DISTINCT ID)
,[TotalAvail] = SUM(CASE WHEN [AVAIL] = 1 THEN 1 ELSE 0 END)
,[DTDIFF] = SUM(DATEDIFF(DAY,T1.DT,F.DT2))
FROM #TEMP T1 INNER JOIN #FK F
ON T1.FK = F.FK;
DROP TABLE #TEMP;
DROP TABLE #FK;
OUTPUT
TotalIds TotalAvail DTDIFF
5 4 3
DESIRED OUTPUT
TotalIds TotalAvail DTDIFF
5 3 3
GOAL:
I want to get sum/count of UNIQUE IDs where [AVAIL] = 1.
I can do that by COUNT(DISTINCT ID) WHERE [AVAIL] = 1 BUT... I need to do that within this SUM since I'm querying other data within the same query.
Desired output = 3
(for ID 1, 3, and 5).
Updated with Current/Desired output.
Updated with more data.
You could change UNION ALL for UNION and remove the duplicates
But you mention otherValue, so maybe you need something like this
SELECT SUM(otherValue)
FROM (
SELECT DISTINCT ID, AVAIL, otherValue
FROM TEMP
WHERE [AVAIL] = 1
) T
CREATE TABLE #TEMP (ID INT, AVAIL BIT, FK INT, DT DATETIME);
INSERT INTO #TEMP (ID,AVAIL,FK,DT)
SELECT 1,1,1,GETDATE()
UNION ALL
SELECT 2,0,2,GETDATE()
UNION ALL
SELECT 3,1,3,GETDATE()
UNION ALL
SELECT 1,1,4,GETDATE()
UNION ALL
SELECT 4,0,5,GETDATE()
UNION ALL
SELECT 5,1,6,GETDATE();
CREATE TABLE #FK (FK INT, DT2 DATETIME)
INSERT INTO #FK (FK, DT2)
SELECT 1,NULL
UNION
SELECT 2,DATEADD(DAY,1,GETDATE())
UNION
SELECT 3,DATEADD(DAY,1,GETDATE())
UNION
SELECT 4,NULL
UNION
SELECT 5,NULL
UNION
SELECT 6,DATEADD(DAY,1,GETDATE())
UNION
SELECT 7,DATEADD(DAY,1,GETDATE())
SELECT
[TotalIds] = COUNT(DISTINCT ID)
,[TotalAvail] = COUNT(DISTINCT CASE WHEN [AVAIL] = 1 THEN ID ELSE NULL END)
,[DTDIFF] = SUM(DATEDIFF(DAY,T1.DT,F.DT2))
FROM #TEMP T1 INNER JOIN #FK F
ON T1.FK = F.FK;
DROP TABLE #TEMP;
DROP TABLE #FK;
Use the cte result for your further process.
;WITH CTE_Temp AS
(SELECT COUNT(DISTINCT ID) [TotalAvail]
FROM #TEMP
WHERE [Avail]=1)
SELECT [TotalAvail]
FROM CTE_Temp

How to insert different values from same row in multiple rows of a new table

I have the A table:
id bigint,
a integer,
b integer,
c integer,
date date
and I have to put every single value in a new table following by the date, in this way:
B table:
id bigint,
type integer,
date date
For example, if in my A table I had a row like this:
id a b c date
13 5 4 7 2014-11-09
I would like to put this values in B table like this:
id type date
1 5 2014-11-09,
2 4 2014-11-09,
3 7 2014-11-09
Any suggestions?
Unpivot first the data using UNION ALL and then assign a new id using ROW_NUMBER:
SELECT
ROW_NUMBER() OVER(ORDER BY id, col) AS id,
type,
date
FROM (
SELECT id, 'a' AS col, a AS type, date FROM tableA UNION ALL
SELECT id, 'b' AS col, b AS type, date FROM tableA UNION ALL
SELECT id, 'c' AS col, c AS type, date FROM tableA
) t
If the new id is auto generated, you do not need the ROW_NUMBER at all.
SELECT a AS type, date FROM tableA UNION ALL
SELECT b AS type, date FROM tableA UNION ALL
SELECT c AS type, date FROM tableA
INSERT INTO A
( id ,
a ,
b ,
c ,
date )
VALUES ( 13,
5 ,
4 ,
7 ,
2014-11-09
)
ALTER TRIGGER NewTrigger ON A
AFTER INSERT
AS
BEGIN
SET NOCOUNT ON;
DECLARE #id AS NVARCHAR(50)
DECLARE #type AS NVARCHAR(50)
DECLARE #date AS INT
SELECT #Date = INSERTED.Date
FROM INSERTED
INSERT INTO B
( Date,
Type,
id
)
VALUES ( #Date,
4,
1
)
END
GO
SELECT * A
SELECT * FROM B
In Oracle you can try using REGEXP to simulate the sqame . Hope this helps.
SELECT DISTINCT LEVEL,
TRIM(regexp_substr(a.colk,'[^,]+', 1, level)) TYPE,
SYSDATE
FROM
(SELECT LEVEL,
COL1
||','
||COL2
||','
||COL3 colk,
SYSDATE
FROM
(SELECT 13 AS ID,5 COL1,4 AS COL2,7 AS COL3,sysdate AS dt FROM DUAL
)
CONNECT BY LEVEL <= REGEXP_COUNT(COL1
||','
||COL2
||','
||COL3,',')+1
)a
CONNECT BY regexp_substr(a.colk, '[^,]+', 1, level) IS NOT NULL;
Contest for shortest code :) My variant is:
insert into b(type, date)
select unnest(array[a,b,c]), date from a;
Assuming that the id column in the b table is autoincremented.

SQL grouping by parent child

If I had this structure with the columns:
Primary_Key, Name, Parent_Primary_ID, DISPLAY_ORDER
1 Event NULL 1
2 News NULL 2
3 Event_List 1 1
4 Event_Detail 1 2
5 News_List 2 1
6 News_Details 2 2
how would you return data like:
1 Event
3 Event_List
4 Event_Detail
2 News
5 News_List
6 News_Detail
Thanks
Rob
If SQL Server 2005+
DECLARE #YourTable TABLE
(Primary_Key INT PRIMARY KEY,
Name VARCHAR(100),
Parent_Primary_ID INT NULL,
DISPLAY_ORDER INT)
INSERT INTO #YourTable
SELECT 1,'Event',NULL,1 UNION ALL
SELECT 2,'News',NULL,2 UNION ALL
SELECT 3,'Event_List',1,1 UNION ALL
SELECT 4,'Event_Detail',1,2 UNION ALL
SELECT 5,'News_List',2,1 UNION ALL
SELECT 6,'News_Details',2,2;
WITH Hierarchy
AS (SELECT *,
path = CAST(DISPLAY_ORDER AS VARCHAR(100))
FROM #YourTable
WHERE Parent_Primary_ID IS NULL
UNION ALL
SELECT y.Primary_Key,
y.Name,
y.Parent_Primary_ID,
y.DISPLAY_ORDER,
CAST(path + '.' + CAST(y.DISPLAY_ORDER AS VARCHAR) AS VARCHAR(100))
FROM #YourTable y
JOIN Hierarchy h
ON h.Primary_Key = y.Parent_Primary_ID)
SELECT Primary_Key,
Name
FROM Hierarchy
ORDER BY path
Try (asumming standardish sql is supported)
DECLARE #YourTable TABLE
(Primary_Key INT PRIMARY KEY,
Name VARCHAR(100),
Parent_Primary_ID INT NULL,
DISPLAY_ORDER INT)
INSERT INTO #YourTable
SELECT 1,'Event',NULL,1 UNION ALL
SELECT 2,'News',NULL,2 UNION ALL
SELECT 3,'Event_List',1,1 UNION ALL
SELECT 4,'Event_Detail',1,2 UNION ALL
SELECT 5,'News_List',2,1 UNION ALL
SELECT 6,'News_Details',2,2;
select
primary_key = t1.primary_key,
name = t1.name
from
#YourTable t1
left join #YourTable t2 on t1.parent_primary_id = t2.Primary_Key
order by
coalesce(t2.DISPLAY_ORDER,t1.DISPLAY_ORDER,0),
case
when t2.Primary_Key is null then 0
else t1.DISPLAY_ORDER
end
I don't see any grouping in your results. Unless you are trying to do something you aren't telling us I would use the query below:
SELECT Primary_Key, Name FROM YourTable
I don't see how you are ordering those results so I didn't try to order them.