Relational algebra without aggregate function - sql

We have a table Phone whose values are
(Name, Number)
--------------
(John, 123)
(John, 456)
(Bravo, 789)
(Ken, 741)
(Ken, 589)
If the question is to Find the guy who uses only one number, the answer is Bravo.
I solved this using aggregate function. But I don't know how to solve without using aggregate function.

Here is my solution:
SELECT *
FROM test t
WHERE NOT EXISTS (
SELECT 1
FROM test
WHERE NAME = t.NAME
AND number <> t.number);
And a sample SQLFiddle.
I'm not sure about this representation in relational algebra (and it's most likely not correct or complete but it might give you a starting point):
RESULT = {(name, number) ∈ TEST | (name, number_2) ¬∃ TEST, number <> number_2}
(this is the main idea, you could probably try and have a look here to try and rewrite this correctly, since I haven't written anything in relational algebra for more than 10 years).
Or maybe you're looking for a different type of representation, like this one here?

You can use LEFT JOIN and use the same table in your JOIN , something like this..
SELECT a.NAME, a.NUMBER FROM test a
LEFT JOIN test b ON a.name = b.name AND a.number <> b.number
WHERE b.name IS NULL;
Hope this helps. :)

Try the following if you are using Access-SQL -
SELECT Name
FROM Phone
GROUP BY Name
HAVING COUNT(Name) = 1;
Otherwise, try -
SELECT Name
FROM Phone
WHERE COUNT(Name) = 1;
If you have any questions, then please feel free to reply.

You can take advantage of RANK() functions something like below.
SELECT * FROM #Tbl WHERE Name NOT IN(
SELECT Name FROM (
SELECT Name, RANK() OVER(PARTITION BY Name ORDER BY Id) AS Rank
FROM #Tbl) t
WHERE t.Rank > 1)
The only draw back of this method is, you need to use unique id from your table to get the right result.
SQLFiddle

Another way of doing it is:
select Name from Phone p
where (select name from Phone p2 where p.name = p2.name and p2.number <> p.number limit 1) is null
Edit: added limit 1 to make sure sub select returns a scalar

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.

How to split a single column into multiple columns in SQL Server select statement?

I am new to SQL Server . I have a single long column with names starting from a, b, c and d. I want to show them in separate columns of NameA, NameB, NameC and NameD. I tried union but it shows in one column. I tried case but I dont know how to use it. Please help.
Existing column
names
A1
B1
A2
C1
A3
A4
A_names| B_names | C_names
A1 | B1 | C1
A2
A3
A4
just for fun and curious why you want that:
select *
from
( select idx = left(names,1)
, names
, rn = row_number() over (partition by left(names,1) order by names)
from
( values ('A1'),('B1'),('A2'),('C1'),('A3'),('A4'),('B2'))
v(names)
) dat
pivot
( max(names)
for idx in ([A],[B],[C],[D])
) p
http://sqlfiddle.com/#!6/9eecb/4013/0
I don't think this is something that should be solved in SQL. It's a representational thing that should probably be done in the application.
However, if you insist to use SQL for this, this is how you could do it. The main problem with this query is that the ROW_NUMBER function will be quite bad for performance.
WITH nameA
(
SELECT name, ROW_NUMBER() OVER(ORDER BY name) AS rn
FROM t1
WHERE name LIKE 'a%'
), nameB AS
(
SELECT name, ROW_NUMBER() OVER(ORDER BY name) AS rn
FROM t1
WHERE name LIKE 'b%'
)
SELECT name FROM nameA
FULL OUTER JOIN nameB
ON nameA.rn = nameB.rn
ORDER BY nameA.rn,nameB.rn;
You can use CASE (https://msdn.microsoft.com/en-us/library/ms181765.aspx)
This Query should work:
SELECT
CASE WHEN users.name like 'a%' THEN users.name ELSE NULL END AS NameA,
CASE WHEN users.name like 'b%' THEN users.name ELSE NULL END AS NameB,
CASE WHEN users.name like 'c%' THEN users.name ELSE NULL END AS NameC,
CASE WHEN users.name like 'd%' THEN users.name ELSE NULL END AS NameD
FROM users
Look at this post, it is very close to your problem.
Itzik Ben-Gan | SQL Server Pro
This isn't really the way relational databases work. When you have data in the same row, it's supposed to be related in some way - a common ID, at the least. What is it that would connect the person whose name happens to begin with A to the person whose name happens to begin with B? Why would you ever want the RDBMS to make such an arbitrary connection?
If you have a requirement to display users in such a way, you'd just want to write several queries and have your presentation layer deal with laying them out properly, e.g.
SELECT name FROM users WHERE name LIKE 'a%'
SELECT name FROM users WHERE name LIKE 'b%'
SELECT name FROM users WHERE name LIKE 'c%'
etc...
The presentation layer could run each query and then populate a table appropriately. Even better would be to have the presentation layer just run this query:
SELECT name FROM users
And then appropriately sort and display the data, which is probably going to be less expensive than multiple scans on your users table by SQL Server.

SQL - passing variable from first select to second select

I have one table things full of items listed by ItemID. Given an ItemID, I need to get the record with the ItemID and all other items with the same name.
In the sample data below, given the ItemID of 1, I need to select all records with the same name (in this case, "poptarts") as ItemID 1, including the record with ItemID 1.
ItemID = 1 name = poptarts
ItemID = 7 name = poptarts
ItemID = 8 name = cheddar
ItemID = 323 name = poptarts
select a.ItemID, a.name from things where a.ItemID = '1'
UNION
select b.ItemID, b.name from things where b.name = a.name
The SQL I've written above however does not pass a.name to the second select. Is there any way to pass the first name value to the second select? I would like for the statement to return itemid = 1 as the first row and 7 and 323 as the other rows.
UNION is only really used to concatenate two distinct sets. Based on your example, you could probably do something like this:
SELECT a.ItemID, a.Name
FROM things a
WHERE name IN (SELECT name FROM things WHERE itemID = 1)
There are lots of ways to write this kind of query and will depend on which flavor of SQL you're using but this should be more or less universal.
select
a.itemID,
a.name
from
things a
where a.name in (
select name
from things b
where b.itemID = '1'
)
SELECT this.name, this.id, that.id
FROM thing this
LEFT JOIN thing that ON that.name=this.name AND that.id <> this.id
WHERE this.id = 1
;
NOTE: this also selects the this-rows that have no twin records; in that case the that.id will be NULL. If you want to suppress the records without twin-records, remove the LEFT.
UPDATE: added the id <> id clause to suppres the obvious match.
If you really only have one table, no need to bring it in twice, UNION, or anything fancy like htat.
SELECT
name
FROM
a --assuming this is your only table
GROUP BY
itemID, name
HAVING
itemID = '1'

SQL: How to properly check if a record exists

While reading some SQL Tuning-related documentation, I found this:
SELECT COUNT(*) :
Counts the number of rows.
Often is improperly used to verify the existence of a record.
Is SELECT COUNT(*) really that bad?
What's the proper way to verify the existence of a record?
It's better to use either of the following:
-- Method 1.
SELECT 1
FROM table_name
WHERE unique_key = value;
-- Method 2.
SELECT COUNT(1)
FROM table_name
WHERE unique_key = value;
The first alternative should give you no result or one result, the second count should be zero or one.
How old is the documentation you're using? Although you've read good advice, most query optimizers in recent RDBMS's optimize SELECT COUNT(*) anyway, so while there is a difference in theory (and older databases), you shouldn't notice any difference in practice.
I would prefer not use Count function at all:
IF [NOT] EXISTS ( SELECT 1 FROM MyTable WHERE ... )
<do smth>
For example if you want to check if user exists before inserting it into the database the query can look like this:
IF NOT EXISTS ( SELECT 1 FROM Users WHERE FirstName = 'John' AND LastName = 'Smith' )
BEGIN
INSERT INTO Users (FirstName, LastName) VALUES ('John', 'Smith')
END
You can use:
SELECT 1 FROM MyTable WHERE <MyCondition>
If there is no record matching the condition, the resulted recordset is empty.
You can use:
SELECT 1 FROM MyTable WHERE... LIMIT 1
Use select 1 to prevent the checking of unnecessary fields.
Use LIMIT 1 to prevent the checking of unnecessary rows.
SELECT COUNT(1) FROM MyTable WHERE ...
will loop thru all the records. This is the reason it is bad to use for record existence.
I would use
SELECT TOP 1 * FROM MyTable WHERE ...
After finding 1 record, it will terminate the loop.
The other answers are quite good, but it would also be useful to add LIMIT 1 (or the equivalent, to prevent the checking of unnecessary rows.
You can use:
SELECT COUNT(1) FROM MyTable WHERE ...
or
WHERE [NOT] EXISTS
( SELECT 1 FROM MyTable WHERE ... )
This will be more efficient than SELECT * since you're simply selecting the value 1 for each row, rather than all the fields.
There's also a subtle difference between COUNT(*) and COUNT(column name):
COUNT(*) will count all rows, including nulls
COUNT(column name) will only count non null occurrences of column name
Other option:
SELECT CASE
WHEN EXISTS (
SELECT 1
FROM [MyTable] AS [MyRecord])
THEN CAST(1 AS BIT) ELSE CAST(0 AS BIT)
END
I'm using this way:
IF (EXISTS (SELECT TOP 1 FROM Users WHERE FirstName = 'John'), 1, 0) AS DoesJohnExist

ORDER BY the IN value list

I have a simple SQL query in PostgreSQL 8.3 that grabs a bunch of comments. I provide a sorted list of values to the IN construct in the WHERE clause:
SELECT * FROM comments WHERE (comments.id IN (1,3,2,4));
This returns comments in an arbitrary order which in my happens to be ids like 1,2,3,4.
I want the resulting rows sorted like the list in the IN construct: (1,3,2,4).
How to achieve that?
You can do it quite easily with (introduced in PostgreSQL 8.2) VALUES (), ().
Syntax will be like this:
select c.*
from comments c
join (
values
(1,1),
(3,2),
(2,3),
(4,4)
) as x (id, ordering) on c.id = x.id
order by x.ordering
In Postgres 9.4 or later, this is simplest and fastest:
SELECT c.*
FROM comments c
JOIN unnest('{1,3,2,4}'::int[]) WITH ORDINALITY t(id, ord) USING (id)
ORDER BY t.ord;
WITH ORDINALITY was introduced with in Postgres 9.4.
No need for a subquery, we can use the set-returning function like a table directly. (A.k.a. "table-function".)
A string literal to hand in the array instead of an ARRAY constructor may be easier to implement with some clients.
For convenience (optionally), copy the column name we are joining to ("id" in the example), so we can join with a short USING clause to only get a single instance of the join column in the result.
Works with any input type. If your key column is of type text, provide something like '{foo,bar,baz}'::text[].
Detailed explanation:
PostgreSQL unnest() with element number
Just because it is so difficult to find and it has to be spread: in mySQL this can be done much simpler, but I don't know if it works in other SQL.
SELECT * FROM `comments`
WHERE `comments`.`id` IN ('12','5','3','17')
ORDER BY FIELD(`comments`.`id`,'12','5','3','17')
With Postgres 9.4 this can be done a bit shorter:
select c.*
from comments c
join (
select *
from unnest(array[43,47,42]) with ordinality
) as x (id, ordering) on c.id = x.id
order by x.ordering;
Or a bit more compact without a derived table:
select c.*
from comments c
join unnest(array[43,47,42]) with ordinality as x (id, ordering)
on c.id = x.id
order by x.ordering
Removing the need to manually assign/maintain a position to each value.
With Postgres 9.6 this can be done using array_position():
with x (id_list) as (
values (array[42,48,43])
)
select c.*
from comments c, x
where id = any (x.id_list)
order by array_position(x.id_list, c.id);
The CTE is used so that the list of values only needs to be specified once. If that is not important this can also be written as:
select c.*
from comments c
where id in (42,48,43)
order by array_position(array[42,48,43], c.id);
I think this way is better :
SELECT * FROM "comments" WHERE ("comments"."id" IN (1,3,2,4))
ORDER BY id=1 DESC, id=3 DESC, id=2 DESC, id=4 DESC
Another way to do it in Postgres would be to use the idx function.
SELECT *
FROM comments
ORDER BY idx(array[1,3,2,4], comments.id)
Don't forget to create the idx function first, as described here: http://wiki.postgresql.org/wiki/Array_Index
In Postgresql:
select *
from comments
where id in (1,3,2,4)
order by position(id::text in '1,3,2,4')
On researching this some more I found this solution:
SELECT * FROM "comments" WHERE ("comments"."id" IN (1,3,2,4))
ORDER BY CASE "comments"."id"
WHEN 1 THEN 1
WHEN 3 THEN 2
WHEN 2 THEN 3
WHEN 4 THEN 4
END
However this seems rather verbose and might have performance issues with large datasets.
Can anyone comment on these issues?
To do this, I think you should probably have an additional "ORDER" table which defines the mapping of IDs to order (effectively doing what your response to your own question said), which you can then use as an additional column on your select which you can then sort on.
In that way, you explicitly describe the ordering you desire in the database, where it should be.
sans SEQUENCE, works only on 8.4:
select * from comments c
join
(
select id, row_number() over() as id_sorter
from (select unnest(ARRAY[1,3,2,4]) as id) as y
) x on x.id = c.id
order by x.id_sorter
SELECT * FROM "comments" JOIN (
SELECT 1 as "id",1 as "order" UNION ALL
SELECT 3,2 UNION ALL SELECT 2,3 UNION ALL SELECT 4,4
) j ON "comments"."id" = j."id" ORDER BY j.ORDER
or if you prefer evil over good:
SELECT * FROM "comments" WHERE ("comments"."id" IN (1,3,2,4))
ORDER BY POSITION(','+"comments"."id"+',' IN ',1,3,2,4,')
And here's another solution that works and uses a constant table (http://www.postgresql.org/docs/8.3/interactive/sql-values.html):
SELECT * FROM comments AS c,
(VALUES (1,1),(3,2),(2,3),(4,4) ) AS t (ord_id,ord)
WHERE (c.id IN (1,3,2,4)) AND (c.id = t.ord_id)
ORDER BY ord
But again I'm not sure that this is performant.
I've got a bunch of answers now. Can I get some voting and comments so I know which is the winner!
Thanks All :-)
create sequence serial start 1;
select * from comments c
join (select unnest(ARRAY[1,3,2,4]) as id, nextval('serial') as id_sorter) x
on x.id = c.id
order by x.id_sorter;
drop sequence serial;
[EDIT]
unnest is not yet built-in in 8.3, but you can create one yourself(the beauty of any*):
create function unnest(anyarray) returns setof anyelement
language sql as
$$
select $1[i] from generate_series(array_lower($1,1),array_upper($1,1)) i;
$$;
that function can work in any type:
select unnest(array['John','Paul','George','Ringo']) as beatle
select unnest(array[1,3,2,4]) as id
Slight improvement over the version that uses a sequence I think:
CREATE OR REPLACE FUNCTION in_sort(anyarray, out id anyelement, out ordinal int)
LANGUAGE SQL AS
$$
SELECT $1[i], i FROM generate_series(array_lower($1,1),array_upper($1,1)) i;
$$;
SELECT
*
FROM
comments c
INNER JOIN (SELECT * FROM in_sort(ARRAY[1,3,2,4])) AS in_sort
USING (id)
ORDER BY in_sort.ordinal;
select * from comments where comments.id in
(select unnest(ids) from bbs where id=19795)
order by array_position((select ids from bbs where id=19795),comments.id)
here, [bbs] is the main table that has a field called ids,
and, ids is the array that store the comments.id .
passed in postgresql 9.6
Lets get a visual impression about what was already said. For example you have a table with some tasks:
SELECT a.id,a.status,a.description FROM minicloud_tasks as a ORDER BY random();
id | status | description
----+------------+------------------
4 | processing | work on postgres
6 | deleted | need some rest
3 | pending | garden party
5 | completed | work on html
And you want to order the list of tasks by its status.
The status is a list of string values:
(processing, pending, completed, deleted)
The trick is to give each status value an interger and order the list numerical:
SELECT a.id,a.status,a.description FROM minicloud_tasks AS a
JOIN (
VALUES ('processing', 1), ('pending', 2), ('completed', 3), ('deleted', 4)
) AS b (status, id) ON (a.status = b.status)
ORDER BY b.id ASC;
Which leads to:
id | status | description
----+------------+------------------
4 | processing | work on postgres
3 | pending | garden party
5 | completed | work on html
6 | deleted | need some rest
Credit #user80168
I agree with all other posters that say "don't do that" or "SQL isn't good at that". If you want to sort by some facet of comments then add another integer column to one of your tables to hold your sort criteria and sort by that value. eg "ORDER BY comments.sort DESC " If you want to sort these in a different order every time then... SQL won't be for you in this case.