Using VALUE to make temporary table - sql

What is the purpose of dummy function abc123() in the following queries? Both of them work, but I don't understand the need for the dummy function.
I've tried to remove it, but always with syntax errors.
SELECT MAX(NumbersTable) AS NumbersTable
FROM ( VALUES (1), (3), (2) ) AS abc123(NumbersTable)
SELECT TOP 1 NumbersTable
FROM ( VALUES (1), (3), (2) ) AS abc123(NumbersTable)
ORDER BY NumbersTable DESC
I expect the result to be 3, and that is what I get.

It is not a function. It defines the derived table created by values. The abc123 is the table alias. The NumbersTable is the name of the column.
If you run:
SELECT *
FROM ( VALUES (1), (3), (2) ) AS abc123(NumbersTable)
You will see:
NumbersTable
1
2
3
Because NumbersTable is the name of the column. You can also write:
SELECT abc123.NumbersTable
FROM ( VALUES (1), (3), (2) ) AS abc123(NumbersTable)

Related

Is one row on correct for 'SELECT (SELECT SUM(t.val)) FROM mytable t'

Postgres and some online SQL engines return just one row for the SELECT:
CREATE TABLE mytable(val INTEGER);
INSERT INTO mytable VALUES (1), (2), (3), (NULL);
SELECT (SELECT SUM(t.val)) FROM mytable t;
The result is:
6
Does it correspond to the SQL spec? Which section on the spec do I need?
Should the subquery be invoked for every table record? Why the result is not several rows like:
1
2
3
null

INNER JOIN on value instead of dimension

This is a tricky and (in my humble opinion) unnecessary question my friend got during an interview process, which I also did not know when he asked me about it. When you run this SQL query:
SELECT *
FROM (VALUES (1), (1), (Null), (Null), (Null)) AS tb1 (col)
JOIN (VALUES (1), (1), (1), (Null), (Null)) AS tb2 (col)
ON tb1.col = tb2.col
It generates this result:
tb1.col
tb2.col
1
1
1
1
1
1
1
1
1
1
1
1
Why this JOIN works like that?
As mentioned by jarlh, the NULLs are not compared when executing tb1.col = tb2.col.
As for what all the 1's are, perhaps the following query will help understanding where each value comes from.
In this example, we compare the first letter of the values (which is always the letter A)
SELECT *
FROM (VALUES ('Abigail'), ('Allie'), (Null), (Null), (Null)) AS tb1 (col)
JOIN (VALUES ('Aria'), ('Allison'), ('Audrey'), (Null), (Null)) AS tb2 (col)
ON left(tb1.col, 1) = left(tb2.col, 1)
col
col
Abigail
Aria
Abigail
Allison
Abigail
Audrey
Allie
Aria
Allie
Allison
Allie
Audrey

SELECT Rows Between two Number column in SQL Server

Hi i am having Ranking table
CREATE TABLE [dbo].[Ranking](
[Code] [varchar](5) NULL,
[Name] [varchar](50) NULL,
[RankFrom] [varchar](5) NULL,
[RankTo] [varchar](5) NULL
) ON [PRIMARY]
Adding following Values
INSERT INTO [Ranking]([Code],[Name],[RankFrom],[RankTo])VALUES('01','Hisanth','105','110')
My result as follows when my query is
select * from Ranking
Code
Name
RankFrom
RankTo
01
Hisanth
105
110
But I need to get result like as follows
Code
Name
RankTo
01
Hisanth
105
01
Hisanth
106
01
Hisanth
107
01
Hisanth
108
01
Hisanth
109
01
Hisanth
110
How to write query for this
One option uses a recursive query:
with cte as (
select code, name, rankfrom, rankto from ranking
union all
select code, name, rankfrom, rankto - 1 from cte where rankto > rankfrom
)
select code, name, rankto from cte
If your ranges may be greater than 100, then you need to add option(maxrecursion 0) at the very end of the query.
If you really are storing these numbers as strings (which obviously is not a good idea), then you would need to convert them first:
with cte as (
select code, name, convert(int, rankfrom) as rankfrom, convert(int, rankto) as rankto from ranking
union all
select code, name, rankfrom, rankto - 1 from cte where rankto > rankfrom
)
select code, name, rankto from cte
The use of recusion is the worst thing to do in terms of performances. You never should write a CTE recursive query like the one that have been given in the first answer, but create a hard table of number into your database, like this one :
CREATE TABLE N (i INT PRIMARY KEY);
INSERT INTO N VALUES (0), (1), (2), (3), (4), (5), (6), (7), (8), (9);
INSERT INTO N
SELECT N1.i + 10*N2.i + 100*N3.i + 1000*N4.i
FROM N AS N1
CROSS JOIN N AS N2
CROSS JOIN N AS N3
CROSS JOIN N AS N4
WHERE N1.i + 10*N2.i + 100*N3.i + 1000*N4.i >= 10;
Then the answer is trivial :
SELECT R.*
FROM Ranking AS R
JOIN N ON i BETWEEN R.RANKFROM AND R.RANKTO;
To execute it faster, create an index like this one :
CREATE INDEX X_RNK_FROM_TO ON Ranking (RANKFROM, RANKTO);
To have much more speed, store the N table in a readonly filegroup.
IN CONCLUSION : every database should have a number table and a calendar one. They are called tally table and are a great use for this typical cases.

postgresql count the top 3 rows

I know we can use LIMIT in PostreSQL to get the top 3 values in the relation but what if there are duplicate values. For example,
5
4
4
3
2
Ordering it in DESC order and using LIMIT 3 will just return 5,4,4. But how do we get 5,4,4,3 (The top 3 with duplicates).
I know how to do this the long way but I was wondering if there are any PostreSQL built in things?
One easy way would be to use the dense_rank window function to rank the values as desired and then peel off those with the desired ranks.
For example, given this:
create table t (
id serial not null primary key, -- just a placeholder so that we can differentiate the duplicate `c`s.
c int not null
);
insert into t (c)
values (1), (1), (2), (3), (4), (4), (4), (5);
you'd presumably want the rows with c in (5,4,3) and you could do that with:
select id, c
from (
select id, c, dense_rank() over (order by c desc) as r
from t
) dt
where r <= 3
Demo: http://sqlfiddle.com/#!15/5b262/8
Note that you need to use dense_rank rather than rank because rank will arrange things in the right order but it will leave gaps in the rankings so r <= 3 wouldn't necessarily work. Compare the r values in the above fiddle with dense_rank and rank and you'll see the difference.
Suppose you have below record
index Name
1 "ABC"
2 "XYZ"
2 "ABC"
1 "XYZ"
1 "XYZ"
in this case you can distinct to choose non duplicate record.
Select distinct index, name from table_name;

Dynamic SQL Procedure that can insert into a table using a while loop to control the number of row entries

I have a small SQL based challenge that i'm trying to solve to better my knowledge of Dynamic SQL.
My requirements are as follows.
I created a table that looks as follows:
CREATE TABLE Prison_Doors
(
DoorNum INT IDENTITY(1,1) PRIMARY KEY,
DoorOpen BIT,
DoorClosed BIT,
Trips INT
)
GO
I need to Create a Dynamic SQL Proc to insert 50 Door numbers and assign them as closed.
Expected result of proc:
|DoorNum|DoorOpen|DoorClosed|Trips|
|-------|--------|----------|-----|
| 1 | 0 | 1 |null |
|-------|--------|----------|-----|
|---------All the way to 50-------|
|-------|--------|----------|-----|
| 50 | 0 | 1 |null |
This is what I have written but it is not inserting:
BEGIN
DECLARE #SQL VARCHAR(8000)
DECLARE #Index INT
SET #Index=1
WHILE (#Index<=50)
BEGIN
SET #SQL= 'INSERT INTO Prison_Doors(DoorNum,DoorOpen,DoorClosed)
VALUES('+CAST(#Index AS VARCHAR)+',0,1),'
SET #Index=#Index+1
END
SET #SQL = SUBSTRING(#SQL, 1, LEN(#SQL)-1)
EXEC(#SQL)
END
I would like to know what I am doing wrong.
after all of this is done I then need to run another loop to start at door one and change every second door to open and change trips to one and then increment to every 3 doors to open and trips becomes 2 and this incrementation continues until all doors are open which will then select the number of trips that it took.
I hope somebody can assist me with this as I am new to Dynamic SQL and I just need some guidance and not the complete solution.
Help is much appreciated :)
The error you got is because you are trying to insert into an identity column DoorNumber:
DoorNum INT IDENTITY(1,1) PRIMARY KEY,
Remove that columns from the column list, instead of:
INSERT INTO Prison_Doors(DoorNum,DoorOpen,DoorClosed)
remove that column DoorNum:
INSERT INTO Prison_Doors(DoorOpen,DoorClosed)
...
However, there is no need for dynamic SQL to do that, you can do this using an anchor table like this:
WITH temp
AS
(
SELECT n
FROM (VALUES(1), (2), (3), (4)) temp(n)
), nums
AS
(
SELECT ROW_NUMBER() OVER(ORDER BY (SELECT 1)) AS n
FROM temp t1, temp t2, temp t3
)
INSERT INTO Prison_Doors(DoorOpen, DoorClosed)
SELECT 0 AS DoorOpen, 1 AS DoorClosed
FROM nums
WHERE n <= 50;
Live Demo.
Update:
What my code does line by line?
Generating Sequence of numbers:
The first problem was generating a sequence of 50 numbers from 1 to 50, I used an anchor table with only four rows from 1 to 4 like this:
SELECT n
FROM (VALUES(1), (2), (3), (4)) temp(n);
This syntax using the VALUES is new to SQL-Server-2008, it is called Row Value Constructor. After the VALUES, you assign an alias of the table and the target columns in parentheses like temp(n).
For old versions you have to use something like :
SELECT n
FROM
(
SELECT 1 AS n
UNION ALL
SELECT 2
UNION ALL
SELECT 3
UNION ALL
SELECT 4
) AS temp;
This will give you only 4 rows, but we need to generate 50. Thats why I CROSS JOIN this table three time with itself using:
FROM temp t1, temp t2, temp t3
It is the same as
FROM temp t1
CROSS JOIN temp t2
CROSS JOIN temp t3
This will multiply these rows 64 times, 4 rows3 = 64 rows:
1 1 1
1 2 1
1 3 1
1 4 1
2 1 1
2 2 1
2 3 1
2 4 1
....
3 1 4
3 2 4
3 3 4
3 4 4
4 1 4
4 2 4
4 3 4
4 4 4
The Use Of ROW_NUMBER() Function:
Then using the ROW_NUMBER() will give us a ranking number from 1 to 64 like this:
WITH temp
AS
(
SELECT n
FROM (VALUES(1), (2), (3), (4)) temp(n)
)
SELECT ROW_NUMBER() OVER(ORDER BY (SELECT 1)) AS n
FROM temp t1, temp t2, temp t3;
Note that: ROW_NUMBER requires an ORDER BY clause, in or case it doesn't matter the order, so I used (SELECT 1).
There is also another way for generating a sequence numbers with out the use of ROW_NUMBER, it also depends on the CROSS JOIN, with an anchor table like:
WITH temp
AS
(
SELECT n
FROM (
VALUES(0), (1), (2), (3), (4), (5), (6), (7), (8), (9)
) temp(n)
), nums
AS
(
SELECT t1.n * 100 + t2.n * 10 + t3.n + 1 AS n
FROM temp t1, temp t2, temp t3
)
SELECT n
FROM nums
ORDER BY n;
Another Way of Generating Sequence Of Numbers
Common Table Expressions:
The CTE is called common table expression, and it was introdeced in SQL Server 2005. It is one of the table expressions types that SQL Server supports. The other three are:
Derived tables,
Views, and
Inline table-valued functions.
It has a lot of important advantages. One of them is let you create a virtual tables that you can reuse them later, like what I did:
WITH temp
AS
(
SELECT n
FROM (VALUES(1), (2), (3), (4)) temp(n)
), nums
AS
(
SELECT ROW_NUMBER() OVER(ORDER BY (SELECT 1)) AS n
FROM temp t1, temp t2, temp t3
)
...
Here I defined two CTE's temp then another one nums that ruse that temp so this OL, you can create multiple CTE's, just put a semicolon, then a new one with the AS clause and two ().
Insert into one table from another table using INSERT INTO ... SELECT ...
Now, we have a virtual table nums having rows from 1 to 64, we need to insert the rows from 1 to 50.
For this, you can use the INSERT INTO ... SELECT ....
Note that the columns in the INSERT clause are optional, but If you do so, you have to put a value for each row, if not you will got an error, for example if you have four columns and you put only three values in the VALUES clause or in the SELECT clause, then you will got an error. This is not valid for the idenetityt columns which are defined with:
Identity(1,1)
^ ^
| |
| ------------------The seed
The start
In this case you simply ignore that column in the columns list in the INSERT clause and it will have the identity value. There is an option that let you insert a value manually like in the #Raj's answer.
Note that: In my answer, I am not inserting the sequence numbers in to that column instead, inserting the values 50 times. But the actual numbers are generating automatically because of the Identity column:
...
INSERT INTO Prison_Doors(DoorOpen, DoorClosed)
SELECT 0 AS DoorOpen, 1 AS DoorClosed
FROM nums;
WHERE n <= 50;
Firstly, you have declared DoorNum as Identity and then you are trying to explicitly insert values into that column. SQL Server does not allow this, unless you choose to
SET IDENTITY_INSERT ON
Try this query. It should insert the 50 rows you want
DECLARE #SQL VARCHAR(8000)
DECLARE #Index INT
SET #Index=1
WHILE (#Index<=50)
BEGIN
SET #SQL= 'INSERT INTO Prison_Doors(DoorOpen,DoorClosed)VALUES(0,1)'
EXEC(#SQL)
SET #Index=#Index+1
END
Raj