SQL: PIVOTting Count & Percentage against a column - tsql

I'm trying to produce a report that shows, for each Part No, the results of tests on those parts in terms of the numbers passed and failed, and the percentages passed and failed.
So far, I have the following:
SELECT r2.PartNo, [Pass] AS Passed, [Fail] as Failed
FROM
(SELECT ResultID, PartNo, Result FROM Results) r1
PIVOT (Count(ResultID) FOR Result IN ([Pass], [Fail])) AS r2
ORDER By r2.PartNo
This is half of the solution (the totals for passes and fails); the question is, how do I push on and include percentages?
I haven't tried yet, but I imagine that I can start again from scratch, and build up a series of subqueries, but this is more a learning exercise - I want to know the 'best' (most elegant or most efficient) solution, so I thought I'd seek advice.
Can I extend this PIVOT query, or should I take a different approach?
DDL:
CREATE TABLE RESULTS (
[ResultID] [int] NOT NULL,
[SerialNo] [int] NOT NULL,
[PartNo] [varchar](10) NOT NULL,
[Result] [varchar](10) NOT NULL);
DML:
INSERT INTO Results VALUES (1, '100', 'ABC', 'Pass')
INSERT INTO Results VALUES (2, '101', 'DEF', 'Pass')
INSERT INTO Results VALUES (3, '100', 'ABC', 'Fail')
INSERT INTO Results VALUES (4, '102', 'DEF', 'Pass')
INSERT INTO Results VALUES (5, '102', 'DEF', 'Pass')
INSERT INTO Results VALUES (6, '102', 'DEF', 'Fail')
INSERT INTO Results VALUES (7, '101', 'DEF', 'Fail')
UPDATE:
My solution, based on bluefeet's answer is:
SELECT r2.PartNo,
[Pass] AS Passed,
[Fail] as Failed,
ROUND(([Fail] / CAST(([Pass] + [Fail]) AS REAL)) * 100, 2) AS PercentFailed
FROM
(SELECT ResultID, PartNo, Result FROM Results) r1
PIVOT (Count(ResultID) FOR Result IN ([Pass], [Fail])) AS r2
ORDER By r2.PartNo
I've ROUNDed a FLOAT(rather than CAST to DECIMAL twice) because its a tiny bit more efficient, and I've also decided that we only real need the failure %age.

It sounds like you just need to add a column for Percent Passed and Percent Failed. You can calculate those columns on your PIVOT.
SELECT r2.PartNo
, [Pass] AS Passed
, [Fail] as Failed
, ([Pass] / Cast(([Pass] + [Fail]) as decimal(5, 2))) * 100 as PercentPassed
, ([Fail] / Cast(([Pass] + [Fail]) as decimal(5, 2))) * 100 as PercentFailed
FROM
(
SELECT ResultID, PartNo, Result
FROM Results
) r1
PIVOT
(
Count(ResultID)
FOR Result IN ([Pass], [Fail])
) AS r2
ORDER By r2.PartNo

Related

TSQL - Select values with same IS

have a view like this:
Table
The record "NDocumento" is populated only in the first row of a transaction by design. These rows are grouped by the column "NMov" which is the ID.
Since this is a view, I would like to populate each empty "NDocumento" record with the corresponding value contained in the first transaction through a SELECT statement.
As you can see by the picture this is MS-SQL Server 2008, so the lack of LAG makes the game harder.
I would immensely appreciate any help,
thanks
Try this:
SELECT
T1.NDocumento
, T2.NMov
, T2.NRiga
-- , T2. Rest of the fields
FROM NDocumentoTable T1
JOIN NDocumentoTable T2 ON T2.NMov = T1.NMov
WHERE T1.NRiga = 1
I used LAG() over the partition of NMov,Causale by based on your data. You cna change the partition with your requirement. The logic is you get the previous value if the NDocument is empty for the given partition.
CREATE TABLE myTable_1
(
NMov int
,NRiga int
,CodiceAngrafica varchar(100)
,Causale varchar(100)
,DateRegistration date
,DateDocumented date
,NDocument varchar(100)
)
INSERT INTO myTable_1 VALUES (5133, 1, '', 'V05', '01/14/2021', '01/14/2021', 'VI-2100001')
,(5133, 2, '', 'V05', null, null, '')
,(5134, 1, '', 'V05', '01/14/2021', '01/14/2021', 'VI-2100002')
,(5134, 2, '', 'V05', null, null, '')
SELECT
NMov
,NRiga
,CASE WHEN ISNULL(NDocument,'') = ''
THEN LAG(NDocument) OVER (PARTITION BY NMov,Causale ORDER BY NMov)
ELSE NDocument END AS [NDocument]
FROM myTable_1

Postgresql data calculation

Im trying to do some calculations using postgres, but no sucess so far. My query goes something like this:
select ....,
(select json_agg(data_table)
from (..... HERE GOES DE RESULT OF THE CALCULATION + a lot of business and data.... ) as data_table)
from foo
So i gonna exemplify with a table:
create temp table tbdata (id smallint, parent_id smallint, value numeric(25,2));
insert into tbdata values(1, null, 100), (2, 1, 50), (3, 1, 49), (4, 3, 20), (5, 3, 29);
select * from tbdata;
I need to calculate the difference between the sum of the siblings and the parent value. Example:
ID 2(50) + ID 3(49) = 99
ID 1(parent) = 100
so i need to add 1 to any of the childs (lets say 3), the result gonna be:
ID 2(50) + ID 3(49 + 1) = 100
ID 1(parent) = 100
After that, my ID3 have changed, so i need to update any of his childs:
ID 4(20) + ID 5(29) = 49
ID 3(parent) = 50
then again, updating value of ID 5 with the difference (50 - 49)
ID 4(20) + ID 5(29 + 1) = 50
ID 3(parent) = 50
I tried using recursive queries, windows function, and cte, but i always stuck in something. I was able to do using a function with a loop, but i dont want to do that.
Theres any way i can do it with a single SQL?

postgresql: "...where X IN <array type column values>" syntax?

I'm having problem with using the values of an array column in a where clause. Complete example to reproduce:
create type public.genre_type as enum ('scifi', 'fantasy', 'crime', 'horror', 'classics');
create table public.reader_profile(
id integer,
fave_genres genre_type ARRAY
);
create table public.books(
id serial not null,
title text,
genre_type public.genre_type
);
insert into public.reader_profile(id, fave_genres) values (1, array['crime', 'horror']::public.genre_type[]);
insert into public.reader_profile(id, fave_genres) values (2, array['fantasy', 'scifi']::public.genre_type[]);
insert into public.reader_profile(id, fave_genres) values (3, array['scifi', 'classics']::public.genre_type[]);
insert into public.books(title, genre_type) values ('gone with the wind', 'classics');
insert into public.books(title, genre_type) values ('Foundation', 'scifi');
insert into public.books(title, genre_type) values ('Dune', 'scifi');
-- THE FOLLOWING FAILS!!!
select * from public.books
where genre_type in (
select fave_genres from public.reader_profile where id = 2
);
I've tried ...where genre_type = ANY() per other stackoverflow answers as well as ...where genre_type <# () and I can't get anything to work! It seems the inner query (which works) is being return as an array type and not a list of values or something. Any help appreciated!!
I agree with #Hogan that this seems doable with a JOIN but the syntax you are looking for is the following:
SELECT *
FROM books
WHERE genre_type = ANY(ARRAY(SELECT fave_genres FROM reader_profile WHERE id = 2))
;
Demo
Can I suggest using a join instead?
select *
from public.books b
join public.reader_profile fg on b.genre_type = ANY(rp.fave_genres) and fg.id = 2

Interconnecting tables on PostgreSQL

I am a newbie here.
I am using PostgreSQL to manipulate lots of data in my specific field of research. Unfortunately, I am encountering a problem that is not allowing me to continue my analysis. I tried to simplify my problem to clearly illustrate it.
Let's suppose I have a table called "Buyers" with those data:
table_buyers
The buyers can make ONLY ONE purchase in each store or none. There are three stores and there a table for each one. Just like below:
table_store1
table_store2
table_store3
To create the tables, I am using the following code:
CREATE TABLE public.buyer
(
ID integer NOT NULL PRIMARY KEY,
name text NOT NULL,
phone text NOT NULL
)
WITH (
OIDS = FALSE
)
;
CREATE TABLE public.Store1
(
ID_buyer integer NOT NULL PRIMARY KEY,
total_order numeric NOT NULL,
total_itens integer NOT NULL
)
WITH (
OIDS = FALSE
)
;
CREATE TABLE public.Store2
(
ID_buyer integer NOT NULL PRIMARY KEY,
total_order numeric NOT NULL,
total_itens integer NOT NULL
)
WITH (
OIDS = FALSE
)
;
CREATE TABLE public.Store3
(
ID_buyer integer NOT NULL PRIMARY KEY,
total_order numeric NOT NULL,
total_itens integer NOT NULL
)
WITH (
OIDS = FALSE
)
;
To add the information on the tables, I am using the following code:
INSERT INTO buyer (ID, name, phone) VALUES
(1, 'Alex', 88888888),
(2, 'Igor', 77777777),
(3, 'Mike', 66666666);
INSERT INTO Store1 (ID_buyer, total_order, total_itens) VALUES
(1, 87.45, 8),
(2, 14.00, 3),
(3, 12.40, 4);
INSERT INTO Store2 (ID_buyer, total_order, total_itens) VALUES
(1, 785.12, 7),
(2, 9874.21, 25);
INSERT INTO Store3 (ID_buyer, total_order, total_itens) VALUES
(2, 45.87, 1);
As all the tables are interconnected by buyer's ID, I wish I could have a query that generates an output just like this:
desired output table.
Please, note that if the buyer did not buy anything in a store, I must print '0'.
I know this is an easy task, but unfortunately, I have been failing on accomplish it.
Using the 'AND' logical operator, I tried the following code to accomplish this task:
SELECT
buyer.id,
buyer.name,
store1.total_order,
store2.total_order,
store3.total_order
FROM
public.buyer,
public.store1,
public.store2,
public.store3
WHERE
buyer.id = store1.id_buyer AND
buyer.id = store2.id_buyer AND
buyer.id = store3.id_buyer;
But, obviously, it just returned 'Igor' as this was the only buyer that have bought items on all three stores (print screen).
Then, I tried the 'OR' logical operator, just like the following code:
SELECT
buyer.id,
buyer.name,
store1.total_order,
store2.total_order,
store3.total_order
FROM
public.buyer,
public.store1,
public.store2,
public.store3
WHERE
buyer.id = store1.id_buyer OR
buyer.id = store2.id_buyer OR
buyer.id = store3.id_buyer;
But then, it returns 12 lines with wrong values (print screen).
Clearly, my mistake is about not considering that 'Buyers' don't have to on all three stores on my code. I just can't correct it on my own, can you please help me?
I appreciate a lot for an answer that can light up my way. Thanks a lot!
Tips about how I can search for this issue are very welcome as well!
Ok. I doubt that this is the final answer for you, but its a start
SELECT
buyer.id,
buyer.name,
COALESCE( gb_store1.total_orders, 0 ) as store1_total,
COALESCE( gb_store2.total_orders, 0 ) as store2_total,
COALESCE( gb_store3.total_orders, 0 ) as store3_total
FROM
public.buyer,
LEFT OUTER JOIN ( SELECT ID_buyer,
SUM( total_orders ) as total_orders,
SUM( total_itens ) as total_itens
FROM public.store1
GROUP BY ID_buyer ) gb_store1 ON gb_store1.id_buyer = buyer.id ,
LEFT OUTER JOIN ( SELECT ID_buyer,
SUM( total_orders ) as total_orders,
SUM( total_itens ) as total_itens
FROM public.store2
GROUP BY ID_buyer ) gb_store2 ON gb_store2.id_buyer = buyer.id ,
LEFT OUTER JOIN ( SELECT ID_buyer,
SUM( total_orders ) as total_orders,
SUM( total_itens ) as total_itens
FROM public.store3
GROUP BY ID_buyer ) gb_store3 ON gb_store3.id_buyer = buyer.id ;
So, this query has a couple elements should focus on. The subselects/groupby allow you to total within your subtables by ID_buyer. The LEFT OUTER JOIN make its so your query can still return a result, even if a subselect finds no matching record. Finally, the COALESCE allows you to return 0 when one of your totals is NULL (because the subselect found no match).
Hope this helps.

How can I filter RETURNING *?

I have the following scenario. I have a table that has an IsDeleted flag I set for doing a 'soft delete' of records. I am doing an UPSERT where I am adding, modifying and flagging as deleted some records. I want to exclude records that have been flagged as deleted from the RETURNING statement. I have attempted to just append WHERE tbltest_IsDeleted = 0 to the end of the following SQL but it gives me the error: ERROR: syntax error at or near "WHERE"
How can I filter the results of the RETURNING * in the following statement?
INSERT INTO tbltest (
tbltest_ID,
tbltest_Name,
tbltest_Description,
tbltest_IsDeleted)
VALUES
(DEFAULT, 'new record','new record description', 0),
(4, 'modified record name','modified record description', 0),
(5, 'existing record name','existing record description', 1)
ON CONFLICT (tbltest_ID) DO UPDATE SET (
tbltest_Name,
tbltest_Description,
tbltest_IsDeleted) = (
excluded.tbltest_Name,
excluded.tbltest_Description,
excluded.tbltest_IsDeleted) RETURNING *;
Worked it out, here is how I was able to do it:
WITH rows AS (
INSERT INTO tbltest (
tbltest_ID,
tbltest_Name,
tbltest_Description,
tbltest_IsDeleted)
VALUES
(DEFAULT, 'new record','new record description', 0),
(4, 'modified record name','modified record description', 0),
(5, 'existing record name','existing record description', 1)
ON CONFLICT (tbltest_ID) DO UPDATE SET (
tbltest_Name,
tbltest_Description,
tbltest_IsDeleted) = (
excluded.tbltest_Name,
excluded.tbltest_Description,
excluded.tbltest_IsDeleted) RETURNING *
)
SELECT * FROM rows WHERE rows.tbltest_IsDeleted = 0
Hopefully this saves someone some time ;-)