How to write a SQL-Query for the following table? - sql

My table is defined like this:
Name is a string and property too.
ID | Name | Property
An example for data in this table is this:
ID | Name | Property
1 Peter Newsletter
2 Paul Register
3 Peter Register
4 Shaun Newsletter
5 Steve Register
Now I like to query all people that have the property newsletter and register.
As a result I should get Peter, because he has both property's.
So the resulting table should be like:
ID | Name | Property
1 Peter Newsletter
3 Peter Register
So everything I try to query is which person has both property's newsletter and register.

Here is one method:
select t.*
from table t
where exists (select 1
from table t2
where t2.name = t.name and t2.property = 'NewsLetter'
) and
exists (select 1
from table t2
where t2.name = t.name and t2.property = 'Register'
);
If you just want the list of names, perhaps with ids, I would do that as:
select t.name
from table t
where t2.property in ('NewsLetter', 'Register')
group by t.name
having count(distinct property) = 2;
How you get the list of ids depends on your database, something like listagg() or group_concat() or string_agg().

An alternative, pretty much on the same lines as Gordon's solution, but without using EXISTS:
select * from tablename
where name in (select name from tablename where property = 'Newsletter')
and name in (select name from tablename where property = 'Register')

One more way:
SELECT * FROM T as T1
WHERE Property IN ('Newsletter','Register')
AND EXISTS (SELECT * FROM T
WHERE Name=T1.Name
and Property IN ('Newsletter','Register')
and Property <> T1.Property
)
SQLFiddle demo

Another one, for the record
WITH cteHasBoth
as (select Name
from MyTable
where Property in ('Newsletter', 'Register')
group by Name
having count(*) = 2)
select ID, Name
from MyTable
where name in (select Name from cteHasBoth)
This would require only two sacns through the table.

It's hard to be sure without knowing more about the data. Given the exact requirements that you gave to us, this will give the results you showed:
WITH multprop (multName) AS (
SELECT NAME FROM myTable
WHERE Property IN('Newsletter','Register')
GROUP BY NAME
HAVING count(*)>1 )
select id, Name, Property
from multprop inner join myTable
on multName = Name
But minor differences in your requirements will mess things up. For example, will there ever be Property values other than the two you listed? Or can a Name show up multiple times with the same Property?
EDIT: The added WHERE clause limits rows in the CTE to the requested specific set of Property values. This is from the more detailed requirements in comments.

Related

SQL - How to get values from multiple tables without being ambiguous

Apologies if this question had been asked before (it probably did). I never used SQL before and the answers I've got only got me more confused.
I need to find out if an ID exists on different tables and get the total number from all tables.
Here is my query:
select * from public.ui1, public.ui2, public.ui3 where id = '123'
So if id 123 doesn't exist in ui1 and ui2 but does exist in ui3, I'd still like to get it. (I would obviously like to get it if it exists in the other tables)
I am currently getting an ambiguous error message as id exists in all tables but I am not sure how to construct this query in the appropriate manner. I tried join but failed miserably. Any help on how to reconstruct it and a stupid proof explanation would be highly appreciated!
EDIT: What I would finally like to find out is if id = 123 exists in any of the tables.
It's a bit unclear what the result is you expect. If you want the count then you can use a UNION ALL
select 'ui1' as source_table,
count(*) as num_rows
from public.ui1
where id = 123
union all
select 'ui2',
count(*)
from public.ui2
where id = 123
union all
select 'ui3',
count(*)
from public.ui3
where id = 123
If you only want to know if the id exists in at least one of the tables (so a true/false) result you can use:
select exists (select id from ui1 where id = 123
union all
select id from ui2 where id = 123
union all
select id from ui3 where id = 123)
What I would finally like to find out is if id = 123 exists in any of the tables.
The best way to do this is probably just using exists:
select v.id,
(exists (select 1 from public.ui1 t where t.id = v.id) or
exists (select 1 from public.ui2 t where t.id = v.id) or
exists (select 1 from public.ui3 t where t.id = v.id)
) as exists_flag
from (values (123)) v(id);
As written, this returns one row per id defined in values(), along with a flag of whether or not the id exists -- the question you are asking.
This can easily be tweaked if you want additional information, such as which tables the id exists in, or the number of times each appears.

querying data with sqlite

I have data in an sqlite db that contains the following columns:
date | name | id | code
all as TEXT (I sourced it from a csv file) and I want to build a query that finds all names that have code ABC120 but not ABC306 nor ABC305 on the same date and group the result GROUP BY name.
How do I do this?
If you want to use GROUP BY you must group by name, date first and set the conditions in the HAVING clause, but also you must use DISTINCT so the results do not contain duplicate names:
select distinct name
from tablename
group by name, date
having sum(code = 'ABC120') > 0 and sum(code in ('ABC305', 'ABC306')) = 0;
You can get the same results with EXISTS:
select distinct t.name
from tablename t
where t.code = 'ABC120'
and not exists (select 1 from tablename where name = t.name and date = t.date and code in ('ABC305', 'ABC306'))
You can use having:
select date, name
from t
where code in ('ABC120', 'ABC306', 'ABC305')
group by date, name
having min(code) = 'ABC120' and max(code) = 'ABC120';
Note: because of the three codes you chose, you could just use max(code) = 120. However, that does not generalize to other code values.

I need a query for children that start with my name but doesn't start with any in that set

Suppose I have a table with text primary key called "name". Given a name (that may contain any arbitrary characters including %), I need all of the rows from that table that start with that name, are longer than that name, and that don't start with anything else in the table that is longer than the given name.
For example, suppose my table contains names ad, add, adder, and adage. If I query for "children of ad", I want to get back add, adage. (adder is a child of add). Can this be done efficiently, as I have several million rows? Recursive queries are certainly available.
I have a different approach at present where I maintain a "parent" column. The code to maintain this column is quite painful, and it would be unnecessary if this other approach were reasonable.
I can't tell about its efficiency but I think it works:
with cte as (
select name
from tablename
where name like 'ad' || '_%'
)
select c.name
from cte c
where not exists (
select 1 from cte
where c.name like name || '_%'
);
See the demo.
Equivalent to the above query with a self LEFT JOIN:
with cte as (
select name
from tablename
where name like 'ad' || '_%'
)
select c.name
from cte c left join cte cc
on c.name like cc.name || '_%'
where cc.name is null
See the demo.
Results:
| name |
| ----- |
| add |
| adage |
You could use left on the column like this:
select *
from sometable
where lower(left(name,2)) = 'ad' and length(name) > 2

(TRANSACT SQL) how to create Master-Detail in a row using sql?

I have two tables
table1
------
ID
NAME
ADDRESS
table2
-------
ID
PHONE
EMAIL
how can i create report like this
------------------------------------
01 Dave 123 Veneu
555-5 A#YAHOO.COM
66-66 B#Yahoo.co.id
213-1 D#c.com
02 John 23 Park
322-1 C#you.com
54-23 D#Net.com
231-2 me#you.com
im using sql server 2005 express,, thank you in advance.
Not sure why you would ever want to write this in anything other than a report designer, but just for the hell of it:
SELECT ID AS Column1, NAME AS Column2, Address AS Column3, ID AS SortColumn1, 1 AS SortColumn2
UNION
SELECT '', PHONE, EMAIL, ID AS SortColumn1, 2 AS SortColumn2
ORDER BY SortColumn1, SortColumn2
The output is going to basically be a load of gibberish really, and you've got the two extra columns on the end of to get rid of.
You shouldn't. It's a general principle that formatting should not be done in the database layer.
SQL Server should be used to generate data, then your application should process the data, including the formatting.
I would open two queries. One that loads table one, ordered by the ID column. And the other that load table two, also ordered by the ID column. You can then iterate through both record sets at the same time, something like the following pseudo-code...
rs1 = SQL.Execute("SELECT * FROM table1 ORDER BY ID")
rs2 = SQL.Execute("SELECT * FROM table2 ORDER BY ID, phone")
rs2.Next()
WHILE rs1.Next()
Output The Address Info Here
WHILE rs1.ID = rs2.ID
Output The Phone/Email Info Here
rs2.Next()
END WHILE
END WHILE
In order to have one-to-many relationship, as in your example (one person has multiple phones and emails), you need to add some kind of link column to the second table, which would contain the ID of the person the email / phone belongs to.
So your table structure should look like this:
table1
------
ID
NAME
ADDRESS
table2
-------
ID
TABLE1_ID
PHONE
EMAIL
Then, you could query your data using joins:
SELECT table1.name, table1.address, table2.phone, table2.email WHERE table2.table1_id = table1.id
I strongly agree with dems, but if you really need to come up with something like that, the following could work (albeit without the empty lines)
SELECT case
when group_rn = 1 then id
else ''
end as id,
case
when group_rn = 1 then name
else phone
end as name_phone_column,
case
when group_rn = 1 then address
else _email
end as address_email_column
FROM (
SELECT t1.id,
t1.name,
t1.address,
t2.email,
t2.phone,
row_number() over (partition by t1.id order t1.name) as group_rn
FROM table1 t1
LEFT JOIN table2 t2 ON t1.id = t2.id
) t
ORDER BY id
This assumes that phone and name both have the same datatype, just like address and email.

SQL get rows matching ALL conditions

I would like to retrieve all rows matching a set of conditions on the same column. But I would like the rows only if ALL the conditions are good, and no row if only one condition fails.
For example, taking this table:
|id|name|
---------
|1 |toto|
|2 |tata|
I would like to be able to request if "tata" && "toto" are in this table. But when asking if "tata" and "tuto" are in, I would like an empty response if one of argument is in not in the table, for example asking if "toto" && "tutu" are included in the table.
How can I do that ?
Currently, I'am doing one query per argument, which is not very efficient. I tried several solutions including a subselect or a group+having, but no one is working like I want.
thanks for your support !
cheers
This isn't the most efficient way, but this query would work.
SELECT * FROM table_name
WHERE (name = 'toto' OR name = 'tata')
AND ( SELECT COUNT(*) FROM table_name WHERE name = 'toto') > 0
AND ( SELECT COUNT(*) FROM table_name WHERE name = 'tata') > 0
This is a little vague. If the names are unique, you could count the matching rows that match a where clause:
where name='toto' or name='tata'
If the count is 2, then you know both matched. If name is not unique you could potentially select the first ID (select top 1 id ...) that matches each in a union and count those with an outer select.
Even if you had an arbitrary number of names to match, you could create a stored procedure or code in whatever top-level language you are using to build the select statement.
SELECT 1 AS found FROM hehe
WHERE 1 IN (SELECT 1 FROM hehe WHERE name='tata')
AND 1 IN (SELECT 1 FROM hehe WHERE name='toto')
If name is unique you can simplify to:
SELECT *
FROM tbl
WHERE name IN ('toto', 'tata')
AND (SELECT count(*) FROM tbl WHERE name IN ('toto', 'tata')) > 1;
If it isn't:
SELECT *
FROM tbl
WHERE name IN ('toto', 'tata')
AND EXISTS (SELECT * FROM tbl WHERE name = 'toto')
AND EXISTS (SELECT * FROM tbl WHERE name = 'tata');
Or, in PostgreSQL, MySQL and possibly others:
SELECT *
FROM tbl
WHERE name IN ('toto', 'tata')
AND (SELECT count(DISTINCT name) FROM tbl WHERE name IN ('toto', 'tata')) > 1;