SQL Dynamic Pivot only using SELECT - sql

Where I work, we can only use "Read only" capabilities for SQL - I'm stuck using "SELECT" statements for everything. I find workarounds to create higher level outputs but this one has me stumped and I honestly don't know if it's possible. I don't know what version of SQL this is, I'm sorry.
Is there any way to "PIVOT" data dynamically while only using SELECT, and without using any Declaration statements?
Here's what I'm working with:
MyTable has 3 main fields I need to reference, CAT_ID, USER_ID and OPT. (Category, User, and option)
SELECT
CAT_ID,
USER_ID,
"1" = SUM( IIF(OPT = '1' , 1,0)),
"2" = SUM( IIF(OPT = '2' , 1,0)),
"3" = SUM( IIF(OPT = '3' , 1,0)),
A = SUM( IIF(OPT = 'A' , 1,0)),
B = SUM( IIF(OPT = 'N' , 1,0)),
C = SUM( IIF(OPT = 'U' , 1,0))
FROM myTable
WHERE CAT_ID = 'CAT A'
GROUP BY CAT_ID, USER_ID
Output produces a matrix that I can use to quickly ID what capabilities a user has in a specific category.
The goal is to use this output to help audit our users to make sure my workers have set people up correctly for the job they use.
CAT_ID USER_ID 0 1 2 3 A B C
CAT A USER1 1 1 1 1 1 1 1
CAT A USER2 1 1 1 1 1 1 1
CAT A USER3 1 1 1 1 0 0 0
CAT A USER4 1 1 1 1 1 1 1
CAT A USER5 1 1 1 1 1 1 1
I thought about maybe creating a temp table list of the options to use as a subquery but I'm not sure if that's doable with pivot. What I tried certainly didn't work.
SELECT * INTO #OPTIONS FROM (
SELECT DISTINCT OPT FROM myTable)
AS OPTIONS
SELECT * FROM myTable MT
PIVOT (
COUNT(CAT_ID)
FOR MT.OPT
IN (SELECT OPT FROM #OPTIONS)
) AS PTTBL
But as I said, this doesn't work. What else can I try, or did I simply make a mistake?

Related

Create a query that counts instances in a related table

I have re-written this query about 20 times today and I keep getting close but no dice... I'm sure this is easy-peasy for y'all, but my SQL (Oracle) is pretty rusty.
Here's what I need:
PersonID Count1 Count2 Count3 Count4
1 0 0 2 1
2 1 1 1 0
3 1 1 1 2
Data is coming from several sources. I have a table People, and a table Values. People can have any number of values in that table.
PersonID Item Value
1 Check1 3
1 Check2 3
1 Check3 4
2 Check4 2
2 Check5 3
2 Check6 1
.. etc
So the query would, for each PersonID, count how many times the particular Value appears. The values are always 1, 2, 3, or 4. I tried to do 4 subqueries, but it wouldn't read the PersonID from the main query and just returned the count of all instances of value=1.
I was then thinking do a Group_By ... I don't know. Any help is appreciated!
ETA: I've deleted & re-written the query many times in many ways and unfortunately did not save any intermediate attempts. I didn't include it originally because I was in the middle of rearranging it again, and it's not runnable as-is. But here it is as it stands now:
/*sources are the tested requirements
values are the scores people received on the tested sources
people are those who were tested on the requirements */
WITH sub_query4 (
SELECT values.personid,
count (values.ID) as count4 --how many 4s
FROM values
INNER JOIN sources ON values.valueid = sources.sourceid
INNER JOIN people ON people.personid = values.personid
WHERE values.yearid = 2017
AND values.quarter = 'Q1'
AND instr (sources.identifier, 'TESTBANK.01', 1 ,1) > 0
AND values.value = '4'
GROUP_BY people.personid
)
SELECT p.first_name,
p.last_name,
p.position,
p.email,
p.locationid,
sub_query4.count4 as count4 --eventually this would repeat for 1, 2, & 3
FROM people p
WHERE p.locationid=406
AND p.position in (9,10);
values is a bad name for a table because it is a SQL keyword.
In any case, conditional aggregation should work:
select personid,
sum(case when value = 1 then 1 else 0 end) as cnt_1,
sum(case when value = 2 then 1 else 0 end) as cnt_2,
sum(case when value = 3 then 1 else 0 end) as cnt_3,
sum(case when value = 4 then 1 else 0 end) as cnt_4
from values
group by personid;
I prefer to use PIVOT for this. Here is Example SQL Fiddle
SELECT "PersonID", val1,val2,val3,val4 FROM
(
SELECT "PersonID", "Value" from VALS
)
PIVOT
(
count("Value")
FOR "Value" IN (1 as val1, 2 as val2, 3 as val3, 4 as val4)
);

MS SQL Join/Count Query

I have the following tables:
Sessions
Id (int)
UserId (int)
Start (DateTime)
Stop (DateTime)
Users
Id (int)
Username (nvarchar(200))
Logs
SessionId (int)
LogLevelId (int)
Timestamp (DateTime)
Message (varchar(max))
LogLevels
Id (int)
DisplayText (varchar(5))
What I would like is an output that shows an overview of the list of sessions with the following columns:
SessionId | Username | Start | Stop | [total number of logs from each log level]
I have a solution where in C# I:
Select all of the log levels and their associated display text
Get a list of all sessions using the following query:
-
SELECT [Sessions].[Id]
,[Username]
,[Start]
,[Stop]
,[Application]
FROM [Sessions]
JOIN [Users] ON [Users].[Id] = [UserId]
I loop through each of the results from step 1 to assemble a query to count for each possible log level. Then perform a query per result from step 2 putting a where clause at the end to filter based on specific session. Each of those queries looks something like the following:
-
SELECT
COUNT(CASE [Logs].[LogLevelId] WHEN 1 THEN 1 END) AS 'Debugs'
,COUNT(CASE [Logs].[LogLevelId] WHEN 2 THEN 1 END) as 'Infos'
,COUNT(CASE [Logs].[LogLevelId] WHEN 3 THEN 1 END) as 'Warnings'
,COUNT(CASE [Logs].[LogLevelId] WHEN 4 THEN 1 END) as 'Errors'
,COUNT(CASE [Logs].[LogLevelId] WHEN 5 THEN 1 END) as 'Fatals'
FROM [Logs]
WHERE [SessionId] = |C# SESSION ID HERE|
I know this isn't an optimal solution and I wonder how it would be possible for me to pull all of this information in a single query or in two queries rather than 2 queries + N where N is the total number of session rows.
Consider joining the former query with latter query all in an aggregate GROUP BY query.
SELECT l.SessionId
, u.Username
, s.Start
, s.Stop
, COUNT(CASE WHEN l.[LogLevelId] = 1
AND lvl.DisplayText = 'Debugs' THEN 1 END) AS 'Debugs'
, COUNT(CASE WHEN l.[LogLevelId] = 2
AND lvl.DisplayText = 'Infos' THEN 1 END) as 'Infos'
, COUNT(CASE WHEN l.[LogLevelId] = 3
AND lvl.DisplayText = 'Warnings' THEN 1 END) as 'Warnings'
, COUNT(CASE WHEN l.[LogLevelId] = 4
AND lvl.DisplayText = 'Errors' THEN 1 END) as 'Errors'
, COUNT(CASE WHEN l.[LogLevelId] = 5
AND lvl.DisplayText = 'Fatals' THEN 1 END) as 'Fatals'
FROM
[Sessions] s
JOIN [Users] u ON u.[Id] = s.[UserId]
JOIN [Logs] l ON l.[SessionId] = s.[Id]
JOIN [LogLevels] lvl ON lvl.[Id] = l.[LogLevelId]
GROUP BY l.[SessionId]
, u.Username
, s.Start
, s.Stop

How to filter rows based on group values in SQL?

I have a table with the following format
serialnumber,test,result
-------------------------
ABC 1 "TOO HIGH"
ABC 2 "PASS"
ABC 3 "TOO LOW"
DEF 1 "PASS"
DEF 2 "PASS"
DEF 3 "PASS"
I need to do two operations:
1) for each serial number that has all pass records, I need to roll it up into a single record
2) for every serial that contains a "TOO HIGH" or "TOO LOW" record, I need to exclude all "PASS" records for that serial number
How would I go about doing this in teradata 15, preferably in a single statement?
SELECT *
FROM tab
QUALIFY
-- #1, only PASS
( SUM(CASE WHEN result <> 'PASS' THEN 1 ELSE 0 end)
OVER (PARTITION BY serialnumber) = 0
AND ROW_NUMBER()
OVER (PARTITION BY serialnumber
ORDER BY test) = 1
)
OR
-- #2
( SUM(CASE WHEN result <> 'PASS' THEN 1 ELSE 0 end)
OVER (PARTITION BY serialnumber) > 0
AND result_ <> 'PASS'
)
Consider a union query combining both conditions, using an aggregate query for #1 and a inner join query with derived tables for #2. Hopefully, Teradata's dialect supports the syntax:
SELECT TableName.SerialNumber,
Min(TableName.Test) As Test,
Min(TableName.Result) As Result
FROM SerialNumber
GROUP BY SerialNumber
HAVING Sum(CASE WHEN TableName.Result='"PASS"' THEN 1 ELSE 0 END) = Count(*)
UNION
SELECT TableName.SerialNumber,
TableName.Test,
TableName.Result
FROM SerialNumber
INNER JOIN
(SELECT SerialNumber FROM SerialNumber
WHERE TableName.Result = '"TOO HIGH"') AS toohighSub
INNER JOIN
(SELECT SerialNumber FROM SerialNumber
WHERE TableName.Result = '"TOO LOW"') AS toolowSub
ON toolowSub.SerialNumber = toohighSub.SerialNumber
ON TableName.SerialNumber = toolowSub.SerialNumber
WHERE TableName.Result <> '"PASS"';

SQL Count with multiple conditions then join

Quick one,
I have a table, with the following structure
id lid taken
1 1 0
1 1 0
1 1 1
1 1 1
1 2 1
Pretty simply so far right?
I need to query the taken/available from the lid of 1, which should return
taken available
2 2
I know I can simply do two counts and join them, but is there a more proficient way of doing this rather than two separate queries?
I was looking at the following type of format, but I can not for the life of me get it executed in SQL...
SELECT
COUNT(case taken=1) AS taken,
COUNT(case taken=0) AS available FROM table
WHERE
lid=1
Thank you SO much.
You can do this:
SELECT taken, COUNT(*) AS count
FROM table
WHERE lid = 1
GROUP BY taken
This will return two rows:
taken count
0 2
1 2
Each count corresponds to how many times that particular taken value was seen.
Your query is correct just needs juggling a bit:
SELECT
SUM(case taken WHEN 1 THEN 1 ELSE 0 END) AS taken,
SUM(case taken WHEN 1 THEN 0 ELSE 1 END) AS available FROM table
WHERE
lid=1
Alternatively you could do:
SELECT
SUM(taken) AS taken,
COUNT(id) - SUM(taken) AS available
FROM table
WHERE
lid=1
SELECT
SUM(case WHEN taken=1 THEN 1 ELSE 0 END) AS taken,
SUM(case WHEN taken=0 THEN 1 ELSE 0 END) AS available
FROM table
WHERE lid=1
Weird application of CTE's:
WITH lid AS (
SELECT DISTINCT lid FROM taken
)
, tak AS (
SELECT lid,taken , COUNT(*) AS cnt
FROM taken t0
GROUP BY lid,taken
)
SELECT l.lid
, COALESCE(a0.cnt, 0) AS available
, COALESCE(a1.cnt, 0) AS taken
FROM lid l
LEFT JOIN tak a0 ON a0.lid=l.lid AND a0.taken = 0
LEFT JOIN tak a1 ON a1.lid=l.lid AND a1.taken = 1
WHERE l.lid=1
;

Get the distinct count of values from a table with multiple where clauses

My table structure is this
id last_mod_dt nr is_u is_rog is_ror is_unv
1 x uuid1 1 1 1 0
2 y uuid1 1 0 1 1
3 z uuid2 1 1 1 1
I want the count of rows with:
is_ror=1 or is_rog =1
is_u=1
is_unv=1
All in a single query. Is it possible?
The problem I am facing is that there can be same values for nr as is the case in the table above.
Case statments provide mondo flexibility...
SELECT
sum(case
when is_ror = 1 or is_rog = 1 then 1
else 0
end) FirstCount
,sum(case
when is_u = 1 then 1
else 0
end) SecondCount
,sum(case
when is_unv = 1 then 1
else 0
end) ThirdCount
from MyTable
you can use union to get multiple results e.g.
select count(*) from table with is_ror=1 or is_rog =1
union
select count(*) from table with is_u=1
union
select count(*) from table with is_unv=1
Then the result set will contain three rows each with one of the counts.
Sounds pretty simple if "all in a single query" does not disqualify subselects;
SELECT
(SELECT COUNT(DISTINCT nr) FROM table1 WHERE is_ror=1 OR is_rog=1) cnt_ror_reg,
(SELECT COUNT(DISTINCT nr) FROM table1 WHERE is_u=1) cnt_u,
(SELECT COUNT(DISTINCT nr) FROM table1 WHERE is_unv=1) cnt_unv;
how about something like
SELECT
SUM(IF(is_u > 0 AND is_rog > 0, 1, 0)) AS count_something,
...
from table
group by nr
I think it will do the trick
I am of course not sure what you want exactly, but I believe you can use the logic to produce your desired result.