Pivoting a table in sql - sql

I want same output shown in Output table. I have TableA and I want to pivote it and want output table as shown in image.
Thanks
I have a table #tableA
+-----+-------------+-------+------------+
| A | Allocations | Seats | EndDate |
+-----+-------------+-------+------------+
| ABC | 450 | 23 | 2017-10-05 |
| ABC | 23 | 765 | 2017-05-01 |
| PQR | 54 | 34 | 2017-07-04 |
| ABC | 234 | 45 | 2017-11-27 |
| PQR | 987 | 76 | 2017-03-05 |
| ABC | 76 | 65 | 2017-02-23 |
| PQR | 89 | 324 | 2017-08-14 |
| ABC | 45 | 34 | 2017-07-13 |
+-----+-------------+-------+------------+
Which can be created and populated as below.
CREATE TABLE #TableA
(
A VARCHAR(50),
Allocations INT,
Seats INT,
EndDate DATETIME
);
INSERT INTO #TableA
VALUES ('ABC',450,23,'2017-10-05'),
('ABC',23,765,'2017-05-01'),
('PQR',54,34,'2017-07-04'),
('ABC',234,45,'2017-11-27'),
('PQR',987,76,'2017-03-05'),
('ABC',76,65,'2017-02-23'),
('PQR',89,324,'2017-08-14'),
('ABC',45,34,'2017-07-13');
A column has ABC and PQR unique values.
Datetime column has multiple values.
How can I get the following output?
Datetime all Datetime values from TableA in column.
Output :-
date | 2017-12-13 | 2017-12-20 | 2017-12-27 | -|-|-|-|-|-|-|-|-|
-------------------------------------------------------------------------------
A | ABC | ABC | ABC |
Allocations | 50 | 50 | 50 |
Seats | 27 | 27 | 27 |
A | PQR | PQR | PQR |
Alloc | 50 | 50 | 50 |
Seats | 12 | 12 | 12 |

This is something that you should do in the reporting layer not SQL.
It is possible in SQL (demo) but not something that SQL is designed to do.
WITH T
AS (SELECT A,
thing,
priority,
value,
d
FROM #TableA
CROSS APPLY (VALUES(CAST(EndDate AS DATE)))D(d)
CROSS APPLY (VALUES(1, 'A', NULL),
(2, 'Allocations', Allocations),
(3, 'Seats', Seats)) V(priority, thing, value))
SELECT thing,
case when thing = 'A' THEN A ELSE CAST(ISNULL([2017-02-23],0) AS VARCHAR(50)) END AS [2017-02-23],
case when thing = 'A' THEN A ELSE CAST(ISNULL([2017-03-05],0) AS VARCHAR(50)) END AS [2017-03-05],
case when thing = 'A' THEN A ELSE CAST(ISNULL([2017-05-01],0) AS VARCHAR(50)) END AS [2017-05-01],
case when thing = 'A' THEN A ELSE CAST(ISNULL([2017-07-04],0) AS VARCHAR(50)) END AS [2017-07-04],
case when thing = 'A' THEN A ELSE CAST(ISNULL([2017-07-13],0) AS VARCHAR(50)) END AS [2017-07-13],
case when thing = 'A' THEN A ELSE CAST(ISNULL([2017-08-14],0) AS VARCHAR(50)) END AS [2017-08-14],
case when thing = 'A' THEN A ELSE CAST(ISNULL([2017-10-05],0) AS VARCHAR(50)) END AS [2017-10-05],
case when thing = 'A' THEN A ELSE CAST(ISNULL([2017-11-27],0) AS VARCHAR(50)) END AS [2017-11-27]
FROM T
PIVOT (SUM(value) FOR d in (
[2017-02-23],
[2017-03-05],
[2017-05-01],
[2017-07-04],
[2017-07-13],
[2017-08-14],
[2017-10-05],
[2017-11-27])) P
ORDER BY A, priority
The above doesn't even address the dynamic aspect. For that you would need to generate a dynamic SQL string based on the above and the dates in #TableA

Related

Change value in row based on a lookup from other rows

I have this data in a table(actually output of a query):
+--------------+------+---------+
| Connection | Pin | Circuit |
+--------------+------+---------+
| Value 1 | 1 | 33 |
| Value 1 | 2 | 1004 |
| Value 1 | 3 | 1015 |
| Value 1 | 4 | |
| Value 2 | SP-A | 1003 |
| Value 2 | SP-A | 1004 |
| Value 2 | SP-A | 1005 |
| Value 2 | SP-B | 1014 |
| Value 2 | SP-B | 1015 |
| Value 2 | SP-B | 1016 |
+--------------+------+---------+
I would like to use an SQL query to change it to this:
(changing the Pin based on a matching Circuit)
e.g.:
For each "SP-A", get the list of possible Circuits (1003, 1004, 1005)
Then look for a matching Circuit in another Connection (here this matches 1004, so we get Pin = 2)
Then replace the original value "SP-A" here with the match "2"
+------------+-------+---------+
| Connection | Pin | Circuit |
+------------+-------+---------+
| Value 1 | 1 | 33 |
| Value 1 | 2 | 1004 |
| Value 1 | 3 | 1015 |
| Value 1 | 4 | |
| Value 2 | *2* | 1003 |
| Value 2 | *2* | *1004*|
| Value 2 | *2* | 1005 |
| Value 2 | *3* | 1014 |
| Value 2 | *3* | *1015*|
| Value 2 | *3* | 1016 |
+------------+-------+---------+
My SQL skills are lacking.
I'm doing this in MS-Access.
First of all, try this, if it works, apply it on your main data.
CREATE TABLE #TEMP
(Connection nvarchar(50),
Pin nvarchar(50),
Circuit nvarchar(50))
INSERT INTO #TEMP
SELECT Connection, Pin, Circuit FROM Table_1
UPDATE TU
SET Pin = (
SELECT '*' + T1.Pin + '*' FROM Table_1 T1
INNER JOIN Table_1 T2 ON T1.Circuit = T2.Circuit AND T1.Connection <> T2.Connection AND T2.Pin = TU.Pin
)
FROM #TEMP TU
WHERE Connection = 'Value 2'
UPDATE TU
SET Circuit = '*' + T2.Circuit + '*'
FROM #TEMP TU
INNER JOIN #TEMP T2 ON TU.Circuit = T2.Circuit AND TU.Connection <> T2.Connection
WHERE TU.Connection = 'Value 2'
SELECT * FROM #TEMP
DROP TABLE #TEMP
You can express the logic using a correlated subquery:
update t
set pin = (select top (1) t2.pin
from t as t2
where t2.circuit = t.circuit and
t2.connection <> t.connection
)
where pin in ('SP-A', 'SP-B');

How to select timestamp values in PostgreSQL under conditions?

I have a database table 'table1' as follows:
f_key | begin | counts|
1 | 2018-10-04 | 15 |
1 | 2018-10-06 | 20 |
1 | 2018-10-08 | 34 |
1 | 2018-10-09 | 56 |
I have another database table 'table2' as follows:
f_key | p_time | percent|
1 | 2018-10-05 | 80 |
1 | 2018-10-07 | 90 |
1 | 2018-10-08 | 70 |
1 | 2018-10-10 | 60 |
The tables can be joined by the f_key field.
I want to get a combined table as shown below:
If the begin time is earlier than any of the p_time then the p_time value in the combined table would be the same as begin time and the percent value would be 50. (As shown in row 1 in the following table)
If the begin time is later than any of the p_time then the p_time value in the combined table would be the very next available p_time and the percent value would be the corresponding value of the selected p_time.
(As shown in row 2, 3 and 4 in the following table)
row | f_key | begin | counts| p_time | percent|
1 | 1 | 2018-10-04 | 15 | 2018-10-04 | 50 |
2 | 1 | 2018-10-06 | 20 | 2018-10-05 | 80 |
3 | 1 | 2018-10-08 | 34 | 2018-10-07 | 90 |
4 | 1 | 2018-10-09 | 56 | 2018-10-08 | 70 |
You can try to use row_number window function to make row number which is the closest row from table1 by begin.
then use coalesce function to let begin time is earlier than any of the p_time then the p_time value in the combined table would be the same as begin time and the percent value would be 50
PostgreSQL 9.6 Schema Setup:
CREATE TABLE table1(
f_key INT,
begin DATE,
counts INT
);
INSERT INTO table1 VALUES (1,'2018-10-04',15);
INSERT INTO table1 VALUES (1,'2018-10-06',20);
INSERT INTO table1 VALUES (1,'2018-10-08',34);
INSERT INTO table1 VALUES (1,'2018-10-09',56);
CREATE TABLE table2(
f_key INT,
p_time DATE,
percent INT
);
INSERT INTO table2 VALUES (1, '2018-10-05',80);
INSERT INTO table2 VALUES (1, '2018-10-07',90);
INSERT INTO table2 VALUES (1, '2018-10-08',70);
INSERT INTO table2 VALUES (1, '2018-10-10',60);
Query 1:
SELECT ROW_NUMBER() OVER(ORDER BY begin) "row",
t1.f_key,
t1.counts,
coalesce(t1.p_time,t1.begin) p_time,
coalesce(t1.percent,50) percent
FROM (
SELECT ROW_NUMBER() OVER(PARTITION BY t1.begin,t1.f_key order by t2.p_time desc) rn,
t2.p_time,
t2.percent,
t1.counts,
t1.f_key,
t1.begin
FROM table1 t1
LEFT JOIN table2 t2 ON t1.f_key = t2.f_key and t1.begin > t2.p_time
)t1
WHERE rn = 1
Results:
| row | f_key | counts | p_time | percent |
|-----|-------|--------|------------|---------|
| 1 | 1 | 15 | 2018-10-04 | 50 |
| 2 | 1 | 20 | 2018-10-05 | 80 |
| 3 | 1 | 34 | 2018-10-07 | 90 |
| 4 | 1 | 56 | 2018-10-08 | 70 |

Adding Missing Days in Data -SQL

I have a table that's only showing weekday values. It's grabbing these from a file that's imported only on the weekdays. I'm needing to also add in the weekend (or holidays) with the previously known day's value. I have asked this question when I was needing it to be used in MS Access. I'm now moving this database to SQL Server.
If you're wanting to see what worked for me in Access, you're more than welcome to check out the link.
I have attempted to adapt the MS Access SQL to SQL Server with:
SELECT a1.IDNbr, a1.Balance, CONVERT(int, DAY(a1.BalDate)) + 3
FROM tblID a1 INNER JOIN tblID a2 ON (CONVERT(int, DAY(a1.BalDate)) + 4 = a2.BalDate) AND (a1.IDNbr = a2.IDNbr)
WHERE NOT EXISTS (
SELECT *
FROM tblID a3
WHERE a3.IDNbr = a1.IDNbr AND a3.BalDate = CONVERT(int, DAY(a1.BalDate)) + 3) AND (DATEPART(W, a1.BalDate) = 6
);
However, I'm getting the Error:
Msg 206, Level 16, State 2, Line 4
Operand type clash: date is incompatible with int
Question: How can I get this query (which I will be turning into an INSERT statement) to show all the missing days within my data and to assign the value of the last known day to the missing days?
Data that I have(starting on Friday):
+-------------------------------------+
|ID | IDNbr | Balance | BalDate |
+-------------------------------------+
|001| 91 | 529 | 1/5/2018 |
|002| 87 | 654 | 1/5/2018 |
|003| 45 | 258 | 1/5/2018 |
|004| 91 | 611 | 1/8/2018 |
|005| 87 | 753 | 1/8/2018 |
|006| 45 | 357 | 1/8/2018 |
|...| .. | ... | ........ |
+-------------------------------------+
'BalDate then skips past 1/6/2018 and 1/7/2018 to 1/8/2018
Data that I'm needing:
+-------------------------------------+
|ID | IDNbr | Balance | BalDate |
+-------------------------------------+
|001| 91 | 529 | 1/5/2018 |
|002| 87 | 654 | 1/5/2018 |
|003| 45 | 258 | 1/5/2018 |
|004| 91 | 529 | 1/6/2018 |
|005| 87 | 654 | 1/6/2018 |
|006| 45 | 258 | 1/6/2018 |
|007| 91 | 529 | 1/7/2018 |
|008| 87 | 654 | 1/7/2018 |
|009| 45 | 258 | 1/7/2018 |
|010| 91 | 611 | 1/8/2018 |
|011| 87 | 753 | 1/8/2018 |
|012| 45 | 357 | 1/8/2018 |
|...| .. | ... | ........ |
+-------------------------------------+
'I'm needing it to add the Saturday(1/6/2018) and Sunday(1/7/2018) before continuing on to 1/8/2018
Any help would be appreciated. Thank you in advance!
If there are downvotes, I ask that you please explain why you are downvoting so I may correct it!
Ok, you're going to need the CalTable() function from Bernd's answer. We're going to use it to create a list of all calendar dates between the MIN(BalDate) and the MAX(BalDate) in tblID. We're also going to CROSS JOIN that with the list of DISTINCT IDNbr values, which I assume is the PK of tblID.
Let's create some sample data.
CREATE TABLE #tblID (ID VARCHAR(3), IDNbr INT, Balance INT, BalDate DATE)
INSERT INTO #tblID
(
ID
,IDNbr
,Balance
,BalDate
)
VALUES
('001',91,529,'1/5/2018'),
('002',87,654,'1/5/2018'),
('003',45,258,'1/5/2018'),
('004',91,611,'1/8/2018'),
('005',87,753,'1/8/2018'),
('006',45,357,'1/8/2018')
Next, we're going to INSERT new records into #tblID for the missing days. The magic here is in the LAG() function, which can looks at a previous row's data. We give it an expression for the offset value, based on the difference between missing date and the last date with data.
;WITH IDs AS
(
SELECT DISTINCT
IDNbr
FROM #tblID
)
,IDDates AS
(
SELECT
BalDate = c.[Date]
,i.IDNbr
FROM [CalTable]((SELECT MIN(BalDate) FROM #tblID), (SELECT MAX(BalDate) FROM #tblID)) c
CROSS APPLY IDs i
)
,FullResults AS
(
SELECT
i.BalDate
,i.IDNbr
,Balance = CASE WHEN t.Balance IS NOT NULL THEN t.Balance
ELSE LAG(t.Balance,
DATEDIFF(
DAY
,(SELECT MAX(t1.BalDate) FROM #tblID t1 WHERE t1.IDNbr = i.IDNbr AND t1.BalDate <= i.BalDate GROUP BY t1.IDNbr)
,i.BalDate
)
) OVER (PARTITION BY i.IDNbr ORDER BY i.BalDate ASC)
END
FROM IDDates i
LEFT JOIN #tblID t ON t.BalDate = i.BalDate AND t.IDNbr = i.IDNbr
)
INSERT INTO #tblID
(
IDNbr
,Balance
,BalDate
)
SELECT
f.IDNbr
,f.Balance
,f.BalDate
FROM FullResults f
LEFT JOIN #tblID t ON t.IDNbr = f.IDNbr AND t.BalDate = f.BalDate
WHERE t.IDNbr IS NULL
At this point, if we didn't care about the ID field, which appears to be a 3-character string representation of the row number, we'd be good. However, while I don't think it's a good practice to use a string in this manner, I'm also not one to comment on someone else's business requirements that I am not privy to.
So let's assume we have to update the ID field to match the expected output. We can do that like this:
;WITH IDUpdate AS
(
SELECT
ID = RIGHT('000' + CAST(ROW_NUMBER() OVER (ORDER BY BalDate ASC, IDNbr DESC) AS VARCHAR), 3)
,t.IDNbr
,t.Balance
,t.BalDate
FROM #tblID t
)
UPDATE t
SET t.ID = i.ID
FROM #tblID t
INNER JOIN IDUpdate i ON i.IDNbr = t.IDNbr AND i.BalDate = t.BalDate
Now if you query your updated table, you'll get the following:
SELECT
ID
,IDNbr
,Balance
,BalDate
FROM #tblID
ORDER BY BalDate ASC, IDNbr DESC
Output:
ID | IDNbr | Balance | BalDate
------------------------------
001 | 91 | 529 | 2018-01-05
002 | 87 | 654 | 2018-01-05
003 | 45 | 258 | 2018-01-05
004 | 91 | 529 | 2018-01-06
005 | 87 | 654 | 2018-01-06
006 | 45 | 258 | 2018-01-06
007 | 91 | 529 | 2018-01-07
008 | 87 | 654 | 2018-01-07
009 | 45 | 258 | 2018-01-07
010 | 91 | 611 | 2018-01-08
011 | 87 | 753 | 2018-01-08
012 | 45 | 357 | 2018-01-08
Here is a samples for the linked function:
create FUNCTION [dbo].[CalTable]
(
#startDate date,
#endDate date
)
RETURNS
#calender TABLE
(
[Date] date not null primary key CLUSTERED,
isMondayToFriday bit not null
)
AS
BEGIN
declare #currentday date = #startDate;
declare #isMondayToFriday bit;
while (#currentday<=#endDate)
begin
-- respect DATEFIRST depending on language settings
if (DATEPART(dw, #currentday)+##DATEFIRST-2)%7+1>5
set #isMondayToFriday = 0
else
set #isMondayToFriday = 1
insert into #calender values (#currentday, #isMondayToFriday);
set #currentday = DATEADD(D, 1, #currentday);
end
RETURN
END
GO
select * from [CalTable]({d'2018-01-01'}, {d'2018-02-03'});
use this for find the gaps.

T-SQL return individual values instead of cumulative value

I have a 1 table in a db that stored Incoming, Outgoing and Net values for various Account Codes over time. Although there is a date field the sequence of events per Account Code is based on the "Version" number where 0 = original record for each Account Code and it increments by 1 after each change to that Account Code.
The Outgoing and Incoming values are stored in the db as cumulative values rather than the individual transaction value but I am looking for a way to Select * From this table and return the individual amounts as opposed to the cumulative.
Below are test scripts of table and data, and also 2 examples.
If i Select where code = '123' in the test table I currently get this (values are cumulative);
+------+------------+---------+---------+---------+-----+
| Code | Date | Version | Incoming| Outgoing| Net |
+------+------------+---------+---------+---------+-----+
| 123 | 01/01/2018 | 0 | 100 | 0 | 100 |
| 123 | 07/01/2018 | 1 | 150 | 0 | 150 |
| 123 | 09/01/2018 | 2 | 150 | 100 | 50 |
| 123 | 14/01/2018 | 3 | 200 | 100 | 100 |
| 123 | 18/01/2018 | 4 | 200 | 175 | 25 |
| 123 | 23/01/2018 | 5 | 225 | 175 | 50 |
| 123 | 30/01/2018 | 6 | 225 | 225 | 0 |
+------+------------+---------+---------+---------+-----+
This is what I would like to see (each individual transaction);
+------+------------+---------+----------+----------+------+
| Code | Date | Version | Incoming | Outgoing | Net |
+------+------------+---------+----------+----------+------+
| 123 | 01/01/2018 | 0 | 100 | 0 | 100 |
| 123 | 07/01/2018 | 1 | 50 | 0 | 50 |
| 123 | 09/01/2018 | 2 | 0 | 100 | -100 |
| 123 | 14/01/2018 | 3 | 50 | 0 | 50 |
| 123 | 18/01/2018 | 4 | 0 | 75 | -75 |
| 123 | 23/01/2018 | 5 | 25 | 0 | 25 |
| 123 | 30/01/2018 | 6 | 0 | 50 | -50 |
+------+------------+---------+----------+----------+------+
If I had the individual transaction values and wanted to report on the cumulative, I would use an OVER PARTITION BY, but is there an opposite to that?
I am not looking to redesign the create table or the process in which it is stored, I am just looking for a way to report on this from our MI environment.
Note: I've added other random Account Codes into this to emphasis how the data is not ordered by Code or Version, but by Date.
thanks in advance for any help.
USE [tempdb];
IF EXISTS ( SELECT *
FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_NAME = 'Table1'
AND TABLE_SCHEMA = 'dbo')
DROP TABLE [dbo].[Table1];
GO
CREATE TABLE [dbo].[Table1]
(
[Code] CHAR(3)
,[Date] DATE
,[Version] CHAR(3)
,[Incoming] DECIMAL(20,2)
,[Outgoing] DECIMAL(20,2)
,[Net] DECIMAL(20,2)
);
GO
INSERT INTO [dbo].[Table1] VALUES
('123','2018-01-01','0','100','0','100'),
('456','2018-01-02','0','50','0','50'),
('789','2018-01-03','0','0','0','0'),
('456','2018-01-04','1','100','0','100'),
('456','2018-01-05','2','150','0','150'),
('789','2018-01-06','1','50','50','0'),
('123','2018-01-07','1','150','0','150'),
('456','2018-01-08','3','200','0','200'),
('123','2018-01-09','2','150','100','50'),
('789','2018-01-10','2','0','0','0'),
('456','2018-01-11','4','225','0','225'),
('789','2018-01-12','3','75','25','50'),
('987','2018-01-13','0','0','50','-50'),
('123','2018-01-14','3','200','100','100'),
('654','2018-01-15','0','100','0','100'),
('456','2018-01-16','5','250','0','250'),
('987','2018-01-17','1','50','50','0'),
('123','2018-01-18','4','200','175','25'),
('789','2018-01-19','4','100','25','75'),
('987','2018-01-20','2','150','125','25'),
('321','2018-01-21','0','100','0','100'),
('654','2018-01-22','1','0','0','0'),
('123','2018-01-23','5','225','175','50'),
('321','2018-01-24','1','100','50','50'),
('789','2018-01-25','5','100','50','50'),
('987','2018-01-26','3','150','150','0'),
('456','2018-01-27','6','250','250','0'),
('456','2018-01-28','7','270','250','20'),
('321','2018-01-29','2','100','100','0'),
('123','2018-01-30','6','225','225','0'),
('987','2018-01-31','4','175','150','25')
;
GO
SELECT *
FROM [dbo].[Table1]
WHERE [Code] = '123'
GO;
USE [tempdb];
IF EXISTS ( SELECT *
FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_NAME = 'Table1'
AND TABLE_SCHEMA = 'dbo')
DROP TABLE [dbo].[Table1];
GO;
}
Just use lag():
select Evt, Date, Version,
(Loss - lag(Loss, 1, 0) over (partition by evt order by date)) as incoming,
(Rec - lag(Rec, 1, 0) over (partition by evt order by date)) as outgoing,
(Net - lag(Net, 1, 0) over (partition by evt order by date)) as net
from [dbo].[Table1];

Sql - Row as column

I have data in below format, around 8 to 9 departments, for each department few questions.
| Department | NoOfCases | Question | Rate |
+============+===========+==========+======+
| VC | 4 | A | 80 |
| VC | 2 | B | 90 |
| VC | 1 | C | 95 |
| ED | 5 | A | 85 |
| ED | 1 | B | 90 |
| ED | 3 | C | 95 |
| PH | 3 | A | 80 |
I want into below format, I want total no of cases per department and every question as column and rate as its value.
| Department | NoOfCases | A | B | C(actual questions as columns) |
+============+===========+====+====+================================+
| VC | 7 | 80 | 90 | 95 |
| ED | 9 | 85 | 90 | 95 |
| PH | 3 | 80 | | |
Can we achieve this?
You can achieve it using a PIVOT with a GROUP BY:
--create table variable to hold sample data
declare #tmp table( Department nvarchar(2),NoOfCases int, Question nvarchar(1), Rate int)
--populate sample data
insert into #tmp select 'VC', 4,'A', 80
insert into #tmp select 'VC', 2,'B', 90
insert into #tmp select 'VC', 1,'C', 95
insert into #tmp select 'ED', 5,'A', 85
insert into #tmp select 'ED', 1,'B', 90
insert into #tmp select 'ED', 3,'C', 95
insert into #tmp select 'PH', 3,'A', 80
select * from #tmp
--pivot with group by
select Department,SUM(piv.NoOfCases) AS NoOfCases,
ISNULL(SUM(A),0) AS A, ISNULL(SUM(B),0) AS B, ISNULL(SUM(C),0) AS C
from
(
--select data
select Department,NoOfCases , Question ,RATE
from #tmp
) src
pivot
(
MAX(RATE)
for Question in ([A], [B], [C])
) piv
GROUP BY Department
This is the output of the command: