Sql - Row as column - sql

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:

Related

SQL recursive query same table

I have a situation where I have to extract data from a non well designed database.
I have two tables
tableA
+----+----------+-----+---------+
| ID | NAME | AGE | UNIT_ID |
+----+----------+-----+---------+
| 1 | Brown | 25 | 50 |
| 2 | White | 27 | 100 |
| 3 | Gilmour | 24 | 150 |
+----+----------+-----+---------+
tableB
+-----+----------+--------+--------+
| ID | DESC | ID_LV1 | ID_LV2 |
+-----+----------+--------+--------+
| 20 | Unit_20 | 20 | 40 |
| 40 | Unit_40 | 40 | 50 |
| 50 | Unit_50 | 100 | 40 |
| 100 | Unit_100 | 100 | 50 |
| 150 | Unit_150 | 50 | 20 |
+-----+----------+--------+--------+
ID_LV1 and ID_LV2 are linked to ID of the same table (tableB)
The goal is to run a query and get these results:
+----+----------+-----+-----------+-----------+-----------+
| ID | NAME | AGE | UNIT_DESC | LV1_DESC | LV2_DESC |
+----+----------+-----+-----------+-----------+-----------+
| 1 | Brown | 25 | Unit_50 | Unit_100 | Unit_40 |
| 2 | White | 27 | Unit_100 | Unit_100 | Unit_50 |
| 3 | Gilmour | 24 | Unit_150 | Unit_50 | Unit_20 |
+----+----------+-----+-----------+-----------+-----------+
My SQL is pretty rusty. The SQL server that I'm working with doesn't allow me to create views.
My last chance is to import in excel and run a vlookup :-)!
You can achieve your desired result simply using inner join
create table tableA( ID int, NAME varchar(100), AGE int, UNIT_ID int);
insert into tableA values( 1 , 'Brown' , 25 , 50 );
insert into tableA values( 2 , 'White' , 27 , 100 );
insert into tableA values( 3 , 'Gilmour' , 24 , 150 );
create table tableB( ID INT, DESCRIPTION VARCHAR(100), ID_LV1 INT, ID_LV2 INT);
INSERT INTO tableB values( 20 , 'Unit_20' , 20 , 40 );
INSERT INTO tableB values( 40 , 'Unit_40' , 40 , 50 );
INSERT INTO tableB values( 50 , 'Unit_50' ,100 , 40 );
INSERT INTO tableB values( 100 , 'Unit_100' , 100 , 50 );
INSERT INTO tableB values( 150 , 'Unit_150' , 50 , 20 );
Query:
select a.ID, a.Name, a.Age,b.Description, b_lv1.Description, b_lv2.Description from tableA a
inner join tableB b on a.UNIT_ID=b.ID
inner join tableB b_lv1 on b.ID_LV1=b_lv1.ID
inner join tableB b_lv2 on b.ID_LV2=b_lv2.ID
order by a.ID
Output:
ID
Name
Age
Description
Description
Description
1
Brown
25
Unit_50
Unit_100
Unit_40
2
White
27
Unit_100
Unit_100
Unit_50
3
Gilmour
24
Unit_150
Unit_50
Unit_20
db<>fiddle here

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 |

How to apply TOP statement to only 1 column while selecting multiple columns from a table?

I am trying to select multiple columns from a table, but I want to select top certain number of records based on one column. I tried this :
select roll_no ,marks as Percentage
from database
where marks in (select top (3) *
from database
where subject = ''
order by marks desc) order by percentage desc
and I am getting the error:
Only one expression can be specified in the select list when the
sub-query is not introduced with EXISTS or more than specified number
of records.
I also tried :
select roll_no ,marks as Percentage
from database
where marks in (select top (3) marks
from database
where subject = ''
order by marks desc) order by percentage desc
which returns the right result for some subjects but for others..it is displaying top marks from other subjects as well.
eg :
+---------+-------+
| roll_no | marks |
+---------+-------+
|10003 | 87 |
|10006 | 72 |
|10003 | 72 |
|10002 | 67 |
|10004 | 67 |
+---------+-------+
How to frame the query correctly?
sample data :
+---------+-------+---------+
| roll_no | marks |subject |
+---------+-------+---------+
|10001 | 45 | Maths |
|10001 | 72 | Science |
|10001 | 64 | English |
|10002 | 52 | Maths |
|10002 | 35 | Science |
|10002 | 75 | English |
|10003 | 52 | Maths |
|10003 | 35 | Science |
|10003 | 75 | English |
|10004 | 52 | Maths |
|10004 | 35 | Science |
|10004 | 75 | English |
+---------+-------+---------+
If I'm right and you are looking for the best 3 marks for each subject, then you can get it with the following:
DECLARE #SelectedSubject VARCHAR(50) = 'Maths'
;WITH FilteredSubjectMarks AS
(
SELECT
D.Subject,
D.Roll_no,
D.Marks,
MarksRanking = DENSE_RANK() OVER (ORDER BY D.Marks DESC)
FROM
[Database] AS D
WHERE
D.Subject = #SelectedSubject
)
SELECT
F.*
FROM
FilteredSubjectMarks AS F
WHERE
F.MarksRanking <= 3
You can use window functions to rank your marks column (specifically dense_rank, which allows duplicate rankings whilst retaining sequential numbering) and then return all rows with a rank of 3 or less:
declare #t table(roll_no int identity(1,1),marks int);
insert into #t(marks) values(2),(4),(5),(8),(6),(1),(3),(2),(1),(8);
with t as
(
select roll_no
,marks
,dense_rank() over (order by marks desc) as r
from #t
)
select *
from t
where r <= 3;
Output:
+---------+-------+---+
| roll_no | marks | r |
+---------+-------+---+
| 4 | 8 | 1 |
| 10 | 6 | 1 |
| 5 | 6 | 2 |
| 3 | 5 | 3 |
+---------+-------+---+

Pivoting a table in 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

Trying to add total for individual for every month/year

" We have two tables. One for hours which have been sent to a vendor (Master) and one for the hours we are about to send (Load). I am attempting to sum the hours based on month and year for each individual with subtotals. We want to find those individuals who have more than 300 hours for a particular month.
CREATE TABLE MasterTabletesting(
ID CHAR(9) NOT NULL
,Workdate DATE NOT NULL
,Emp CHAR(30) NOT NULL
,HoursWorked DECIMAL(18,2) NOT NULL);
INSERT INTO MasterTabletesting(ID,Workdate,Emp,HoursWorked) VALUES ('25','20160731','7502',24);
INSERT INTO MasterTabletesting(ID,Workdate,Emp,HoursWorked) VALUES ('25','20160731','21874',128);
INSERT INTO MasterTabletesting(ID,Workdate,Emp,HoursWorked) VALUES ('25','20160731','7502',166);
INSERT INTO MasterTabletesting(ID,Workdate,Emp,HoursWorked) VALUES ('72','20160831','7508',180);
INSERT INTO MasterTabletesting(ID,Workdate,Emp,HoursWorked) VALUES ('72','20160831','5501',180);
INSERT INTO MasterTabletesting(ID,Workdate,Emp,HoursWorked) VALUES ('72','20160831','21037',23);
INSERT INTO MasterTabletesting(ID,Workdate,Emp,HoursWorked) VALUES ('66','20160831','83641',22);
CREATE TABLE LoadTabletesting(
ID CHAR(9) NOT NULL
,Workdate CHAR(8) NOT NULL
,Emp CHAR(30) NOT NULL
,HoursWorked DECIMAL(18,2) NOT NULL);
INSERT INTO LoadTabletesting(ID,Workdate,Emp,HoursWorked) VALUES ('8','07312016','7500',24);
INSERT INTO LoadTabletesting(ID,Workdate,Emp,HoursWorked) VALUES ('6','07312016','21974',128);
INSERT INTO LoadTabletesting(ID,Workdate,Emp,HoursWorked) VALUES ('25','07312016','7500',166);
INSERT INTO LoadTabletesting(ID,Workdate,Emp,HoursWorked) VALUES ('72','08312016','7500',180);
INSERT INTO LoadTabletesting(ID,Workdate,Emp,HoursWorked) VALUES ('88','08312016','5507',180);
INSERT INTO LoadTabletesting(ID,Workdate,Emp,HoursWorked) VALUES ('23','08312016','21012',23);
INSERT INTO LoadTabletesting(ID,Workdate,Emp,HoursWorked) VALUES ('55','08312016','83667',22);}
The date in the load table is a char variable type which I have resolved with cast. My approach has been to place the necessary data from both tables in a view titled "Over300agtest:
SELECT ID AS 'ID'
,Employer AS 'Emp'
,month(WorkDate) AS 'Mnth'
,year(WorkDate) AS 'Yr'
,HoursworkedAS 'Hrs'
,’amt’ as ‘Table’
FROM Mastertabletesting
WHERE HoursWorked IS NOT NULL
GROUP BY ID, Employer, month(WorkDate), year(WorkDate)
UNION all
SELECT ID AS 'ID'
,Employer AS 'Emp'
,month(CAST((RIGHT(workdate, 4) + LEFT(workdate, 4)) AS DATE)) AS 'Mnth'
,YEAR(CAST((RIGHT(workdate, 4) + LEFT(workdate, 4)) AS DATE)) AS 'Yr'
,hoursworked AS 'Hrs'
,‘alt’ as ‘Table’
FROM Loadtabletesting
WHERE HoursWorked IS NOT NULL
GROUP BY ID, Employer, month(CAST((RIGHT(workdate, 4) +LEFT workdate,4)) AS DATE)), year(CAST((RIGHT(workdate, 4) + LEFT(workdate, 4)) AS DATE))
Then I am using a common table expression to find those which have over 300 hours in a particular month and then using a query to join to the CTE in order to add the employer they worked for. Any suggestions on how to add a line under the hours column for each month for the total would be greatly appreciated.
with monthsum as(
Select ID as 'ID'
, mnth as 'mnth'
, yr as 'yr'
,SUM(hrs)as 'TotalHrs'
From over300agtest
Group by ID, mnth, yr
having SUM(hrs) > 300)
Select ms.ID
,ms.mnth
,ms.yr
,emp
,hrs
,o3.[table]
,totalhrs
From monthsum ms left outer join over300agtest o3
on ms.ID = o3.ID and ms.mnth = o3.mnth and ms.yr =o3.yr
Order by ms.ID, ms.yr asc, ms.mnth asc
Here is the current output:
+----+------+------+-------+-----+-------+----------+
| ID | mnth | yr | emp | hrs | table | totalhrs |
+----+------+------+-------+-----+-------+----------+
| 25 | 7 | 2016 | 7502 | 24 | AMT | 484 |
| 25 | 7 | 2016 | 21874 | 128 | AMT | 484 |
| 25 | 7 | 2016 | 7502 | 166 | AMT | 484 |
| 25 | 7 | 2016 | 7500 | 166 | ALT | 484 |
| 72 | 8 | 2016 | 7508 | 180 | AMT | 563 |
| 72 | 8 | 2016 | 5501 | 180 | AMT | 563 |
| 72 | 8 | 2016 | 21037 | 23 | AMT | 563 |
| 72 | 8 | 2016 | 7500 | 180 | ALT | 563 |
+----+------+------+-------+-----+-------+----------+
Here is the output I am going for:
+-------+------+------+-------+-----+-------+
| ID | mnth | yr | emp | hrs | table |
+-------+------+------+-------+-----+-------+
| 25 | 7 | 2016 | 7502 | 24 | AMT |
| 25 | 7 | 2016 | 21874 | 128 | AMT |
| 25 | 7 | 2016 | 7502 | 166 | AMT |
| 25 | 7 | 2016 | 7500 | 166 | ALT |
| Total | | | | 484 | |
| 72 | 8 | 2016 | 7508 | 180 | AMT |
| 72 | 8 | 2016 | 5501 | 180 | AMT |
| 72 | 8 | 2016 | 21037 | 23 | AMT |
| 72 | 8 | 2016 | 7500 | 180 | ALT |
| Total | | | | 563 | |
+-------+------+------+-------+-----+-------+
I suggest this approach. You should be able to work out the specifics.
select convert(char(6), cast(workdate as date), 120) yearMonth
, sum(hrs) totalHours
from etc
group by convert(char(6), cast(workdate as date), 120)
having sum(hrs) > 300