What does a double not exists clause mean? - mysql

SELECT c.name
FROM Customer c
WHERE NOT EXISTS(SELECT w.WID
FROM Woker w
WHERE NOT EXISTS(SELECT la
FROM look_after la
WHERE la.CID = c.CID
AND la.WID = w.WID));
I dont know what the code means... Could anyone tell me broadly what the code do? C is a Customer, who will looked after from a Worker.

The query selects customers that are looked after by all workers.
The double not exists is a way to implement relational division.

As an illustration to Andomar's excellent answer an example:
-- Some test data
DROP SCHEMA tmp CASCADE;
CREATE SCHEMA tmp ;
SET search_path=tmp;
CREATE TABLE persons
( person_id INTEGER NOT NULL PRIMARY KEY
, pname varchar
);
INSERT INTO persons( person_id, pname ) VALUES
(1 , 'Bob' ) ,(2 , 'Alice' ) ,(3 , 'Carol' )
;
CREATE TABLE movies
( movie_id INTEGER NOT NULL PRIMARY KEY
, mname varchar
);
INSERT INTO movies( movie_id, mname ) VALUES
(1, 'The Blues brothers' ), (2, 'Modern Times' ), (3, 'The Sound of Music' )
,(4, 'Amadeus' ), (5, 'Never say Never' )
;
-- people that have seen a particular movie
CREATE TABLE person_movie
( person_id INTEGER NOT NULL
, movie_id INTEGER NOT NULL
, PRIMARY KEY ( person_id, movie_id)
);
INSERT INTO person_movie( person_id, movie_id) VALUES
(1 ,5 ) ,(1 ,1 )
,(2 ,5 ) ,(2 ,4 ) ,(2 ,1 ) ,(2 ,3 ) ,(2 ,2 )
,(3 ,1 ) ,(3 ,3 )
;
-- Find the people that have seen ALL the movies
-- This is equivalent to:
-- Find persons for whom NO movie exists that (s)he has NOT seen
SELECT * FROM persons p
WHERE NOT EXISTS (
SELECT * FROM movies m
WHERE NOT EXISTS (
SELECT * FROM person_movie pm
WHERE pm.movie_id = m.movie_id
AND pm.person_id = p.person_id
)
);
-- similar: Find the movies that have been seen by ALL people
SELECT * FROM movies m
WHERE NOT EXISTS (
SELECT * FROM persons p
WHERE NOT EXISTS (
SELECT * FROM person_movie pm
WHERE pm.movie_id = m.movie_id
AND pm.person_id = p.person_id
)
);
Results:
person_id | pname
-----------+-------
2 | Alice
(1 row)
movie_id | mname
----------+--------------------
1 | The Blues brothers
(1 row)

Related

How return foregin key existence information to select result?

I have a query:
SELECT `Name`, `ID_dir`, 999 as `children`
FROM `dir` dir WHERE dir.`fid_parent` IS NULL
AND (
EXISTS (
SELECT 1
FROM `file` f
WHERE dir.ID_dir = f.fid_parent
)
OR (
SELECT 1
FROM `dir` d2
WHERE dir.ID_dir = d2.fid_parent
)
)
Where I check if the directory has any foreign key.
How can I move that information in place of 999 in "Select ... 999 as children"?
I want to return (0 or 1) xor Boolean in that place as children.
Put the EXISTS subquery in the SELECT list.
SELECT Name, ID_Dir, (
EXISTS (
SELECT 1
FROM `file` f
WHERE dir.ID_dir = f.fid_parent
)
OR (
SELECT 1
FROM `dir` d2
WHERE dir.ID_dir = d2.fid_parent
)
) AS children
FROM dir WHERE fid_parent IS NULL

How to sort by timestamp streaks SQL

Say I have the following tables (simplified version of what I'm working with):
CREATE TABLE posts (
id INTEGER,
title VARCHAR(255),
text TEXT,
author_id INTEGER,
created_at TIMESTAMP
);
CREATE TABLE authors (
id INTEGER,
name VARCHAR(255),
email VARCHAR(255)
);
What I want to do is retrieve only the authors but order them by number of one-week streaks. That is, number of consecutive weeks an author published a post. The time a post has been made is stored in the posts created_at column
What I'm having the most difficulty with is understanding how to calculate the difference in time between posts across rows. I’m using MySQL
Without window functions it's a bit hard to do in MySql 5.7
But here's an experiment test snippet that uses variables :
Sample data:
DROP TABLE IF EXISTS `posts`;
DROP TABLE IF EXISTS `authors`;
CREATE TABLE `authors` (
id INTEGER PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(255),
email VARCHAR(255)
);
CREATE TABLE `posts` (
id INTEGER PRIMARY KEY AUTO_INCREMENT,
title VARCHAR(255),
`text` TEXT,
author_id INTEGER,
created_at TIMESTAMP,
CONSTRAINT fk_posts_author_id FOREIGN KEY (author_id) REFERENCES `authors`(id)
);
insert into `authors` (name, email) values
('john doe', 'john.doe#home.net'),
('jane sheppard', 'jane.sheppard#home.net');
insert into `posts` (author_id, created_at, title, `text`) values
(1, '2019-02-07', 'When', 'bla'),
(1, '2019-02-09', 'I', 'bla2'),
(1, '2019-02-14', 'Start', 'bla3'),
(1, '2019-02-19', 'looking', 'bla4'),
(1, '2019-03-10', '...', 'bla5'),
(2, '2019-02-01', 'I', 'blah1'),
(2, '2019-02-05', 'frighten', 'blah2'),
(2, '2019-02-19', 'even', 'blah3'),
(2, '2019-03-20', 'myself', 'blah4');
Query:
SELECT q3.ConcurrentWeeks, q3.StartWeekDate, a.*
FROM
(
SELECT COUNT(*) as ConcurrentWeeks, MIN(WkDt) as StartWeekDate, author_id
FROM
(
SELECT q1.WkDt, q1.Total
, case
when #author = author_id and #yr = yr and #wk = wk-1 then #rnk
else #rnk := #rnk + 1
end as rnk
, #author := author_id as author_id
, #yr := yr as yr
, #wk := wk as wk
FROM
(
SELECT
author_id, YEAR(created_at) as yr, WEEK(created_at) as wk
, COUNT(*) AS Total
, COALESCE(MIN(STR_TO_DATE(concat(YEAR(created_at),' monday ',WEEK(created_at)),'%X %W %V')), MIN(CAST(created_at AS DATE))) AS WkDt
FROM `posts` p
GROUP BY author_id, YEAR(created_at), WEEK(created_at)
ORDER BY author_id, yr, wk
) q1
CROSS JOIN (select #author := null, #yr := null, #wk := null, #rnk := 0) init
) q2
GROUP BY author_id, rnk
HAVING ConcurrentWeeks > 1
) q3
LEFT JOIN `authors` a ON a.id = q3.author_id
ORDER BY ConcurrentWeeks DESC, StartWeekDate ASC
Result:
ConcurrentWeeks StartWeekDate id name email
--------------- ------------- -- ------------- ----------------------
3 2019-02-04 1 john doe john.doe#home.net
2 2019-01-28 2 jane sheppard jane.sheppard#home.net

MySQL sum plus count in same query

I'm trying to get the rates from anonymous people plus the ones who are registered. They are in different tables.
SELECT product.id, (SUM( users.rate + anonymous.rate ) / COUNT( users.rate + anonymous.rate ))
FROM products AS product
LEFT JOIN users ON users.id_product = product.id
LEFT JOIN anonymous ON anonymous.id_product = product.id
GROUP BY product.id
ORDER BY product.date DESC
So, the tables are like the following:
users-->
id | rate | id_product | id_user
1 2 2 1
2 4 1 1
3 5 2 2
anonymous-->
id | rate | id_product | ip
1 2 2 192..etc
2 4 1 198..etc
3 5 2 201..etc
What I'm trying with my query is: for each product, I would like to have the average of rates. Currently the output is null, but I have values in both tables.
Thanks.
Try like this..
SELECT product.id, (SUM( ifnull(ur.rate,0) + ifnull(ar.rate,0) ) / (COUNT(ur.rate)+Count(ar.rate)))
FROM products AS product
LEFT JOIN users_rate AS ur ON ur.id_product = product.id
LEFT JOIN anonymous_rate AS ar ON ar.id_product = product.id
GROUP BY product.id
Sql Fiddle Demo
First, you are getting a cross join for each product within the table. This is not what you really want. I think this is close to what you are looking for
SELECT p.id,
(coalesce(u.sumrate, 0) + coalesce(a.sumrate, 0)) / coalesce(u.num, 0) + coalesce(a.num, 0))
FROM products p LEFT JOIN
(select id_product, sum(rate) as sumrate, count(*) as num
from users u
group by id_product
) u
ON u.id_product = p.id left join
(select id_product, sum(rate) as sumrate, count(*) as num
from anonymous a
group by id_product
) a
ON a.id_product = p.id
ORDER BY p.date DESC;
Assuming that id is unique in the product table, you don't need an aggregation at the outer level.
You cannot use count and sum on joins if you group by
CREATE TABLE products (id integer);
CREATE TABLE users_rate (id integer, id_product integer, rate integer, id_user integer);
CREATE TABLE anonymous_rate (id integer, id_product integer, rate integer, ip varchar(25));
INSERT INTO products VALUES (1);
INSERT INTO products VALUES (2);
INSERT INTO products VALUES (3);
INSERT INTO products VALUES (4);
INSERT INTO users_rate VALUES(1, 1, 3, 1);
INSERT INTO users_rate VALUES(1, 2, 3, 1);
INSERT INTO users_rate VALUES(1, 3, 3, 1);
INSERT INTO users_rate VALUES(1, 4, 3, 1);
INSERT INTO anonymous_rate VALUES(1, 1, 3, '192..');
INSERT INTO anonymous_rate VALUES(1, 2, 3, '192..');
select p.id,
ifnull(
( ifnull( ( select sum( rate ) from users_rate where id_product = p.id ), 0 ) +
ifnull( ( select sum( rate ) from anonymous_rate where id_product = p.id ), 0 ) )
/
( ifnull( ( select count( rate ) from users_rate where id_product = p.id ), 0 ) +
ifnull( ( select count( rate ) from anonymous_rate where id_product = p.id ), 0 )), 0 )
from products as p
group by p.id
http://sqlfiddle.com/#!2/a2add/8
I've check on sqlfiddle. When there are no rates 0 is given. You may change that.

SQL Server Split Datetime for Day-Hourly Query?

SQL Server 2008.
declare #pardate table ( pardateid int, pardatewhen datetime2(3) )
insert into #pardate values ( 1 , '2011-09-17 12:43' )
insert into #pardate values ( 2 , '2011-09-17 12:44' )
insert into #pardate values ( 3 , '2011-10-11 12:45' )
insert into #pardate values ( 4 , '2011-10-12 12:46' )
insert into #pardate values ( 5 , '2011-10-13 12:47' )
insert into #pardate values ( 6 , '2011-11-20 12:48' )
insert into #pardate values ( 7 , '2011-11-21 12:49' )
insert into #pardate values ( 8 , '2011-11-22 12:50' )
declare #child table ( childid int , pardateid int , childvalue char(6) )
insert into #child values ( 1 , 1 , 'aaaaaa' )
insert into #child values ( 2 , 2 , 'bbbbbb' )
insert into #child values ( 3 , 3 , 'cccccc' )
insert into #child values ( 4 , 4 , 'dddddd' )
insert into #child values ( 5 , 5 , 'cccccc' )
insert into #child values ( 6 , 6 , 'cccccc' )
insert into #child values ( 7 , 7 , 'eeeeee' )
insert into #child values ( 8 , 8 , 'ffffff' )
select pardatewhen , childvalue , COUNT(childvalue)
from #child childtable join #pardate parenttable on childtable.pardateid=parenttable.pardateid
group by pardatewhen , childvalue
I am trying to get a count of #child.childvalue every day, every hour, so there would be 8760 rows in my result.
First pass had a loop and a CONVERT which takes ~5 minutes to run with the actual result set (this is just a sample for illustation). I did create a CTE to make a calendar temp table (using http://www.sqlpointers.com/2006/07/generating-temporary-calendar-tables.html), and thought it could be joined somehow to add "empty values" into the result set.
I need to get a result set that looks like this
date hour count
...
2011-09-17 0 0
....
2011-09-17 12 2
....
2011-10-11 12 1
How can that be done efficiently?
Thanks.
try this.
;WITH cal AS
(SELECT CAST('2011-01-01' AS DATETIME) AS cal_date
UNION ALL
SELECT DATEADD(hour,1,cal_date)
FROM cal
WHERE cal_date < '2011-12-31 23:00'
)
, par AS
(
select CAST(pardatewhen AS DATE) AS pardate, DATEPART(hh,pardatewhen) AS parhour , COUNT(childvalue) as num
from #child childtable
join #pardate parenttable on childtable.pardateid=parenttable.pardateid
group by CAST(pardatewhen AS DATE), DATEPART(hh,pardatewhen)
)
SELECT CAST(cal.cal_date AS DATE) AS [date],DATEPART(hh,cal.cal_date) AS [hour],ISNULL(par.num,0) AS [childvalue_count]
FROM cal
LEFT JOIN par
ON CAST(cal.cal_date AS DATE) = par.pardate
AND DATEPART(hh,cal.cal_date) = par.parhour
OPTION (MAXRECURSION 9999)
Something like (have childvalue in your query but not in your example result?)
select Cast(pardatewhen as Date) as [date], DatePart(hour,pardatewhen) as [hour] , childvalue , COUNT(childvalue)
from #child childtable
join #pardate parenttable on childtable.pardateid=parenttable.pardateid
group by Cast(pardatewhen as Date), DatePart(hour,pardatewhen), childvalue
Note Date type was introduced in SQL 2008

SQL Server 2008 Pivot Key Value to Joined Parent?

SQL Server 2008, I have the following parent/child table schemata and rows:
create table #par( parid int primary key identity(1,1) , partext varchar(6) )
create table #chi( chiid int primary key identity(1,1) , parid int null , chirefid int null , chiinfo varchar(6) )
create table #chiref ( chirefid int primary key identity(1,1) , chisubdesc varchar(9) )
insert into #par values ( 'par1' ) , ('par2')
insert into #chiref values ( 'chi1' )
insert into #chiref values ( 'chi2' )
insert into #chiref values ( 'chi3' )
insert into #chi values ( 1 , 1 , 'aaa' )
insert into #chi values ( 1 , 2 , 'bbb' )
insert into #chi values ( 2 , 1 , 'ccc' )
insert into #chi values ( 2 , 2 , 'ddd' )
insert into #chi values ( 2 , 3 , 'eee' )
The child #chi has just key/value pairs inside, and I need to convert the text (key) into a column and put the value inside, so the result set is shaped like the below. What is the best way to do that (I cannot change the key/value stuff, it is inherited from another system). And there is a join on the #chiref table for the actual column names (which is really killing me).
partext chi1 chi2 chi3
par1 aaa bbb
par2 ccc ddd eee
Thanks.
EDIT - The thing to remember is that the column names have to match the "key value" in the table. So if the EAV row key is "chi1" then the pivot has to have "chi1". I had simply "1" which broke. So I got it.
Thanks again!
select partext, [chi1], [chi2], [chi3]
from
(
select p.partext, c.chitext, c.chiinfo
from #chi c
join #par p
on c.parid = p.parid
) AS SourceTable
pivot
(
min(chiinfo)
for chitext in ([chi1], [chi2], [chi3])
) as PivotTable
or this one is a little more efficient, although more code:
with c
as
(
select parid, [chi1], [chi2], [chi3]
from
(
select parid, chitext, chiinfo
from #chi
) AS SourceTable
pivot
(
min(chiinfo)
for chitext in ([chi1], [chi2], [chi3])
) as PivotTable
)
select p.partext, c.*
from c
join #par p
on c.parid = p.parid
if you need a dynamic column number, you can do dynamic query:
declare #ColumnList varchar(max)
select #ColumnList = isnull(#columnList + ', ', '') + '[' + chitext + ']'
from
(
select distinct chitext
from #chi
) tt
order by chitext
declare #Command varchar(max) = '
with c
as
(
select parid, ' + #ColumnList + '
from
(
select parid, chitext, chiinfo
from #chi
) AS SourceTable
pivot
(
min(chiinfo)
for chitext in (' + #ColumnList + ')
) as PivotTable
)
select p.partext, ' + #ColumnList + '
from c
join #par p
on c.parid = p.parid
'
execute(#Command)