Specific SQL query for invariant locales - sql

I have a table with the same descriptions on many languages for one object. I need to construct a select query to get one needed description having user locale, invariant locale, and if any of the locales will not match with data in the table get one random record.
+---+----+--------+--------+-------------+
| | id | locale | object | description |
+---+----+--------+--------+-------------+
| 1 | 1 | it | cat | gatto |
| 2 | 2 | pl | cat | kot |
| 3 | 3 | de | cat | Katze |
| 4 | 4 | en | cat | cat |
+---+----+--------+--------+-------------+
If user locale is 'it' and the default is 'en', must return -> gatto;
If user locale is 'ru' and the default is 'en', must return -> cat;
If user locale is 'ru' and the default is 'po', must return -> any record;
Do you know any variant of query which can do that thing?

You could use coalesce() and conditional aggregation as follows:
select
coalesce(
max(case when locale = :user_locale then description end),
max(case when locale = :default_locale then description end)
)
from mytable
where object = 'cat'
Where :user_locale is the parameter that contains the user locale and :default_locale contains the default locale.

First thing you should do is split the table in three entity to avoid redondance in the column "object" AND to avoid violating 3rd normal form (with column "object" and "description" being in functional dependence with locale wich is not the key).
So you will have the following table :
Locale(id_locale, locale)
-->id_locale is primary key
Object(id_object, code)
--> id_object is primary key
Locale_Tran(id_locale,id_object, description)
--> (id_locale, id_object) is composed primary key
--> id_locale is foreign key references on Locale(id_locale)
--> id_object is foreign key references on Object(id_object)
(I choose the name for the table but of course you can adapt it to your actual model)
CREATE DATABASE TEST
USE TEST
CREATE TABLE [LOCALE]
(
id_locale INT IDENTITY(1,1),
locale CHAR(2),
CONSTRAINT pk_locale PRIMARY KEY(id_locale)
);
CREATE TABLE [USER]
(
id INT IDENTITY(1,1),
locale INT NOT NULL,
CONSTRAINT fk_user_locale FOREIGN KEY(locale) REFERENCES LOCALE(id_locale)
);
CREATE TABLE [OBJECT]
(
id_object INT IDENTITY(1,1),
code VARCHAR(100),
CONSTRAINT pk_object PRIMARY KEY(id_object)
);
CREATE TABLE [LOCALE_TRAN]
(
id_locale INT,
id_object INT,
[description] VARCHAR(200) NOT NULL,
CONSTRAINT fk_tran_locale FOREIGN KEY(id_locale) REFERENCES [LOCALE](id_locale),
CONSTRAINT fk_locale_object FOREIGN KEY(id_object) REFERENCES [OBJECT](id_object),
CONSTRAINT pk_locale_tran PRIMARY KEY(id_locale, id_object)
)
INSERT INTO LOCALE
(locale)
VALUES
('IT'),
('FR'),
('PL'),
('DE'),
('EN'),
('RU');
INSERT INTO [USER]
(locale)
VALUES
(2),
(3),
(4),
(5),
(6),
(6),
(1)
INSERT INTO [OBJECT]
(code)
VALUES
('CAT'),
('FISH');
INSERT INTO [LOCALE_TRAN]
VALUES
(1,1,'gatto'),
(2,1,'chat'),
(3,1,'kot'),
(4,1,'katze'),
(5,1,'cat'),
(1,2,'pesce')
Here is the query :
DROP PROCEDURE IF EXISTS dbo.getTran
GO;
CREATE PROCEDURE dbo.getTran
(
#userLocale CHAR(2),
#defaultLocale CHAR(2),
#objectId INT
)
AS
SELECT #userLocale = (SELECT id_locale FROM LOCALE l WHERE l.locale = #userLocale);
SELECT #defaultLocale = (SELECT id_locale FROM LOCALE l WHERE l.locale = #defaultLocale);
SELECT
coalesce(
max(case when id_locale = #userLocale then [description] end),
max(case when id_locale = #defaultLocale then [description] end)
)
FROM
(
SELECT lt.id_locale, lt.[description]
FROM [LOCALE_TRAN] lt WHERE lt.id_object = #objectId
) AS t
GO;
EXEC dbo.getTran 'FR','EN', '1'
I don't really found how to have the random locale and i didn't really understand why you wanna do that... But i will try.

Apply your conditions inside CTEs with the use of NOT EXISTS:
with
cte1 as (select description from tablename where locale = ParamLocale),
cte2 as (
select t.description from tablename t
where t.locale = ParamDefault
and not exists (select 1 from cte1)
),
cte3 as (
select * from cte1
union all
select * from cte2
)
select description from cte3
union all
select * from (
select description from tablename
where not exists (select 1 from cte3)
order by random() limit 1
)
Change ParamLocale and ParamDefault with your parameters.
See the demo.

Related

TSQL group by and combine remaining columns in json array

I want to group a result set by a column and combine the remaining columns into a json array, but I'm not sure how to aggregate the results for this.
I want the following output:
A_ID | Translations
--------------------
1 | [{"Name": "english_1","LCID": "en-gb"},{"Name": "french_1","LCID": "fr-fr"}]
2 | [{"Name": "english_2","LCID": "en-gb"},{"Name": "french_2","LCID": "fr-fr"}]
But I cannot group the results by A_ID without an aggregator so I get the following
A_ID | Translations
--------------------
1 | [{"Name": "english_1","LCID": "en-gb"}]
1 | [{"Name": "french_1","LCID": "fr-fr"}]
2 | [{"Name": "english_2","LCID": "en-gb"}]
2 | [{"Name": "french_2","LCID": "fr-fr"}]
Here is an example:
DROP TABLE IF EXISTS #tabA;
DROP TABLE IF EXISTS #tabB;
DROP TABLE IF EXISTS #tabC;
go
CREATE TABLE #tabA
(
Id int not null
);
CREATE TABLE #tabTranslations
(
translationId int not null,
Name nvarchar(32) not null,
aId int not null, -- Foreign key.
languageId int not null --Foreign key
);
CREATE TABLE #tabLanguages
(
languageId int not null,
LCID nvarchar(32) not null
);
go
INSERT INTO #tabA (Id)
VALUES
(1),
(2);
INSERT INTO #tabTranslations (translationId, Name, aId, languageId)
VALUES
(1, 'english_1', 1, 1),
(2, 'french_1', 1, 2),
(3, 'english_2', 2, 1),
(4, 'french_2', 2, 2);
INSERT INTO #tabLanguages (languageId, LCID)
VALUES
(1, 'en-gb'),
(2, 'fr-fr');
go
select
_a.Id as A_ID,
(
select
_translation.Name,
_language.LCID
for json path
)
from #tabA as _a
inner join #tabTranslations as _translation ON _translation.aId = _a.Id
inner join #tabLanguages as _language ON _language.languageId = _translation.languageId
-- group by _a.Id ??
;
go
DROP TABLE IF EXISTS #tabA;
DROP TABLE IF EXISTS #tabTranslations;
DROP TABLE IF EXISTS #tabLanguages;
go
Alternative solution:
I know I can do this, but I obviously don't want to hard code the available LCIDs (maybe I could generate the sql query and exec it? but this feels too complex), also I would prefer an array
select
_a.Id as A_ID,
(
SELECT
MAX(CASE WHEN [LCID] = 'en-gb' THEN [Name] END) 'en-gb',
MAX(CASE WHEN [LCID] = 'fr-fr' THEN [Name] END) 'fr-fr'
FOR JSON PATH, WITHOUT_ARRAY_WRAPPER
) as b
from #tabA as _a
inner join #tabTranslations as _translation ON _translation.aId = _a.Id
inner join #tabLanguages as _language ON _language.languageId = _translation.languageId
group by _a.Id;
result:
A_ID | Translations
--------------------
1 | { "en-Gb": "english_1", "fr-FR": "french_1"}
2 | { "en-Gb": "english_2", "fr-FR": "french_2"}
If I understand you correctly, next approach may help. Use additional CROSS APPLY operator and FOR JSON PATH to get your expected results:
Statement:
SELECT *
FROM #tabA AS t
CROSS APPLY (
SELECT _translation.Name AS Name, _language.LCID AS LCID
FROM #tabA _a
inner join #tabTranslations as _translation ON _translation.aId = _a.Id
inner join #tabLanguages as _language ON _language.languageId = _translation.languageId
WHERE _a.Id = t.Id
for json path
) _c(Translations)
Output:
Id Translations
1 [{"Name":"english_1","LCID":"en-gb"},{"Name":"french_1","LCID":"fr-fr"}]
2 [{"Name":"english_2","LCID":"en-gb"},{"Name":"french_2","LCID":"fr-fr"}]

Can I use OUTPUT INTO to add data to a relational table with additional values?

I have two tables. One holds common data for articles, and the other holds translations for text. Something like this:
Articles Table
id | key | date
Translations Table
id | article_key | lang | title | content
key is a string and is the primary key.
article_key is a foreign key relating it to articles on the key column.
When I add a new row to the Articles, I'd like to be able to use the key that was just inserted and add a new row to the Translations Table.
I've read about OUTPUT INTO but it doesn't seem like I can add other values to the Translations table. Also I get an error about it being on either side of a relationship.
Is my only course of action to INSERT into Articles followed by an INSERT with a SELECT subquery to get the key?
Edit: Expected output would be something like:
Articles
id | key | date
---------------
1 | somekey | 2018-05-31
Article Translations
id | article_key | lang | title | content
-----------------------------------------
1 | somekey | en | lorem | ipsum
Well this could work based on your description:
SET NOCOUNT ON;
DECLARE #Articles TABLE (id INT NOT NULL
, [key] VARCHAR(50) NOT NULL
, [date] DATE NOT NULL);
DECLARE #ArticleTranslations TABLE (id INT NOT NULL
, article_key VARCHAR(50) NOT NULL
, lang VARCHAR(50) NOT NULL
, title VARCHAR(50) NOT NULL
, content VARCHAR(50) NOT NULL);
INSERT #Articles (id, [key], [date]) -- This is insert into #Articles
OUTPUT INSERTED.id, INSERTED.[key], 'en', 'lorem', 'ipsum' -- This is insert into #ArticleTranslations
INTO #ArticleTranslations (id, article_key, lang, title, content) -- This is insert into #ArticleTranslations
VALUES (1, 'somekey', GETDATE()); -- This is insert into #Articles
SELECT *
FROM #Articles;
SELECT *
FROM #ArticleTranslations;
Try this out Stack Exchange: https://data.stackexchange.com/stackoverflow/query/857925
Maybe it's not that simple as it is. So let me know whether this works or not.

How to match rows in the same table across schemas by using foreign key restraints

Postgres version info: "PostgreSQL 9.1.14 on x86_64-unknown-linux-gnu, compiled by gcc (GCC) 4.4.7 20120313 (Red Hat 4.4.7-4), 64-bit"
I have a table T with columns Id, Field1, Field2 in two different schemas (though that's not important), S and S'. The table T has a reference table F that contains the fields Id, Field1 with Id being a foreign key to table T.
CREATE TABLE S.T
(
Id character varying(36) NOT NULL,
FieldA character varying(32) NOT NULL,
FieldB character varying(250),
CONSTRAINT pk_T PRIMARY KEY (Id)
)
CREATE TABLE S.F
(
Id character varying(36) NOT NULL,
Field1 character varying(36) NOT NULL,
CONSTRAINT pk_F PRIMARY KEY (Id, Field1),
CONSTRAINT fk_F_T FOREIGN KEY (Id)
REFERENCES S.T(Id) MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE NO ACTION
)
Similarly in schema S' we have tables T' and F'. Note that there is a 1..n relationship between T and F, that is, multiple rows in F with the same Id.
I want to create a query that will match rows between T and T' based on the Field1 values in F.
Example in Schema S:
T.Id | T.FieldA | T.FieldB F.Id | F.Field1
-------------------------- ---------------
1 | A | B 1 | XYZ
2 | C | D 1 | WVU
2 | STR
2 | PQR
Example in Schema S':
T'.Id | T'.FieldA | T'.FieldB F'.Id | F'.Field1
-------------------------- ---------------
1' | A' | B' 1' | XYZ
2' | C' | D' 1' | WVU
2' | STR
2' | PQR
How do I make a query matches row 1 from T with row 1' from T' since they have the same foreign key info XYZ and WVU, and similarly row 2 from T with row 2' from T'? In other words rows are uniquely defined by their matching rows in F.
Of course, they query isn't as simple as the above: Rows are matched between T and T' by the info in F (and F', respectively) and T.FieldA and then with a successful match I will need to update T'.FieldB.
Thanking you in anticipation.
your example data - I have renamed some things to make life easier.
create schema a; create schema b;
create table a.t (id int primary key ,a text,b text);
insert into a.t values(1,'A','B'),(2,'C','D');
create table a.f (id int references a.t(id),field1 text);
insert into a.f values (1,'XYZ'),(1,'WVU'),(2,'STR'),(2,'PQR');
create table b.t (id int primary key ,a text,b text);
insert into b.t values(11,'A''','B'''),(22,'C''','D''');
create table b.f (id int references b.t(id),field1 text);
insert into b.f values (11,'XYZ'),(11,'WVU'),(22,'STR'),(22,'PQR');
the join:
SELECT * FROM a.t
JOIN a.f ON a.t.id = a.f.id
JOIN b.f ON a.f.field1 = b.f.field1
JOIN b.t ON b.t.id = b.f.id
an update?:
UPDATE b.t
SET b=b.t.b||'('||a.t.id||')'
FROM a.f
JOIN b.f ON a.f.field1 = b.f.field1
JOIN a.t ON a.t.id = a.f.id
WHERE b.t.id = b.f.id
;
the cleanup
drop schema a cascade;drop schema b cascade;

SQL Server, Merge two records in one record

We have these tables
CREATE TABLE tbl01
(
[id] int NOT NULL PRIMARY KEY,
[name] nvarchar(50) NOT NULL
)
CREATE TABLE tbl02
(
[subId] int NOT NULL PRIMARY KEY ,
[id] int NOT NULL REFERENCES tbl01(id),
[val] nvarchar(50) NULL,
[code] int NULL
)
If we run this query:
SELECT
tbl01.id, tbl01.name, tbl02.val, tbl02.code
FROM
tbl01
INNER JOIN
tbl02 ON tbl01.id = tbl02.id
we get these results:
-------------------------------
id | name | val | code
-------------------------------
1 | one | FirstVal | 1
1 | one | SecondVal | 2
2 | two | YourVal | 1
2 | two | OurVal | 2
3 | three | NotVal | 1
3 | three | ThisVal | 2
-------------------------------
You can see that each two rows are related to same "id"
The question is: we need for each id to retrieve one record with all val, each val will return in column according to the value of column code
if(code = 1) then val as val-1
else if (code = 2) then val as val-2
Like this:
-------------------------------
id | name | val-1 | val-2
-------------------------------
1 | one | FirstVal | SecondVal
2 | two | YourVal | OurVal
3 | three | NotVal | ThisVal
-------------------------------
Any advice?
Use can use MAX and Group By to achieve this
SELECT id,
name,
MAX([val1]) [val-1],
MAX([val2]) [val-2]
FROM ( SELECT tbl01.id, tbl01.name,
CASE code
WHEN 1 THEN tbl02.val
ELSE ''
END [val1],
CASE code
WHEN 2 THEN tbl02.val
ELSE ''
END [val2]
FROM tbl01
INNER JOIN tbl02 ON tbl01.id = tbl02.id
) Tbl
GROUP BY id, name
Is it the PIVOT operator (http://technet.microsoft.com/en-us/library/ms177410(v=sql.105).aspx) that you are looking for?
You've already got a few answers, but heres one using PIVOT as an alternative. The good thing is this approach is easy to scale if there are additional columns required later
-- SETUP TABLES
DECLARE #t1 TABLE (
[id] int NOT NULL PRIMARY KEY,
[name] nvarchar(50) NOT NULL
)
DECLARE #t2 TABLE(
[subId] int NOT NULL PRIMARY KEY ,
[id] int NOT NULL,
[val] nvarchar(50) NULL,
[code] int NULL
)
-- SAMPLE DATA
INSERT #t1 ( id, name )
VALUES ( 1, 'one'), (2, 'two'), (3, 'three')
INSERT #t2
( subId, id, val, code )
VALUES ( 1,1,'FirstVal', 1), ( 2,1,'SecondVal', 2)
,( 3,2,'YourVal', 1), ( 4,2,'OurVal', 2)
,( 5,3,'NotVal', 1), ( 6,3,'ThisVal', 2)
-- SELECT (using PIVOT)
SELECT id, name, [1] AS 'val-1', [2] AS 'val-2'
FROM
(
SELECT t2.id, t1.name, t2.val, t2.code
FROM #t1 AS t1 JOIN #t2 AS t2 ON t2.id = t1.id
) AS src
PIVOT
(
MIN(val)
FOR code IN ([1], [2])
) AS pvt
results:
id name val-1 val-2
---------------------------------
1 one FirstVal SecondVal
2 two YourVal OurVal
3 three NotVal ThisVal
If there are always only two values, you could join them or even easier, group them:
SELECT tbl01.id as id, Min(tbl01.name) as name, MIN(tbl02.val) as val-1, MAX(tbl02.val) as val-2
FROM tbl01
INNER JOIN tbl02 ON tbl01.id = tbl02.id
GROUP BY tbl02.id
note: this query will always put the lowest value in the first column and highest in the second, if this is not wanted: use the join query:
Join query
If you always want code 1 in the first column and code 2 in the second:
SELECT tbl01.id as id, tbl01.name as name, tbl02.val as val-1, tbl03.val as val-2
FROM tbl01
INNER JOIN tbl02 ON tbl01.id = tbl02.id
ON tbl02.code = 1
INNER JOIN tbl03 ON tbl01.id = tbl03.id
ON tbl03.code = 2
Variable amount of columns
You cannot get an variable amount of columns, only when you do this by building your query in code or t-sql stored procedures.
My advice:
If its always to values: join them in query, if not, let your server-side code transform the data. (or even better, find a way which makes it not nessecery to transform data)
Try this - it uses a pivot function but it also creates creates the dynamic columns dependent on code
DECLARE #ColumnString varchar(200)
DECLARE #sql varchar(1000)
CREATE TABLE #ColumnValue
(
Value varchar(500)
)
INSERT INTO #ColumnValue (Value)
SELECT DISTINCT '[' + 'value' + Convert(Varchar(20),ROW_NUMBER() Over(Partition by id Order by id )) + ']'
FROM Test
SELECT #ColumnString = COALESCE(#ColumnString + ',', '') + Value
FROM #ColumnValue
Drop table #ColumnValue
SET #sql =
'
SELECT *
FROM
(
SELECT
id,name,val,''value'' + Convert(Varchar(20),ROW_NUMBER() Over(Partition by id Order by id ))as [values]
FROM Test
) AS P
PIVOT
(
MAX(val) FOR [values] IN ('+#ColumnString+')
) AS pv
'
--print #sql
EXEC (#sql)

Implementing an Aliases table (self-referencing many-to-many)

I am trying to model an Alias relationship. That is, several records in my person table may represent the same actual person. I don't care who the "Primary" person is. All Person records would carry equal weight.
I have implemented this in the past with the two tables you see below.
------------- ------------
| Person | | Alias |
|-----------| |----------|
| PersonID | | AliasID |
| LastName | | PersonID |
| FirstName | ------------
-------------
Here is some sample data:
Person (1, 'Joseph', 'Smith')
Person (2, 'Jane', 'Doe')
Person (3, 'Joe', 'Smith')
Person (4, 'Joey', 'Smith')
Alias(1, 1)
Alias(1, 3)
Alias(1, 4)
I suppose I could move the AliasID to the Person table since there is a 1-to-1 relationship between the PersonID fields. However, I may want to add additional fields to the Alias table (like Sequence number, etc.) at some point in the future.
Is there a better way to model this than what I have here?
This is how I would do it.
--DROP TABLE [dbo].[Alias]
GO
--DROP TABLE [dbo].[RealPerson]
GO
IF EXISTS (SELECT * FROM dbo.sysobjects WHERE id = object_id(N'[dbo].[RealPerson]') and OBJECTPROPERTY(id, N'IsUserTable') = 1)
BEGIN
DROP TABLE [dbo].[RealPerson]
END
GO
CREATE TABLE [dbo].[RealPerson]
(
RealPersonUUID [UNIQUEIDENTIFIER] NOT NULL DEFAULT NEWSEQUENTIALID()
, CreateDate smalldatetime default CURRENT_TIMESTAMP
, MyCompanyFriendlyUniqueIdentifier varchar(128) not null
)
GO
ALTER TABLE dbo.RealPerson ADD CONSTRAINT PK_RealPerson
PRIMARY KEY NONCLUSTERED (RealPersonUUID)
GO
ALTER TABLE [dbo].[RealPerson]
ADD CONSTRAINT CK_MyCompanyFriendlyUniqueIdentifier_Unique UNIQUE (MyCompanyFriendlyUniqueIdentifier)
GO
GRANT SELECT , INSERT, UPDATE, DELETE ON [dbo].[RealPerson] TO public
GO
IF EXISTS (SELECT * FROM dbo.sysobjects WHERE id = object_id(N'[dbo].[Alias]') and OBJECTPROPERTY(id, N'IsUserTable') = 1)
BEGIN
DROP TABLE [dbo].[Alias]
END
GO
CREATE TABLE [dbo].[Alias]
(
AliasUUID [UNIQUEIDENTIFIER] NOT NULL DEFAULT NEWSEQUENTIALID()
, RealPersonUUID [UNIQUEIDENTIFIER] NOT NULL
, CreateDate smalldatetime default CURRENT_TIMESTAMP
, LastName varchar(128) not null
, FirstName varchar(128) not null
, PriorityRank smallint not null
)
GO
ALTER TABLE dbo.Alias ADD CONSTRAINT PK_Alias
PRIMARY KEY NONCLUSTERED (AliasUUID)
GO
ALTER TABLE [dbo].[Alias]
ADD CONSTRAINT FK_AliasToRealPerson
FOREIGN KEY (RealPersonUUID) REFERENCES dbo.RealPerson (RealPersonUUID)
GO
ALTER TABLE [dbo].[Alias]
ADD CONSTRAINT CK_RealPersonUUID_PriorityRank_Unique UNIQUE (RealPersonUUID,PriorityRank)
GO
ALTER TABLE [dbo].[Alias]
ADD CONSTRAINT CK_PriorityRank_Range CHECK (PriorityRank >= 0 AND PriorityRank < 33)
GO
if exists (select * from dbo.sysindexes where name = N'IX_Alias_RealPersonUUID' and id = object_id(N'[dbo].[Alias]'))
DROP INDEX [dbo].[Alias].[IX_Alias_RealPersonUUID]
GO
CREATE INDEX [IX_Alias_RealPersonUUID] ON [dbo].[Alias]([RealPersonUUID])
GO
GRANT SELECT , INSERT, UPDATE, DELETE ON [dbo].[Alias] TO public
GO
INSERT INTO dbo.RealPerson ( RealPersonUUID , MyCompanyFriendlyUniqueIdentifier )
select '11111111-1111-1111-1111-111111111111' , 'ABC'
union all select '22222222-2222-2222-2222-222222222222' , 'DEF'
INSERT INTO dbo.[Alias] ( RealPersonUUID , LastName, FirstName , PriorityRank)
select '11111111-1111-1111-1111-111111111111' , 'Smith' , 'Joseph' , 0
union all select '11111111-1111-1111-1111-111111111111' , 'Smith' , 'Joey' , 1
union all select '11111111-1111-1111-1111-111111111111' , 'Smith' , 'Joe' , 2
union all select '11111111-1111-1111-1111-111111111111' , 'Smith' , 'Jo' , 3
union all select '22222222-2222-2222-2222-222222222222' , 'Doe' , 'Jane' , 0
select 'Main Identity' as X, * from dbo.RealPerson rp join dbo.[Alias] al on rp.RealPersonUUID = al.RealPersonUUID where al.PriorityRank = 0
select 'All Identities' as X, * from dbo.RealPerson rp join dbo.[Alias] al on rp.RealPersonUUID = al.RealPersonUUID
select 'Aliai Only' as X, * from dbo.RealPerson rp join dbo.[Alias] al on rp.RealPersonUUID = al.RealPersonUUID where al.PriorityRank > 0
First, you should identify your entities. Clearly you have a person and each person will have their own identity. They are unique and should allways be kept as such. Then you have Alias's They should be in their own table with a one to many relationship. This should be enfrced with primary keys, forgien keys, indexes for quick lookup where appropriate. Each table need a clustered index also for performance. You should then use stored procedures to return or update the tables. I've intentionally used certain word, because if you google them, you will get lots of good information on what you need to do.