PostgreSQL: Refer to specific nth column of WITH created temponary table - sql

Supposing I have a temporary table created using WITH clause as follows:
WITH temporary_table AS (
VALUES ('First', 1),
('Second', 2),
('Third', 3))
Is it possible to refer to the nth column in the SELECT clause? Something like:
WITH temporary_table AS (
VALUES ('First', 1),
('Second', 2),
('Third', 3)
)
SELECT second_column
FROM temporary_table;
If not - is there any other way to make up some temporary table only for query purposes when having read only privileges?
I haven't found anything helpful on WITH or SELECT PostgreSQL docs.

Just give them a name, e.g as part of the WITH clause:
WITH temporary_table (c1, c2) AS (
VALUES ('First', 1),
('Second', 2),
('Third', 3)
)
SELECT c2
FROM temporary_table;
A slightly more complicated way is to name the columns of the VALUES clause:
WITH temporary_table AS (
select *
from (
VALUES ('First', 1),
('Second', 2),
('Third', 3)
) as t(c1, c2)
)
SELECT c2
FROM temporary_table;

Name the columns.
For example:
WITH temporary_table (a, b) AS (
VALUES ('First', 1),
('Second', 2),
('Third', 3)
)
SELECT b
FROM temporary_table;

Related

Get the number of rows per tenant of several tables

Let's say I have 2 tables: Tenants and Wargles. Wargles has a Foreign Key towards Tenants called TenantId. If I want to get number of wargles per tenant, I can do this:
SELECT t.Id as TenantId, count(w.Id) as WargleCount
FROM Tenants t
JOIN Wargles w ON w.TenantId = t.Id
GROUP BY t.Id
Now, let's say I have another table, Fiddles, that, as Wargles has a FK towards Tenants. How can I add another column to the query above, so I get the number of wargles and the number of fiddles for each tenant?
I tried with this:
SELECT t.Id as TenantId, count(w.Id) as WargleCount, count(f.Id) as FiddleCount
FROM Tenants t
JOIN Wargles w ON w.TenantId = t.Id
JOIN Fiddles f ON f.TenantId = t.Id
GROUP BY t.Id
But this won't work, since it would give me the same number both for WargleCount and FiddleCount, the product of the rows from both tables.
Use two subselects
SELECT t.Id as TenantId,
(SELECT Count(1) FROM Fiddles F WHERE F.TenantId = T.Id) as FiddleCount,
(SELECT Count(1) FROM Wargles W WHERE W.TenantId = T.Id) as WargleCount
FROM Tenants t
The most efficient method is probably to use correlated subqueries:
SELECT t.Id as TenantId,
(SELECT COUNT(*)
FROM Wargles w
WHERE w.TenantId = t.Id
) as WargleCount, count(f.Id) as FiddleCount
(SELECT COUNT(*)
FROM Fiddles f
WHERE f.TenantId = t.Id
) as FiddleCount
FROM Tenants t;
In particular, this can take advantage of indexes on Wargles(TenantId) and Fiddles(TenantId).
In your case, as extendable solution, I would recommend Scalar Function usage.
/* SAMPLE DATA ARRANGE */
CREATE TABLE Tenants (Id INT, Title NVARCHAR(5)) ; INSERT INTO Tenants VALUES (1, 'A'), (2, 'B') , (3, 'C');
CREATE TABLE Wargles (Id INT,TenantId INT);INSERT INTO Wargles VALUES (1, 1), (2, 1) , (3, 1) , (4, 2), (5, 2) , (6, 1), (7, 3) , (8, 3);
CREATE TABLE Fiddles (Id INT,TenantId INT);INSERT INTO Fiddles VALUES (1, 1), (2, 1) , (3, 1) , (4, 2), (5, 2) , (6, 2), (7, 3) , (8, 2);
The Function
/*NEEDED CODE*/
CREATE FUNCTION dbo.ufnGetTenantsNo ( #Id AS INT , #Tb AS INT)
RETURNS INT
AS
BEGIN
DECLARE #Result INT = 0;
IF (#TB = 1)
SELECT #Result = COUNT(*)
FROM Wargles
WHERE TenantId = #Id
ELSE
SELECT #Result = COUNT(*)
FROM Fiddles
WHERE TenantId = #Id
RETURN #Result
END
GO
Select Statement
SELECT Id AS TenantId
,dbo.ufnGetTenantsNo(Id, 1) AS WargleCount
,dbo.ufnGetTenantsNo(Id, 2) AS FiddleCount
FROM Tenants

T-SQL Calculate the max value from an Alpha Numeric key

I have a customers table which has an Alphanumeric key consisting of 5 letters and 3 numbers.
I'm trying to calculate the next 3 digit number in sequence for each 5 letters for example:
Example Key
ALPHA001
ALPHA002
NUMBE001
NUMBE002
NUMBE003
PREST001
PREST002
PREST003
PREST004
PREST005
From the list of keys above i'd like to return the maximum of each unique 5 letter key. i.e.
Returned Values
ALPHA002
NUMBE003
PREST005
First of all: Do not store more than one value within one column. should store the key and the running number in separate columns and combine them just for display purpose...
Try this
DECLARE #mockupTable TABLE(ID INT IDENTITY,YourKey VARCHAR(100));
INSERT INTO #mockupTable VALUES
('ALPHA001')
,('ALPHA002')
,('NUMBE001')
,('NUMBE002')
,('NUMBE003')
,('PREST001')
,('PREST002')
,('PREST003')
,('PREST004')
,('PREST005');
WITH cte AS
(
SELECT *
,ROW_NUMBER() OVER(PARTITION BY LEFT(YourKey,5) ORDER BY CAST(RIGHT(YourKey,3) AS INT) DESC) AS PartitionedRowNumber
FROM #mockupTable
)
SELECT *
FROM cte
WHERE PartitionedRowNumber =1;
The result
ID Key
2 ALPHA002
5 NUMBE003
10 PREST005
You can use row_number():
select top (1) with ties t.*
from table t
order by row_number() over (partition by left(col, patindex('%[0-9]%', col)) order by col desc);
If the letters are fixed then just use left() :
order by row_number() over (partition by left(col, 5) order by col desc);
I'm trying to calculate the next 3 digit number in sequence for each 5
letters
This should do it:
SELECT CONCAT(LEFT(k, 5), FORMAT(MAX(RIGHT(k, 3)) + 1, '000'))
FROM (VALUES
('ALPHA001'),
('ALPHA002'),
('NUMBE001'),
('NUMBE002'),
('NUMBE003'),
('PREST001'),
('PREST002'),
('PREST003'),
('PREST004'),
('PREST005')
) tests(k)
GROUP BY LEFT(k, 5)
You can do this with GROUP BY and MAX:
SELECT KeyPrefix = LEFT(ExampleKey, 5),
NextKey = CONCAT(LEFT(ExampleKey, 5),
RIGHT(CONCAT('000', MAX(CONVERT(INT, RIGHT(ExampleKey, 3))) + 1), 3))
FROM (VALUES
('ALPHA001'), ('ALPHA002'), ('NUMBE001'), ('NUMBE002'), ('NUMBE003'),
('PREST001'), ('PREST002'), ('PREST003'), ('PREST004'), ('PREST005')
) t (ExampleKey)
GROUP BY LEFT(ExampleKey, 5);
The key operations being:
Get number part of key: RIGHT(ExampleKey, 3)
Convert this to an integer: CONVERT(INT, <output from 1>)
Find the max for the key type and add 1: MAX(<output from 2>) + 1
Pad this with zeros: RIGHT(CONCAT('000', MAX(<output from 3>), 3)
Concatenate withthe original prefix: CONCAT(LEFT(ExampleKey, 5), <output from 4>)
I would however highly recommed storing this in two columns, and use a computed column to combine then:
CREATE TABLE dbo.T
(
KeyPrefix CHAR(5) NOT NULL,
KeySequence INT NOT NULL,
TKey AS CONCAT(KeyPrefix, RIGHT(CONCAT('000', KeySequence), 3))
);
Then your query becomes much simpler:
SELECT KeyPrefix,
KeySequence = MAX(KeySequence) + 1,
TKey = CONCAT(KeyPrefix, RIGHT(CONCAT('000', MAX(KeySequence) + 1), 3))
FROM (VALUES
('ALPHA', 1), ('ALPHA', 2), ('NUMBE', 1), ('NUMBE', 2), ('NUMBE', 3),
('PREST', 1), ('PREST', 2), ('PREST', 3), ('PREST', 4), ('PREST', 5)
) t (KeyPrefix, KeySequence)
GROUP BY KeyPrefix;
Although worth noting that you would never actually need to reconstruct the key as I have done above in the column TKey, you just need the max keysequence.
Use this query.
GO
;WITH cte AS
(
SELECT ROW_NUMBER() OVER(PARTITION BY LEFT(YourKey,patindex('%[0-9]%', YourKey)) ORDER BY CAST(RIGHT(YourKey,patindex('%[A-Z]%', YourKey)) AS INT) DESC) AS rr , YourKey FROM #mockupTable
)
SELECT YourKey FROM cte WHERE rr =1;
GO

Find and classify sequential patterns for a distinct group T-SQL

I need help finding and classifying sequential patterns for each distinct key.
From the data I have, I need to create a new table that contains the key and a pattern identifier that belongs to that key.
From the example below the pattern is as follows:
Key #1 and #3 have the values 1, 2 and 3. The Key #3 has the values 8,
9 and 10. When a distinct pattern exists for a key I.E (1, 2, 3) I
need to create an entry on the table for the key # and that specific
pattern (1, 2, 3)
Data:
key value
1 1
1 2
1 3
2 8
2 9
2 10
3 1
3 2
3 3
Expected Output:
key pattern
1 1
2 2
3 1
Fiddle:
http://sqlfiddle.com/#!6/4fe39
Example table:
CREATE TABLE yourtable
([key] int, [value] int)
;
INSERT INTO yourtable
([key], [value])
VALUES
(1, 1),
(1, 2),
(1, 3),
(2, 8),
(2, 9),
(2, 10),
(3, 1),
(3, 2),
(3, 3)
;
You can concatenate the values together in several ways. The traditional method in SQL Server uses for xml:
select k.key,
stuff( (select ',' + cast(t.id as varchar(255))
from t
where k.key = t.key
for xml path ('')
order by t.id
), 1, 1, ''
) as ids
from (select distinct key from t) k;
You can convert this to a unique number using a CTE/subquery:
with cte as (
select k.key,
stuff( (select ',' + cast(t.id as varchar(255))
from t
where k.key = t.key
for xml path ('')
order by t.id
), 1, 1, ''
) as ids
from (select distinct key from t) k
)
select cte.*, dense_rank() over (order by ids) as ids_id
from cte;

EXISTS and NOT EXISTS in a correlated subquery

I've been trying to work out how to do a particular query for a day or so now and it has gotten to the point where I need some outside help. Hence my question.
Given the following data;
DECLARE #Data AS TABLE
(
OrgId INT,
ThingId INT
)
DECLARE #ReplacementData AS TABLE
(
OldThingId INT,
NewThingId INT
)
INSERT INTO #Data (OrgId, ThingId)
VALUES (1, 2), (1, 3), (1, 4),
(2, 1), (2, 4),
(3, 3), (3, 4)
INSERT INTO #ReplacementData (OldThingId, NewThingId)
VALUES (3, 4), (2, 5)
I want to find any organisation that has a "thing" that has been replaced as denoted in the #ReplacementData table variable. I'd want to see the org id, the thing it is that they have that has been replaced and the id of the thing that should replace it. So for example given the data above, I should see;
Org id, Thing Id, Replacement Thing Id org doesn't have but should have
1, 2, 5 -- As Org 1 has 2, but not 5
I've had many attempts at trying to get this working, and I just can't seem to get my head around how to go about it. The following are a couple of my attempts, but I think I am just way off;
-- Attempt using correlated subqueries and EXISTS clauses
-- Show all orgs that have the old thing, but not the new thing
-- Ideally, limit results to OrgId, OldThingId and the NewThingId that they should now have too
SELECT *
FROM #Data d
WHERE EXISTS (SELECT *
FROM #Data oldstuff
WHERE oldstuff.OrgId = d.OrgId
AND oldstuff.ThingId IN
(SELECT OldThingID
FROM #ReplacementData))
AND NOT EXISTS (SELECT *
FROM #Data oldstuff
WHERE oldstuff.OrgId = d.OrgId
AND oldstuff.ThingId IN
(SELECT NewThingID
FROM #ReplacementData))
-- Attempt at using a JOIN to only include those old things that the org has (via the where clause)
-- Also try exists to show missing new things.
SELECT *
FROM #Data d
LEFT JOIN #ReplacementData rd ON rd.OldThingId = d.ThingId
WHERE NOT EXISTS (
SELECT *
FROM #Data dta
INNER JOIN #ReplacementData rep ON rep.NewThingId = dta.ThingId
WHERE dta.OrgId = d.OrgId
)
AND rd.OldThingId IS NOT NULL
Any help on this is much appreciated. I may well be going about it completely wrong, so please let me know if there is a better way of tackling this type of problem.
Try this out and let me know.
DECLARE #Data AS TABLE
(
OrgId INT,
ThingId INT
)
DECLARE #ReplacementData AS TABLE
(
OldThingId INT,
NewThingId INT
)
INSERT INTO #Data (OrgId, ThingId)
VALUES (1, 2), (1, 3), (1, 4),
(2, 1), (2, 4),
(3, 3), (3, 4)
INSERT INTO #ReplacementData (OldThingId, NewThingId)
VALUES (3, 4), (2, 5)
SELECT D.OrgId, RD.*
FROM #Data D
JOIN #ReplacementData RD
ON D.ThingId=RD.OldThingId
LEFT OUTER JOIN #Data EXCLUDE
ON D.OrgId = EXCLUDE.OrgId
AND RD.NewThingId = EXCLUDE.ThingId
WHERE EXCLUDE.OrgId IS NULL

Select Records that match ALL groups in a many to many join table

I have 2 tables: sets and groups. Both are joined using a 3rd table set_has_groups.
I would like to get sets that have ALL groups that I specify
One way of doing it would be
SELECT column1, column2 FROM sets WHERE
id IN(SELECT set_id FROM set_has_group WHERE group_id = 1)
AND id IN(SELECT set_id FROM set_has_group WHERE group_id = 2)
AND id IN(SELECT set_id FROM set_has_group WHERE group_id = 3)
obviously this is not the most beautiful solution
I've also tried this:
SELECT column1, column2 FROM sets WHERE
id IN(SELECT set_id FROM set_has_group WHERE group_id IN(1,2,3) GROUP BY group_id
HAVING COUNT(*) = 3
This looks prettier but the problem is that it takes forever to execute.
While the first query runs in like 200ms the 2nd one takes more than 1 minute.
Any idea why that is?
===UPDATE:
I've played with this some more and I modified the 2nd query like this
SELECT columns FROM `set` WHERE id IN(
select set_id FROM
(
SELECT set_id FROM set_has_group
WHERE group_id IN(1,2,3)
GROUP BY set_id HAVING COUNT(*) = 3
) as temp
)
that is really fast
It's the same as the 2nd query before just that I wrap it in another temporary table
Pretty strange
I am suspecting a small mistyping in the second query.
Really, I am not sure. Probably, the second query is executed via full table scan. At the same time the first one "IN" is really transformed into "EXISTS". So, you can try to use "exists". For example:
...
where 3 = (select count(*) from set_has_group
where group_id in (1, 2, 3) and set_id = id
group by set_id)
Assuming SQL Server, here is a working example with a JOIN that should work better than the IN clauses you are using as long as you have your primary and foreign keys set correctly. I have built joined 5 sets to 3 groups, but set 4 and 5 are not a part of group 3 and will not show in the answer. However, this query is not scalable (for ex. find in group 4, 5, 7, 8 and 13 will require code modifications unless you parse input params into a table variable)
set nocount on
declare #sets table
(
Id INT Identity (1, 1),
Column1 VarChar (50),
Column2 VarChar (50)
)
declare #Set_Has_Group table
(
Set_Id Int,
Group_Id Int
)
insert into #sets values (newid(), newid())
insert into #sets values (newid(), newid())
insert into #sets values (newid(), newid())
insert into #sets values (newid(), newid())
insert into #sets values (newid(), newid())
update #sets set column1 = 'Column1 at Row ' + Convert (varchar, id)
update #sets set column2 = 'Column2 at Row ' + Convert (varchar, id)
insert into #Set_Has_Group values (1, 1)
insert into #Set_Has_Group values (1, 2)
insert into #Set_Has_Group values (1, 3)
insert into #Set_Has_Group values (2, 1)
insert into #Set_Has_Group values (2, 2)
insert into #Set_Has_Group values (2, 3)
insert into #Set_Has_Group values (3, 1)
insert into #Set_Has_Group values (3, 2)
insert into #Set_Has_Group values (3, 3)
insert into #Set_Has_Group values (4, 1)
insert into #Set_Has_Group values (4, 2)
insert into #Set_Has_Group values (5, 1)
insert into #Set_Has_Group values (5, 2)
/* your query with IN */
SELECT column1, column2 FROM #sets WHERE
id IN(SELECT set_id FROM #set_has_group WHERE group_id = 1)
AND id IN(SELECT set_id FROM #set_has_group WHERE group_id = 2)
AND id IN(SELECT set_id FROM #set_has_group WHERE group_id = 3)
/* my query with JOIN */
SELECT * -- Column1, Column2
FROM #sets sets
WHERE 3 = (
SELECT Count (1)
FROM #Set_Has_Group Set_Has_Group
WHERE 1=1
AND sets.Id = Set_Has_Group.Set_Id
AND Set_Has_Group.Group_ID IN (1, 2, 3)
Group by Set_Id
)
Here's a solution that uses a non-correlated subquery and no GROUP BY:
SELECT column1, column2
FROM sets
WHERE id IN (
SELECT g1.set_id FROM set_has_group g1
JOIN set_has_group g2 ON (g1.set_id = g3.set_id)
JOIN set_has_group g3 ON (g1.set_id = g3.set_id)
WHERE g1.group_id = 1 AND g2.group_id = 2 AND g3.group_id = 3);