SQL Server : query with subquery involving select from previous selection - sql

I'm trying to create a query in SQL Server to determine how many times a person's name shows up in a list, but also that list will be unknown, so I would have to get the actual name from the previous select index... It's hard to explain so I'll show the query first and hopefully someone can help.
SELECT
SpeakerName, Spoken,
(SELECT COUNT(SpeakerName)
FROM tbl_SpeakerCard_Log
WHERE SpeakerName = 'SpeakerName[i]' AND SpeakDate = '3-9-16') as TimesSpoken
FROM
tbl_SpeakerCard_Log
WHERE
AID = ####
ORDER BY
GeneralComment ASC
So basically, in SpeakerName[i], I'd like to somehow get the SpeakerName from the outer Select. The output should come out something like this
+-------------+--------+-------------+
| SpeakerName | Spoken | TimesSpoken |
+-------------+--------+-------------+
| Holly | 0 | 4 |
| Robert | 1 | 5 |
| Mike | 1 | 2 |
+-------------+--------+-------------+

Try this:
select x.SpeakerName, x.Spoken, COUNT(*) as TimesSpoken
from tbl_SpeakerCard_Log x
WHERE AID = ####
and x.SpeakDate = '3-9-16'
group by x.SpeakerName, x.Spoken
Don't have SSMS installed on this computer so can't test it.

Related

SQL structure for multiple queries of the same table (using window function, case, join)

I have a complex production SQL question. It's actually PrestoDB Hadoop, but conforms to common SQL.
I've got to get a bunch of metrics from a table, a little like this (sorry if the tables are mangled):
+--------+--------------+------------------+
| device | install_date | customer_account |
+--------+--------------+------------------+
| dev 1 | 1-Jun | 123 |
| dev 1 | 4-Jun | 456 |
| dev 1 | 10-Jun | 789 |
| dev 2 | 20-Jun | 50 |
| dev 2 | 25-Jun | 60 |
+--------+--------------+------------------+
I need something like this:
+--------+------------------+-------------------------+
| device | max_install_date | previous_account_number |
+--------+------------------+-------------------------+
| dev 1 | 10-Jun | 456 |
| dev 2 | 25-Jun | 50 |
+--------+------------------+-------------------------+
I can do two separate queries to get max install date and previous account number, like this:
select device, max(install_date) as max_install_date
from (select [a whole bunch of stuff], dense_rank() over(partition by device order by [something_else]) rnk
from some_table a
)
But how do you combine them into one query to get one line for each device? I have rank, with statements, case statements, and one join. They all work individually but I'm banging my head to understand how to combine them all.
I need to understand how to structure big queries.
ps. any good books you recommend on advanced SQL for data analysis? I see a bunch on Amazon but nothing that tells me how to construct big queries like this. I'm not a DBA. I'm a data guy.
Thanks.
You can use correlated subquery approach :
select t.*
from table t
where install_date = (select max(install_date) from table t1 where t1.device = t.device);
This assumes install_date has resonbale date format.
I think you want:
select t.*
from (select t.*, max(install_date) over (partition by device) as max_install_date,
lag(customer_account) over (partition by device order by install-date) as prev_customer_account
from t
) t
where install_date = max_install_date;

Find spectators that have seen the same shows (match multiple rows for each)

For an assignment I have to write several SQL queries for a database stored in a PostgreSQL server running PostgreSQL 9.3.0. However, I find myself blocked with last query. The database models a reservation system for an opera house. The query is about associating the a spectator the other spectators that assist to the same events every time.
The model looks like this:
Reservations table
id_res | create_date | tickets_presented | id_show | id_spectator | price | category
-------+---------------------+---------------------+---------+--------------+-------+----------
1 | 2015-08-05 17:45:03 | | 1 | 1 | 195 | 1
2 | 2014-03-15 14:51:08 | 2014-11-30 14:17:00 | 11 | 1 | 150 | 2
Spectators table
id_spectator | last_name | first_name | email | create_time | age
---------------+------------+------------+----------------------------------------+---------------------+-----
1 | gonzalez | colin | colin.gonzalez#gmail.com | 2014-03-15 14:21:30 | 22
2 | bequet | camille | bequet.camille#gmail.com | 2014-12-10 15:22:31 | 22
Shows table
id_show | name | kind | presentation_date | start_time | end_time | id_season | capacity_cat1 | capacity_cat2 | capacity_cat3 | price_cat1 | price_cat2 | price_cat3
---------+------------------------+--------+-------------------+------------+----------+-----------+---------------+---------------+---------------+------------+------------+------------
1 | madama butterfly | opera | 2015-09-05 | 19:30:00 | 21:30:00 | 2 | 315 | 630 | 945 | 195 | 150 | 100
2 | don giovanni | opera | 2015-09-12 | 19:30:00 | 21:45:00 | 2 | 315 | 630 | 945 | 195 | 150 | 100
So far I've started by writing a query to get the id of the spectator and the date of the show he's attending to, the query looks like this.
SELECT Reservations.id_spectator, Shows.presentation_date
FROM Reservations
LEFT JOIN Shows ON Reservations.id_show = Shows.id_show;
Could someone help me understand better the problem and hint me towards finding a solution. Thanks in advance.
So the result I'm expecting should be something like this
id_spectator | other_id_spectators
-------------+--------------------
1| 2,3
Meaning that every time spectator with id 1 went to a show, spectators 2 and 3 did too.
Note based on comments: Wanted to make clear that this answer may be of limited use as it was answered in the context of SQL-Server (tag was present at the time)
There is probably a better way to do it, but you could do it with the 'stuff 'function. The only drawback here is that, since your ids are ints, placing a comma between values will involve a work around (would need to be a string). Below is the method I can think of using a work around.
SELECT [id_spectator], [id_show]
, STUFF((SELECT ',' + CAST(A.[id_spectator] as NVARCHAR(10))
FROM reservations A
Where A.[id_show]=B.[id_show] AND a.[id_spectator] != b.[id_spectator] FOR XML PATH('')),1,1,'') As [other_id_spectators]
From reservations B
Group By [id_spectator], [id_show]
This will show you all other spectators that attended the same shows.
Meaning that every time spectator with id 1 went to a show, spectators 2 and 3 did too.
In other words, you want a list of ...
all spectators that have seen all the shows that a given spectator has seen (and possibly more than the given one)
This is a special case of relational division. We have assembled an arsenal of basic techniques here:
How to filter SQL results in a has-many-through relation
It is special because the list of shows each spectator has to have attended is dynamically determined by the given prime spectator.
Assuming that (d_spectator, id_show) is unique in reservations, which has not been clarified.
A UNIQUE constraint on those two columns (in that order) also provides the most important index.
For best performance in query 2 and 3 below also create an index with leading id_show.
1. Brute force
The primitive approach would be to form a sorted array of shows the given user has seen and compare the same array of others:
SELECT 1 AS id_spectator, array_agg(sub.id_spectator) AS id_other_spectators
FROM (
SELECT id_spectator
FROM reservations r
WHERE id_spectator <> 1
GROUP BY 1
HAVING array_agg(id_show ORDER BY id_show)
#> (SELECT array_agg(id_show ORDER BY id_show)
FROM reservations
WHERE id_spectator = 1)
) sub;
But this is potentially very expensive for big tables. The whole table hast to be processes, and in a rather expensive way, too.
2. Smarter
Use a CTE to determine relevant shows, then only consider those
WITH shows AS ( -- all shows of id 1; 1 row per show
SELECT id_spectator, id_show
FROM reservations
WHERE id_spectator = 1 -- your prime spectator here
)
SELECT sub.id_spectator, array_agg(sub.other) AS id_other_spectators
FROM (
SELECT s.id_spectator, r.id_spectator AS other
FROM shows s
JOIN reservations r USING (id_show)
WHERE r.id_spectator <> s.id_spectator
GROUP BY 1,2
HAVING count(*) = (SELECT count(*) FROM shows)
) sub
GROUP BY 1;
#> is the "contains2 operator for arrays - so we get all spectators that have at least seen the same shows.
Faster than 1. because only relevant shows are considered.
3. Real smart
To also exclude spectators that are not going to qualify early from the query, use a recursive CTE:
WITH RECURSIVE shows AS ( -- produces exactly 1 row
SELECT id_spectator, array_agg(id_show) AS shows, count(*) AS ct
FROM reservations
WHERE id_spectator = 1 -- your prime spectator here
GROUP BY 1
)
, cte AS (
SELECT r.id_spectator, 1 AS idx
FROM shows s
JOIN reservations r ON r.id_show = s.shows[1]
WHERE r.id_spectator <> s.id_spectator
UNION ALL
SELECT r.id_spectator, idx + 1
FROM cte c
JOIN reservations r USING (id_spectator)
JOIN shows s ON s.shows[c.idx + 1] = r.id_show
)
SELECT s.id_spectator, array_agg(c.id_spectator) AS id_other_spectators
FROM shows s
JOIN cte c ON c.idx = s.ct -- has an entry for every show
GROUP BY 1;
Note that the first CTE is non-recursive. Only the second part is recursive (iterative really).
This should be fastest for small selections from big tables. Row that don't qualify are excluded early. the two indices I mentioned are essential.
SQL Fiddle demonstrating all three.
It sounds like you have one half of the total question--determining which id_shows a particular id_spectator attended.
What you want to ask yourself is how you can determine which id_spectators attended an id_show, given an id_show. Once you have that, combine the two answers to get the full result.
So the final answer I got, looks like this :
SELECT id_spectator, id_show,(
SELECT string_agg(to_char(A.id_spectator, '999'), ',')
FROM Reservations A
WHERE A.id_show=B.id_show
) AS other_id_spectators
FROM Reservations B
GROUP By id_spectator, id_show
ORDER BY id_spectator ASC;
Which prints something like this:
id_spectator | id_show | other_id_spectators
-------------+---------+---------------------
1 | 1 | 1, 2, 9
1 | 14 | 1, 2
Which suits my needs, however if you have any improvements to offer, please share :) Thanks again everybody!

SQL Server 2008 pivot query gone wrong with column name

I have some problems regarding a pivot query. I am new to this. So look for something in the internet so I found dozens of them. So I decided to follow this Link. Been practice but seems like I ran into some obvious error.
My code is:
select
risk, [Quick] AS Quick, [Brown] AS Brown, [Fox] AS Fox
from
(select risk, site
from tst) as ps
PIVOT
(
count(risk)
for site in ([Brown], [Brown], [Fox])
) AS pvt
But it is throwing an error:
Invalid column name 'risk'.
Basically I want to have an output like this:
|Foo | Quick | Brown | Fox |
| 1 | 10 | 3 | 2 |
| 2 | 5 | 4 | 4 |
| 3 | 4 | 1 | 5 |
| 4 | 2 | 3 | 7 |
| 5 | 3 | 2 | 1 |
Something like that. Just counting how many there is in a specific number. Any help would be much appreciated. Thanks
The problem with your existing query is you are using the column risk in your final select list as well as inside of the aggregate function. Once you've counted the risk values for each site this is not available to display.
To get around this you can add a second version of the risk column to your subquery similar to the following. You then count this other column of risk and display one in the final select:
select risk, [ADAB] AS ADAB, [Bahrain] AS Bahrain, [Thumrait] AS Thumrait
from
(
select risk, piv_risk = risk, site
from qcqcif
) as ps
PIVOT
(
count(piv_risk)
for site in ([ADAB], [Bahrain], [Thumrait])
) AS pvt;
See SQL Fiddle with Demo

Access SQL Max-Function

I have a question concerning MS Access queries involving these tables:
tblMIDProcessMain ={ Process_ID,Process_Title,...}
tblMIDProcessVersion = { ProcessVersion_ID, ProcessVersion_FK_Process, ProcessVersion_VersionNo, ProcessVersion_FK_Status, ...}
tblMIDProcessVersionStatus = { ProcessVersionStatus_ID,ProcessVersionStatus_Value }
The tables store different versions of a process description. The "ProcessVersion_VersionNo" field contains an integer providing the version number. Now I would like to get for each process the highest version number thus the current version. If I do the following it kind of works:
SELECT tblMIDProcessMain.Process_Titel
, Max(tblMIDProcessVersion.ProcessVersion_VersionNo) AS CurrentVersion
FROM tblMIDProcessMain
INNER JOIN tblMIDProcessVersion
ON tblMIDProcessMain.Process_ID = tblMIDProcessVersion.ProcessVersion_FK_Process
GROUP BY tblMIDProcessMain.Process_Titel;
The query returns a recordset with each existing process_title and the respective max number of the version field. But as soon as I add other fields like "ProcessVersion_FK_Status" in the Select statement the query stops working.
Any help would be appreciated. Thanks in advance.
Jon
Edit:
To clarify things a little I added a simplified example
Parent-Table:
Process_ID | Process_Title
----------------------------------
1 | "MyProcess"
2 | "YourProcess"
Child-Table:
Version_ID | Version_FK_ProcessID | Version_No | Version_Status
---------------------------------------------------------------------------
1 | 1 | 1 | "New"
2 | 2 | 1 | "Discarded"
3 | 2 | 2 | "Reviewed"
4 | 2 | 3 | "Released"
Intended Result:
Title | Max_Version_No | Status
--------------------------------------------------------
MyProcess | 1 | "New"
YourProcess | 3 | "Released"
Given the example tables you updated your post with, this should work:
select process_title as Title
, max_version.max_version_no
, c.version_status as status
from (parenttable p
inner join (select max(version_id) as max_version_no, version_fk_process_id from childtable group by version_fk_process_id) max_version
on p.process_id = max_version.version_fk_process_id)
inner join childtable c
on max_version.max_version_no = c.version_id and max_version.version_fk_process_id = c.version_fk_process_id
I assume you are adding the new field to the 'Group By" clause? If not, then you either must include in the 'Group By', or you must use one of the operators like "Max" or "First" etc.

Fetch Id's that are related to a specific set of items, but not others

Good morning all, apologies for the title... i had trouble simplifying the problem down to a line. My database platform is Teradata.
I am working w/ a table like the following (let's call it "t1")
+------------+----------------------------------------+
| Service_Id | Product |
+------------+----------------------------------------+
| 1 | Traffic |
| 1 | Weather |
| 1 | Travel |
| 1 | Audio |
| 1 | Audio Add-on |
| 2 | Traffic |
| 2 | Weather |
| 2 | Travel |
+------------+----------------------------------------+
I am trying to select service_id's that are related to the following products AND ONLY the following products: Traffic, Weather, Travel
"Service_Id = 1" does not apply here because while it has the required products, it also has an "audio" product related to it... so we have to leave it out. I was able to successfully do this through a series of temp (volatile) tables but it's feeling really hacky and I feel there's got to be a better way. Thanks for your assistance.
I'm doing stuff like that (find a subset/superset/exact match for a set of rows) in my training classes using pizzas :-)
There are several ways to get your result, but for an exact match the easiest way is a SUM using following logic:
SELECT service_id
FROM t1
GROUP BY 1
HAVING
SUM(CASE WHEN Product IN ('Traffic', 'Weather', 'Travel') THEN 1 ELSE -1 END = 3
Assuming that Product is unique for every service_ID.
SELECT service_ID
FROM tableName a
WHERE Product IN ('Traffic', 'Weather', 'Travel') AND
EXISTS
(
SELECT 1
FROM tableName b
WHERE a.Service_ID = b.Service_ID
GROUP BY b.Service_ID
HAVING COUNT(*) = 3 -- <<== total number of products
)
GROUP BY service_ID
HAVING COUNT(*) = 3 -- <<== total number of products
SQLFiddle Demo (demo is running under MySQL database, not sure if it will work on teradata)