I have an MS Access Query which returns the following sample data:
+-----+------+------+
| Ref | ANS1 | ANS2 |
+-----+------+------+
| 123 | A | A |
| 234 | B | B |
| 345 | C | C |
| 456 | D | E |
| 567 | F | G |
| 678 | H | I |
+-----+------+------+
Is it possible to have Access return the overall percentage where ANS1 = ANS2?
So my new query would return:
50
I know how to get a count of the records returned by the original query, but not how to calculate the percentage.
Since you're looking for a percentage of some condition being met across the entire dataset, the task can be reduced to having a function return either 1 (when the condition is validated), or 0 (when the condition is not validated), and then calculating an average across all records.
This could be achieved in a number of ways, one example might be to use a basic iif statement:
select avg(iif(t.ans1=t.ans2,1,0)) from YourTable t
Or, using the knowledge that a boolean value in MS Access is represented using -1 (True) or 0 (False), the expression can be reduced to:
select -avg(t.ans1=t.ans2) from YourTable t
In each of the above, change YourTable to the name of your table.
If you know how to get a count, then apply that same knowledge twice:
SELECT Count([ANS1]) As MatchCount FROM [Data]
WHERE [ANS1] = [ANS2]
divided by the total count
SELECT Count([ANS1]) As AllCount FROM [Data]
To combine both of these in a basic SQL query, one needs a "dummy" query since Access doesn't allow selection of only raw data:
SELECT TOP 1
((SELECT Count([ANS1]) As MatchCount FROM [Data] WHERE [ANS1] = [ANS2])
/
(SELECT Count([ANS1]) As AllCount FROM [Data]))
AS MatchPercent
FROM [Data]
This of course assumes that there is at least one row... so it doesn't divide by zero.
Related
I have 2 tables. The first table contains following columns: Start_latitude, start_longitude, end_latitude, end_longitude, sum. The sum column is empty and needs to be filled based on second table.
The second table contains 3 columns: point_latitude, point_longitude
Table 1
-------------------------
|45 | 50 | 46 | 51 | null|
----|---------------------
|45 | 54 | 46 | 57 | null|
--------------------------
Table2:
---------------
| 45.5 | 55.2 |
---------------
| 45.8 | 50.6 |
---------------
| 45.2 | 56 |
---------------
The null values in table1-row1 would be 1 while in row2 it would be 2. It is the count of number of points that lie within the bounding box.
I can do it in python by writing functions to read values between dataframes. How can this be done in Postgresql. This is a sample problem statement that I came up with for my situation.
Update
This version was tested on PostgreSql 9.3 using SQL Fiddle
UPDATE table1 a
SET sum = sub.point_count
FROM (SELECT a.start_lat, a.end_lat, a.start_lon, a.end_lon, COUNT(*) as point_count
FROM table1 a, table2 b
WHERE b.point_lat BETWEEN start_lat AND a.end_lat
AND b.point_lon BETWEEN a.start_lon AND a.end_lon
GROUP BY a.start_lat, a.end_lat, a.start_lon, a.end_lon) as sub
WHERE a.start_lat = sub.start_lat
AND a.end_lat = sub.end_lat
AND a.start_lon = sub.start_lon
AND a.end_lon = sub.end_lon;
Original answer
Here is my solution, it is tested on MySQL but there is nothing specific about this code so it should work on PostgreSql as well
UPDATE table1 a,
(SELECT a.start_lat, a.end_lat, a.start_lon, a.end_lon, COUNT(*) as count
FROM table1 a, table2 b
WHERE b.point_lat BETWEEN start_lat AND a.end_lat
AND b.point_lon BETWEEN a.start_lon AND a.end_lon
GROUP BY a.start_lat, a.end_lat, a.start_lon, a.end_lon) as sub
SET sum = count
WHERE a.start_lat = sub.start_lat
AND a.end_lat = sub.end_lat
AND a.start_lon = sub.start_lon
AND a.end_lon = sub.end_lon
Note that this query would be much shorter if table1 contained a PK Id column.
Using postgreSQL (latest). I'm a total noob.
I have a view that always gives me a table of an even number of rows- no duplicates (the letters are analogous to unique keys) and no nulls, let's call it letter_view:
| letter |
|-------------|
| A |
| B |
| C |
| D |
| E |
| F |
| G |
| H |
My view already uses an ORDER BY clause so the table is pre-sorted.
What I'm trying to do is merge every two rows into a single row
with each value from those two rows. So for n rows, I need the result set to have
n / 2 rows with combined adjacent rows.
| l1 | l2 |
|-------|------|
| A | B |
| C | D |
| E | F |
| G | H |
I've tried using lead and I think I'm close but I can't quite get it in the format I need.
My best query attempt looks like this:
SELECT letter AS letter_1, lead(letter, 1) OVER (PARTITION BY 2) AS letter_2 from letter_view;
but I get:
letter_1 | letter_2
----------+----------
A | B
B | C <--- Don't need this
C | D
D | E <--- Don't need this
E | F
F | G <--- Don't need this
G | H
H | <--- Don't need this
(8 rows)
I checked several other answers on SO, and looked through
the PostgreSQL docs and w3C SQL tutorials but I can't find a succinct answer.
What is this technique called and how would I do it?
I'm trying to do this in pure SQL if possible.
I know I could use multiple queries with LIMIT and OFFSET to get the data with multiple selects or potentially by using a cursor but that seems very inefficient for large input sets although I could be totally wrong. Again, total noob.
Any help in the right direction is highly appreciated.
You can use lead() to get the next value . . . but you need a way to filter as well. I would suggest row_number():
select letter_1, letter_2
from (select letter AS letter_1,
lead(letter, 1) OVER (PARTITION BY 2 order by ??) AS letter_2,
row_number() over (partition by 2 order by ??) as seqnum
from letter_view
) lv
where seqnum % 2 = 1;
Notes:
I included the partition clause as you have in the original code. I don't know what "2" refers to.
You should be explicit about the order by. It is not wise to depend on the ordering of some underlying table or view.
I have a self-referencing/recursive calculation in Excel that needs to be moved to Hive SQL. Basically the column needs to SUM the two values only if the total of the concrete column plus the result from the previous calculation is greater than 0.
The data is as follows, A is the value and B is the expected output:
| A | B |
|-----|-----|
| -1 | 0 |
| 2 | 2 |
| -2 | 0 |
| 2 | 2 |
| 2 | 4 |
| -1 | 3 |
| 2 | 5 |
In Excel it would be written in column B as:
=MAX(0,B1+A2)
The problem in SQL is you need to have the output of the current calculation. I think I've got it sorted in SQL as the following:
DECLARE #Numbers TABLE(A INT, Rn INT)
INSERT INTO #Numbers VALUES (-1,1),(2,2),(-2,3),(2,4),(2,5),(-1,6),(2,7);
WITH lagged AS
(
SELECT A, 0 AS B, Rn
FROM #Numbers
WHERE Rn = 1
UNION ALL
SELECT i.A,
CASE WHEN ((i.A + l.B) >= 0) THEN (i.A + l.B)
ELSE l.B
END,
i.Rn
FROM #Numbers i INNER JOIN lagged l
ON i.Rn = l.Rn + 1
)
SELECT *
FROM lagged;
But this being Hive, it doesn't support CTEs so I need to dumb the SQL down a touch. Is that possible using LAG/LEAD? My brain is hurting having got this far!
I initially thought that it would help to first compute the Sum of all elements until each rank and then fix the values somehow using negative elements.
However, one big negative that would zero the B column will carry forward in the sum and will make all following elements negative.
It's as Gordon commented - 0 is max in the calculation =MAX(0,B1+A2) depends on the previous location where it happened and it seems to be impossible to compute them in advance analytically.
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!
I'm trying to conceptually understand how to rewrite the HAVING clause and COUNT function.
I was asked "Find the names of all reviewers who have contributed three or more ratings. (As an extra challenge, try writing the query without HAVING or without COUNT.)" in relation this this simple database: http://sqlfiddle.com/#!5/35779/2/0
The query with HAVING and COUNT is easy. Without, I'm having difficulty.
Help would be very much appreciated. Thank you.
One option would be to use SUM(1) in place of COUNT in a subquery, and using WHERE instead of HAVING:
SELECT b.name
FROM (SELECT rID,SUM(1) Sum1
FROM rating
GROUP BY rID
)a
JOIN reviewer b
ON a.rID = b.rID
WHERE Sum1 >= 3
Demo: SQL Fiddle
Update: Some explanation of SUM(1):
Adding a constant to a SELECT statement will result in that value being repeated for every row returned, for example:
SELECT rID
,1 as Col1
FROM rating
Returns:
| rID | Col1 |
|-----|------|
| 201 | 1 |
| 201 | 1 |
| 202 | 1 |
| 203 | 1 |
| 203 | 1 |
......
SUM(1) is applying a constant 1 to every row and aggregating it.