Страницы

Поиск по вопросам

Показаны сообщения с ярлыком sql-server. Показать все сообщения
Показаны сообщения с ярлыком sql-server. Показать все сообщения

понедельник, 13 апреля 2020 г.

Exception handling

#c_sharp #sql_server

                    
Есть программа для работы с базой данных на локальном компьютере. Есть проект Data
access layer`а и клиентского приложения. В данном клиентском приложении я ловлю исключения
прямо в обработчику нажатия кнопки, при вызове методов из DAL. Правильно ли так делать?
Если нет, то как и где  правильно это делать? Какие есть правило хорошего тона ловли
исключений?  Покидайте хорошие статьи если есть таковы. Заранее благодарен за помощь.
UPD------
Кстати,  в MVC исключения, связанные с некорректными входящими данными, допустим,
на текстбокс, правильно ловить во View?    


Ответы

Ответ 1



Общие советы: Вынесите логику (в том числе обработку исключений) из обработчиков событий в отдельные методы, в обработчике оставьте только вызов метода (Разделение UI и логики, повторное использование кода). Не перехватывайте все исключения. Обычно всё, что мы можем сделать с неизвестным исключением - записать его в лог. Поэтому вместо перехвата исключений в каждом методе, лучше сделать общий обработчик, например, так Application.ThreadException - событие (для WinForms) Обработайте ожидаемые исключения. Например, при соединении с базой вполне возможно, что SQL Server недоступен. Отличная статья на CodeProject по обработке исключений (на английском).

Ответ 2



Ловить обрабатывать исключения нужно на том же уровне абстракции, где оно происходит. С другой стороны, если нужно пробросить исключение выше, то ему нужно изменить уровень абстракции и сделать более общим, а потом сделать исходное как inner. В вашем случае получается что уровень представления работает с исключениями с уровня данных. Например, подход MVC для этого делает контроллеры, которые отвечают за правильное обеспечение данных представлению. В свою очередь, у моделей делают специальные сервисы, а это сервисы уже взаимодействуют с хранилищем. Так вот доступ к хранилищу происходит через общий интерфейс, который декларирует какие исключения могут происходить. Как только они происходят, контроллер ловит их и упаковывает в исключения на своем уровне абстракции. По поводу статей не подскажу, но вообще это изучается в курсах Software Design, handling exceptions. Знаю что это действительно важный вопрос и его стоит задавать на раннем этапе проектирования.

Запросы к базе данных MSSQL в высоконагруженном приложении

#c_sharp #sql_server

                    
Хочу создать static class для работы с БД, содержащий методы для получения данных.
Но дело в том, что обращения к методам класса могут происходить из разных потоков.
Вопрос. Как поведет себя подобный класс при одновременном обращении к одному из методов
из двух разных потоков? (Будет ли он ждать, пока завершится первый запрос, произойдет
исключение или запрос будет выполнятся в другом потоке?)
Точнее: Как правильно организовать запросы к БД осуществляя их из разных потоков?    


Ответы

Ответ 1



Заложить в подобной задаче статический класс, как фундамент работы с БД, означает отказаться от ООП и перейти к процедурному программированию, постепенно наращивая "мусорник", в лучшем случае разделенный на "хелпер" классы, "датасорсы" или еще чего. Более того, "контекст" БД обычно НЕ потокобезопасный, как и статика, и вам придется постоянно заботиться о синхронизации. Гораздо эффективней применить какую-либо ORM для работы с БД + (UnitOfWork + Repository + Specification). В данном подходе архитектура станет к тому же и тестируемой, чего не скажешь про сплошные статические классы и методы, протестировать которые можно только при помощи дополнительного кода в виде "врапперов". Для управления жизненным циклом объектов имеет смысл использовать один из существующих решений DI/IoC. Например в вашем случае можно на этапе настройки указать жизненный цикл для контекста БД "PerThread" и "контейнер" будет сам следить, чтобы в каждом потоке был свой, новый контекст или одна транзакция.

Где лучше хранить двоичные данные: в БД или отдельном файле? [ASP.NET MVC 3]

#c_sharp #net #sql_server #aspnet_mvc

                    
Доброго времени суток.
Есть база данных в которой хранятся учётные записи пользователей. К каждому пользователю
привязаны два двоичных файла. Первый файл занимает около 30КБ, второй занимает около
300КБ. Со временем количество пользователей может исчисляться сотнями тысяч.
Первый вариант - хранить эти данные в базе данных. Поле Data в таблице будет иметь
тип varbinary(MAX):
public ActionResult GetWorld()
{
    var world = db.GetWorld(...);

    return File(world.Data, "application/octet-stream");
}

public ActionResult SaveWorld(HttpPostedFileBase worldData)
{
    var world = db.GetWorld(...);

    world.Data = new byte[worldData.ContentLength];
    worldData.InputStream.Read(world.Data, 0, worldData.ContentLength);
}

Второй вариант - хранить эти данные в виде отдельных файлов:
public ActionResult GetWorld()
{
    var world = db.GetWorld(...);

    string pathToWorldData = Server.MapPath(
        string.Format("~/App_Data/Worlds/{0}.dat", world.Id));

    return File(pathToWorldData, "application/octet-stream");
}

public ActionResult SaveWorld(HttpPostedFileBase worldData)
{
    var world = db.GetWorld(...);

    string pathToWorldData = Server.MapPath(
        string.Format("~/App_Data/Worlds/{0}.dat", world.Id));

    worldData.SaveAs(pathToWorldData);
}

Есть ещё другой вариант - varbinary(MAX) и FILESTREAM, но чем он по сути отличается
от второго? Только будет много возни в коде.    


Ответы

Ответ 1



Если хранить данные как поле таблицы, база будет пухнуть. Использование опции FILESTREAM позволяет хранить данные как отдельные файлы, но при этом с поддержкой транзакционности. Правда, лог базы данных всё равно будет пухнуть, его надо будет чистить периодически. Теперь о проблемах. FILESTREAM работает только с Windows-аутентификацией при обращении к БД. Возможно, я чего-то не нашёл, но по-другому у меня не получилось. В сочетании с тем, что диспетчер служб Windows любит терять пароли после пары перезагрузок, работать с этой опцией становится совсем весело. Ещё у меня была такая фигня, что после установки какого-то из обновлений Windows её было не включить. С учётом того, что Microsoft любят добавлять всё время новые возможности своих продуктов, не доводя до ума старые, используйте лучше файлы. Если так надо сделать поддержку транзакционности, посмотрите в сторону распределённых транзакций.

Ответ 2



Бинарные данные лучше хранить в файлах. Картинки, видео и музыку никто не хранит в БД. Не стоит лишний раз все усложнять. Для хранения большого количества файлов обычно создают множество подпапок, например можно хранить файлы для первой тысячи записей в папке /0/, для второй в /1/ и т. д.

Ответ 3



ну если есть деньги, то в БД, т.к. 500 мегабайт стандартно вроде, дальше уже доп $$$ а также из базы дольше дергаться будет, факт поле надо выбирать не varchar точно, раз ты байты будешь там хранить, лучше уж varbinary (кстати, для xml файлов есть тип xml в 2008) я бы посоветовал только путь к файлам хранить в БД, а файл дергать из файловой системы

суббота, 11 апреля 2020 г.

T-SQL: Помогите оптимизировать запрос

#sql_server

                    
Добрый день.

Есть запрос, который выполняется большое кол-во времени (даже примерно сказать не
могу, больше 3ех минут). Таймаут на выполнение запроса стоит 1 мин, потом ее выполнение
обрывается. Все индексы построены, в запрос 1 SELECT:

    SELECT
        p.[personId] as [personId],
        --r.[innerId],
        p.inn  as ИНН,
        ps.[firstNameRu] as [Имя рус.],
        ps.[lastNameRu] as [Фамилия рус.],
        ps.[middleNameRu] as [Отчество рус.],
        ps.[firstNameUa] as [Имя укр.] ,
        ps.[lastNameUa] as [Фамилия укр.],
        ps.[middleNameUa] as [Отчество укр.],
        ps.[birthday] as [Дата рождения],
        p.[phone] as Телефон,
        (
            select comment+',  ' from dbo.Reason  
            where personId = p.personId
            for xml path('')
        ) as Причина,
        CASE
            WHEN ps.[lostDate] IS NULL THEN 0
            WHEN ps.[lostDate] IS NOT NULL THEN 1
        END as [Паспорт потерян],
        ps.[series] as Серия,
        ps.[number] as Номер,
        ps.[office] as [Место выдачи],
        ps.[dateReceipt] as [Дата выдачи],
        p.[city] as Город,
        ps.[address] as Прописка
    FROM dbo.Person (NOLOCK) p
    LEFT JOIN dbo.Passport (NOLOCK) ps
      ON ps.personId = p.personId

    WHERE 

    --isnull(inn,0)=isnull(@inn,isnull(inn,0))

    (isnull(inn, 0)               = isnull(@inn,            isnull(inn,0))) AND
    (isnull([firstNameRu], 0)     = isnull(@firstNameRu,    isnull([firstNameRu],0))) AND
    (isnull([lastNameRu], 0)      = isnull(@lastNameRu,     isnull([lastNameRu],0))) AND
    (isnull([middleNameRu], 0)    = isnull(@middleNameRu,   isnull([middleNameRu],0))) AND
    (isnull([firstNameUa], 0)     = isnull(@firstNameUa,    isnull([firstNameUa],0))) AND
    (isnull([lastNameUa], 0)      = isnull(@lastNameUa,     isnull([lastNameUa],0))) AND
    (isnull([middleNameUa], 0)    = isnull(@middleNameUa,   isnull([middleNameUa],0))) AND
    ((@birthday is null) or (ps.[birthday] = @birthday)) AND
    (isnull([phone], 0)           = isnull(@contactPhone,   isnull(phone,0))) AND
    ----(@reason is null) or (
    ----    CONTAINS((
    ----        select comment+',  ' from dbo.Reason  
    ----        where personId = p.personId
    ----        for xml path('')
    ----    ), @reason)
    ----)) AND
    --(@PassportLost is null) or 
    --(
    --(CASE
    --  WHEN ps.[lostDate] IS NULL THEN 0
    --  WHEN ps.[lostDate] IS NOT NULL THEN 1
    --END) = @PassportLost
    --) AND
    (isnull([series], 0)        = isnull(@passportSeries, isnull([series],0))) AND
    (isnull([number], 0)        = isnull(@passportNumber, isnull([number],0))) AND
    (isnull([office], 0)        = isnull(@ktoVidal,       isnull([office],0))) AND
    ((@PassportReceivingDate is null) or ( [dateReceipt] = @PassportReceivingDate)) AND
    (isnull([city], 0)          = isnull(@City,           isnull([city],0))) AND
    (isnull([address], 0)       = isnull(@PassportAdress, isnull([address],0)))


Смысл запроса: у нас есть 2 связанные таблицы и большое число параметров. по которым
может производиться отбор (всего около 15 параметров, причем могут быть заданы не все,
а например только 1 фильтр, или 2 и 3ий)

Подскажите, пожалуйста, как можно оптимизировать запрос?

Спасибо

План выполнения запроса показал, что 60% идет поиск в таблице Person:

    


Ответы

Ответ 1



Ок, по куску плана видна пара проблем: Index scan на плане - это перебор всех строк в таблице Persons в поисках подходящих под фильтр. Фильтр у вас написан так, что он читает и проверяет все упоминаемые в нем колонки, даже если значение не было передано. Судя по толщине стрелок, данных в Persons достаточно много. Т.е. ваш запрос всегда перелопачивает всю базу. Эффективного способа способа написать "если параметр спущен, то сравнить с ним", насколько я знаю, нет. Это одна из проблем выборки данных через хранимки. Вам придется строить SQL динамически, или на стороне приложения, или на стороне SQL Server, и выполнять его через sp_executesql (лучше первый вариант). Вторая проблема - для каждой из выбранных строк SQL Server достает данные из Passport. Индекс, по которому он это делает - не покрывающий. т.е. для каждого из Person SQL Server лезет в индекс IX_Passport_PersonId_IsЧтоТоТам, как в наиболее подходящий (скорее всего в этом индексе первая колонка - Passport.PersonID), достает из этого индека PassportID. с этим PassportID лезет в PK_Passport, достает оттуда данные. этого можно избежать, создав покрывающий индекс - CREATE NONCLUSTERED INDEX [IX_Passport_For_Person] ON [dbo].[Passport] ( [personID] ASC ) INCLUDE ( [firstNameRu], -- и остальные колонки, которые показываются в плане в ноде Key Lookup ) но это лучше сделать после динамического построения условий - может быть у вас key lookup вообще исчезнет.

Ответ 2



Я переписал запрос, и сделал его динамическим. Решило все проблемы. Всем спасибо.

четверг, 9 апреля 2020 г.

array in procedure

#sql #sql_server

                    
Создаю процедуру типа:

CREATE PROCEDURE func
    @tmp  INTEGER,
    @lot INTEGER,
    @qty  INTEGER
AS


Чтобы передавать туда что-то вроде:

EXEC func
    @tmp = 77,
    @lot = 191,
    @qty = 102;


Но как сделать так, чтобы можно было передать множество значений произвольной длинный
(типа массив)? Например:

EXEC func
    @tmp = 77,
    @lot = 191, 201, 199, 63,
    @qty = 102, 314, 271;

    


Ответы

Ответ 1



Если версия сервера 2008 и старше, то можно использовать Table-Valued Parameters: CREATE TYPE intTable AS TABLE (value int) GO CREATE PROCEDURE func @tmp intTable READONLY, @lot intTable READONLY, @qty intTable READONLY AS BEGIN ... END; GO DECLARE @t intTable, @l intTable, @q intTable; INSERT @t (value) VALUES (77); INSERT @l (value) VALUES (191), (201), (199), (63); INSERT @q (value) VALUES (102), (341), (271); EXEC func @tmp = @t, @lot = @l, @qty = @q

Msg 9002 The transaction log for database 'myDataBase' is full due to 'ACTIVE_TRANSACTION

#sql #sql_server

                    
Есть база данных myDataBase в ней есть таблица myTable в таблице около двух миллионов
записей. При попытке добавить столбец к этой таблице

ALTER TABLE dbo.myTable ADD myTable_id int identity(1,1) not null primary key


получаю ошибку: 


  Msg 9002 The transaction log for database 'myDataBase' is full due to 'ACTIVE_TRANSACTION'.**


Из сообщения понятно что переполнен журнал transaction log 

После этого увеличил размер log файла:

ALTER DATABASE myDataBase
MODIFY FILE
    (NAME = myDataBase_log ,
     MAXSIZE = 170MB)
GO


и применил команду:

CHECKPOINT
    DBCC SHRINKFILE ('myDataBase_log')


До выполнения команды: 
ALTER TABLE dbo.myTable ADD myTable_id int identity(1,1) not null primary key 
размер log файла 27MB после 150MB и всё равно ошибка Msg 9002. Подскажите как в данной
ситуации поступить:


Увеличивать размер log файла? если увеличивать то на сколько?
Нормальное ли это поведение log файла?

    


Ответы

Ответ 1



Да, это нормальное поведение. SQL Server немедленно пишет все вносимые вами изменения в transaction log. Ваш ALTER требует полного пересоздания таблицы - т.к. вы добавляете кластерный индекс. Соответственно, в лог будут записано удаление старых данных + вставка их заново, с попутным пересозданием всех индексов. Так что объем лог-файла стоит увеличить минимум до 2x размера таблицы (лучше 4x). Я бы на вашем месте выставил размер лога не исходя из размера базы, а исходя из доступного места на диске (т.е. все свободное место минус минимальный запас). или вообще снял ограничение. Размер таблицы + размер индексов можно посмотреть в View / Object Explorer Details в Management Studio.

Как создать таблицу истории?

#sql #sql_server

                    
Есть таблица Persons:

| ID | Name  | Post       |
| 1  | Kolin | manager    |
| 2  | Emma  | specialist |


Нужно создать копию таблицы - Persons_history котарая отслеживает изменений.  

| ID | Name  | Post       | created_at                  | created_by | Operation |   
+----+-------+------------+-----------------------------+------------+-----------+
| 1  | Kolin | manager    | 2015-08-06 10:04:28.6000000 | hh\Mark    | Insert    |
| 2  | Emma  | specialist | 2015-08-17 17:55:03.6600000 | hh\Mark    | Update    | 


К примеру таблица должна выглядит так.
Как можно реализовать? Как создать таблицу истории?
    


Ответы

Ответ 1



Создаём таблички: IF OBJECT_ID('Persons')IS NOT NULL DROP TABLE Persons IF OBJECT_ID('Persons_History')IS NOT NULL DROP TABLE Persons_History GO CREATE TABLE Persons( Id INT IDENTITY(1,1), name VARCHAR(255), post VARCHAR(255) ) GO CREATE TABLE Persons_History( Id INT, name VARCHAR(255), post VARCHAR(255), modify_at DATETIME, modify_by NVARCHAR(255), Operation VARCHAR(6) ) GO Создание триггеров(можно обойтись и одним на самом деле) CREATE TRIGGER Persons_History_Trigger_Insert ON Persons AFTER INSERT AS INSERT Persons_History SELECT Id, name, post, GETDATE(), SUSER_SNAME(), 'insert' FROM INSERTED GO CREATE TRIGGER Persons_History_Trigger_Update ON Persons AFTER UPDATE AS INSERT Persons_History SELECT Id, name, post, GETDATE(), SUSER_SNAME(), 'update' FROM INSERTED GO CREATE TRIGGER Persons_History_Trigger_Delete ON Persons AFTER DELETE AS INSERT Persons_History SELECT Id, name, post, GETDATE(), SUSER_SNAME(), 'delete' FROM DELETED GO DML операции и вывод результата INSERT Persons VALUES('Kolin', 'manager'),('Emma','specialist') UPDATE Persons SET name='pegoopik' WHERE id=1 DELETE FROM Persons SELECT * FROM Persons_History Ну и результат: Id name post modify_at modify_by Operation ----------- ---------- ---------- ----------------------- ------------------------------ --------- 2 Emma specialist 2016-01-22 12:12:44.157 ALPHA\XXX-Krasovskiy-EA insert 1 Kolin manager 2016-01-22 12:12:44.157 ALPHA\XXX-Krasovskiy-EA insert 1 pegoopik manager 2016-01-22 12:12:44.160 ALPHA\XXX-Krasovskiy-EA update 2 Emma specialist 2016-01-22 12:12:44.160 ALPHA\XXX-Krasovskiy-EA delete 1 pegoopik manager 2016-01-22 12:12:44.160 ALPHA\XXX-Krasovskiy-EA delete Можно ещё добавить, что вместо VARCHAR(6) для поля Operation можно хранить код операции, например, в byte. 0-insert; 1-update; 2-delete. Чуть сэкономит место.

Вывод данных из базы в динамическую таблицу

#java #sql_server #jdbc

                    
Имеется вот такая примерно таблица - 

      
  
  #{msgs.pageTitle}        
                
     
        
           #{msgs.customerIdHeader}
           #{customer.id}
        
        
           #{msgs.NDOKHeader}
           #{customer.NDOK}
        
                     
           #{msgs.SODRABHeader}
           #{customer.SODRAB}
        
     
       


Как написать правильный код с использованием jdbc, который бы подключался к базе
и получал данные, а затем они попадали в данную таблицу?
    


Ответы

Ответ 1



1) Для подключения к MSSQL вам поднадобится соответствующий драйвер. В Maven Central его нету, поэтому вам будет нужно самостоятельно добыть внутри каталога установки драйвера: \sqljdbc_\\sqljdbc.jar \sqljdbc_\\sqljdbc4.jar \sqljdbc_\\sqljdbc41.jar \sqljdbc_\\sqljdbc42.jar согласно документации Microsoft. Скачать драйвер можно с сайта Microsoft. (Поясню на всякий случай: installation directory не надо никуда прописывать - это просто путь до драйвера при обычной установке из инсталлятора. Если у вас уже все скачано, достаточно добавить драйвер в CLASSPATH.) Далее этот драйвер нужно добавить в CLASSPATH любым методом, специфичным для вашего проекта. Например, если вы собираете вручную из консоли, то это будет нечто вроде: CLASSPATH =.;C:\Program Files\Microsoft JDBC Driver 6.0 for SQL Server\sqljdbc_4.2\enu\sqljdbc42.jar CLASSPATH =.:/home/usr1/mssqlserverjdbc/Driver/sqljdbc_4.2/enu/sqljdbc42.jar Если вы используете IntelliJ IDEA, то это делается так: нужно зайти в настройки проекта (File->Project Structure) в разделе Modules найти ваш модуль на вкладке Dependencies в самом низу нажать на плюсик выбрать "JARs or Directories" добавить нужную директорию Если используется Maven, то есть проблема, в Maven Central нету этого артефакта, то есть его придется оформить самостоятельно. Для серьезных больших проектов, самый правильный способ - поднять собственный репозиторий. На выбор, например, есть Artifactory и Nexus. Как работать с этими инструментами - описывать слишком долго, не влезет в максимальный размер ответа. Скорей всего, у вас не тот случай, но я был обязан предупредить. Для небольших проектов, можно установить нужный jar в локальный репозиторий Maven. Про это на сайте Maven есть специальная документация. Вкратце, достаточно просто запустить в терминале строчку: mvn install:install-file -Dfile=<путь до джарки с жрайвером без угловых скобочек> -DgroupId=com.microsoft.sqlserver \ -DartifactId=com.microsoft.sqlserver.jdbc.SQLServerDriver -Dversion=4.2 -Dpackaging=jar и далее добавить в pom.xml новую зависимость вида: com.microsoft.sqlserver com.microsoft.sqlserver.jdbc.SQLServerDriver 4.2 В общем, добавление новой джарки с сборку проекта очень зависит от характеристик вашего проекта, способов сборки, деплоймента, итп. Тут все каждый решает для себя. 2) Далее у нас есть допустим Entity класс: public class Customer { private Integer id; private String NDOK; private String SODRAB; public Customer() { } public Customer(Integer id, String NDOK, String SODRAB) { this.id = id; this.NDOK = NDOK; this.SODRAB = SODRAB; } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getNDOK() { return NDOK; } public void setNDOK(String NDOK) { this.NDOK = NDOK; } public String getSODRAB() { return SODRAB; } public void setSODRAB(String SODRAB) { this.SODRAB = SODRAB; } } 3) Далее создаем managed bean: import java.io.Serializable; import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; import java.util.List; import javax.faces.bean.ManagedBean; import javax.faces.bean.SessionScoped; @ManagedBean @SessionScoped public class CustomerBean implements Serializable { private static final long serialVersionUID = 6081417964063918994L; public List getCustomers() throws ClassNotFoundException, SQLException { Connection connect = null; //Привел пример, как указать еще и в url, это необязательно String url = "jdbc:sqlserver://localhost:1433;" + "databaseName=CustomerDatabase;user=user;password=password"; String username = "user"; String password = "password"; try { Class.forName("com.microsoft.sqlserver.jdbc.SQLServerDriver"); connect = DriverManager.getConnection(url, username, password); // System.out.println("Connection established"+connect); } catch (SQLException ex) { System.out.println("in exec"); System.out.println(ex.getMessage()); } List customers = new ArrayList(); PreparedStatement pstmt = connect .prepareStatement("select id, NDOK, SODRAB from Customer"); ResultSet rs = pstmt.executeQuery(); while (rs.next()) { Customer customer = new Customer(); customer.setId(rs.getInt("id")); customer.setNDOK(rs.getString("NDOK")); customer.setSODRAB(rs.getString("SODRAB")); customers.add(customer); } // close resources rs.close(); pstmt.close(); connect.close(); return customers; } } 4) Передаете данные в таблицу способом, описанным в стартовом посте. 5) PROFIT

понедельник, 30 марта 2020 г.

Оптимизация запроса MS SQL

#sql #sql_server


У меня есть оригинальный запрос нахождения дубликатов:

Original query:

SELECT
    cd1.cust_number_id, cd1.cust_number_id, cd1.First_Name, cd1.Last_Name
FROM @Customer_Data cd1
    inner join @Customer_Data cd2 on
        cd1.Cd_Id <> cd2.Cd_Id
        and cd2.cust_number_id <> cd1.cust_number_id
        AND cd2.Flag = N'A'
        AND cd2.Cust_active = 1
        and cd2.First_Name = cd1.First_Name
        and cd2.Last_Name = cd1.Last_Name
    inner join @Customer c1 on c1.Cust_id = cd1.cust_number_id
    inner join @Customer c2 on c2.cust_id = cd2.cust_number_id
WHERE c1.cust_number <> c2.cust_number  
        AND cd1.Flag = N'A'
        AND cd1.Cust_active = 1




Я оптимизировал его следующим образом.

Optimized query:

 SELECT cd1.cust_number_id, cd1.cust_number_id, cd1.First_Name,cd1.Last_Name
 FROM (
    SELECT cdResult.cust_number_id, cdResult.First_Name,cdResult.Last_Name, COUNT(*)
OVER (PARTITION BY cdResult.First_Name, cdResult.Last_Name) as cnt_name_bday  
    FROM @Customer_Data cdResult
    WHERE cdResult.Flag = N'A'
        AND cdResult.Cust_active = 1
        AND cdResult.First_Name IS NOT NULL
        AND cdResult.Last_Name IS NOT NULL) AS cd1
 WHERE cd1.cnt_name_bday > 1;



4ой и 5ой строки не должно быть в результате выполнения запроса

Test data:

DECLARE @Customer_Data TABLE
(
    Cd_Id INT,
    cust_number_id INT,
    First_Name NVARCHAR(30),
    Last_Name NVARCHAR(30),
    Flag NVARCHAR(10),
    Cust_active INT
)

INSERT @Customer_Data (Cd_Id,cust_number_id,First_Name,Last_Name, Flag, Cust_active)
VALUES (1, 22, N'Alex', N'Bor',  'A', 1),
       (2, 22, N'Alex', N'Bor',  'A', 1),
       (3, 24, N'Alex', N'Bor',  'A', 1),
       (4, 24, N'Tom', N'Cruse', 'A', 1),
       (5, 24, N'Tom', N'Cruse', 'A', 1)

DECLARE @Customer TABLE
(
    Cust_id INT,
    Cust_number INT
)


INSERT @Customer (Cust_id, Cust_number)
VALUES (22, 022),
       (23, 022),
       (24, 024),
       (25, 024)


У меня возникла проблема в том, что я не могу исключить записи с одинаковым cust_numebr.
Результат должен быть, как на первом скриншоте.
    


Ответы

Ответ 1



Исключить одинаковые cust_number довольно просто. Получаем кроме количества строк еще максимальный и минимальный cust_number и проверяем что они не равны. Хотя проверка на количество строк в этом случае становится не нужна. SELECT cd1.cust_number_id, cd1.cust_number_id, cd1.First_Name,cd1.Last_Name FROM ( SELECT cdResult.cust_number_id, cdResult.First_Name,cdResult.Last_Name, MIN(cust_number_id) OVER (PARTITION BY cdResult.First_Name, cdResult.Last_Name) as min_cust, MAX(cust_number_id) OVER (PARTITION BY cdResult.First_Name, cdResult.Last_Name) as max_cust FROM @Customer_Data cdResult WHERE cdResult.Flag = N'A' AND cdResult.Cust_active = 1 AND cdResult.First_Name IS NOT NULL AND cdResult.Last_Name IS NOT NULL ) AS cd1 WHERE min_cust!=max_cust;

Ответ 2



можно вот так! SELECT DISTINCT cd1.CD_ID, cd1.CUST_NUMBER_ID, cd1.CUST_NUMBER_ID, cd1.FIRST_NAME, cd1.LAST_NAME FROM @CUSTOMER_DATA cd1 INNER JOIN @CUSTOMER_DATA cd2 ON cd1.CD_ID <> cd2.CD_ID AND cd2.CUST_NUMBER_ID <> cd1.CUST_NUMBER_ID AND cd2.FIRST_NAME = cd1.FIRST_NAME AND cd2.LAST_NAME = cd1.LAST_NAME WHERE cd1.FLAG = N'A' AND cd1.CUST_ACTIVE = 1 ORDER BY cd1.CD_ID;

Замена содержимого в XML в среде MS SQL

#sql #sql_server #xml #xquery


Коллеги, есть такая задача по замене значений в поле data, где сообственно сам XML
документ.
Необходимо, чтобы производилась замена @yandex.ru на @mail.ru (логин,email) значения
по всем записям. Записей в таблице более 100. Каким образом реализовать проход по всем
записям с последовательной заменой значений?

Структура таблицы:

collaborator (id int, data xml)


Пример содержимого XML документа:


5759501959199724993
Рязанцев
Дмитрий
Александрович
499-0000000
ryazancevda@mail.ru
ryazancevda@mail.ru



Запрос:

declare @xml xml 
select @xml=data from collaborator
declare @email varchar(80)
declare @login varchar(80)

begin
update collaborator
    set @email = @xml.value('(/collaborator/email)[1]', 'varchar(80)')
    set @email = replace(@email, 'yandex.ru', 'mail.ru')
    set @xml.modify('
       replace value of (/collaborator/email/text())[1]
       with sql:variable("@email")
   ')

    set @login = @xml.value('(/collaborator/login)[1]', 'varchar(80)')
    set @login = replace(@login, 'yandex.ru', 'mail.ru')
    set @xml.modify('
       replace value of (/collaborator/login/text())[1]
       with sql:variable("@login")
   ')

 end
select @xml;

    


Ответы

Ответ 1



Я думал, думал... Придумал такое: update collaborator set data.modify(' replace value of (/collaborator/email/text())[1] with concat( substring( (/collaborator/email/text())[1], 1, string-length((/collaborator/email/text())[1]) - string-length("mail.ru")), "yandex.ru") ') where data.exist('/collaborator/email/text()[contains(., "mail.ru")]') = 1 update collaborator set data.modify(' replace value of (/collaborator/login/text())[1] with concat( substring( (/collaborator/login/text())[1], 1, string-length((/collaborator/login/text())[1]) - string-length("mail.ru")), "yandex.ru") ') where data.exist('/collaborator/login/text()[contains(., "mail.ru")]') = 1 Получается два запроса. Это лучшее, что удалось придумать. Обновить за раз можно только одну строку. К тому же набор функций XQuery весьма ограничен, поэтому пришлось изобретать такую сложную конструкцию с использованием concat/substring/string-length. Если точно известно, что все логины и емейлы заканчиваются на "mail.ru", то можно убрать условие where data.exist. В коде неоднократно повторяются однотипные конструкции вида (/collaborator/login/text())[1], что весьма громоздко. Не знаю, может есть способ использовать переменную let $login := ... и далее её подставлять?

Ответ 2



Если XML имеет такую простую структуру, то можно обойтись одним UPDATE: UPDATE t SET t.data = data.query(' element collaborator { for $a in (/collaborator/*) return if (local-name($a) = "email") then element email { text { sql:column("d2.email") } } else if (local-name($a) = "login") then element login { text { sql:column("d2.login") } } else $a } ') FROM collaborator t CROSS APPLY ( SELECT email = t.data.value('(/collaborator/email/text())[1]', 'nvarchar(400)'), login = t.data.value('(/collaborator/login/text())[1]', 'nvarchar(400)') ) d CROSS APPLY ( SELECT email = REPLACE(d.email, N'@mail.ru', N'@yandex.ru'), login = REPLACE(d.login, N'@mail.ru', N'@yandex.ru') ) d2 т.е. XML каждой строки данных пересобирается с помощью FLWOR, но в элементах email и login значения заменяются выражениями возвращаемыми REPLACE.

воскресенье, 29 марта 2020 г.

Последовательный GUID [закрыт]

#sql #sql_server


        
             
                
                    
                        
                            Закрыт. Этот вопрос необходимо уточнить или дополнить
подробностями. Ответы на него в данный момент не принимаются.
                            
                        
                    
                
                            
                                
                
                        
                            
                        
                    
                        
                            Хотите улучшить этот вопрос? Добавьте больше подробностей
и уточните проблему, отредактировав это сообщение.
                        
                        Закрыт 11 месяцев назад.
                                                                                
           
                
        
Прошу помощи в составлении SQL запроса на получении последовательного GUID поля 'ID',
чтобы в дальнейшем его программно передать в запрос на добавления записи 
    


Ответы

Ответ 1



Сейчас вы хотите сперва сгенерировать GUID и получить его на клиент для последующей вставки с ним. Поступите ровно наоборот: вставляйте запись в БД с автоматической генерацией GUID, а потом получайте его на клиент. Допустим, таблица создана следующим образом: create table SomeTable ( id uniqueidentifier default newsequentialid(), foo text ) Запрос на вставку будет таким: insert into SomeTable (foo) output inserted.id values ('bar') В коде C# это будет выглядеть так: using (var conn = new SqlConnection(@"...")) using (var cmd = conn.CreateCommand()) { cmd.CommandText = "insert into SomeTable (foo) output inserted.id values ('bar')"; conn.Open(); var guid = (Guid)cmd.ExecuteScalar(); Console.WriteLine(guid); } PS: Конечно, во избежание sql-инъекций следует всегда использовать параметры. Здесь код без них только ради упрощения восприятия.

суббота, 21 марта 2020 г.

Поиск и удаление строки с unicodeescape в mssql на python

#python #sql #python_3x #sql_server


В SQL таблице есть столбец sc_path, в котором содержится запись C:\Users\elebedev\Desktop\Python
Мне нужно удалить все строки в таблице содержащие эту запись.

from sqlalchemy import create_engine 


path=r'C:\Users\elebedev\Desktop\Python'

con_log = create_engine('mssql+pymssql://log:pass@s1c/base')
sql_log = "DELETE FROM python_log WHERE CONVERT(NVARCHAR, sc_path) = '{}' ".format(path)
with con_log.begin() as conn:
    conn.execute(sql_log)


Код выполняется, но он не видит эти строки. Как мне кажется, что это из за unicodeescape
символа, но это не точно. 

Подскажите, как быть? 
    


Ответы

Ответ 1



Вы используете raw string - соответственно Python сам "заэкранирует" обратные слеши: In [57]: path=r'C:\Users\elebedev\Desktop\Python' In [58]: path Out[58]: 'C:\\Users\\elebedev\\Desktop\\Python' Если вас интересуют строки, начинающиеся с данной подстроки, то запрос нужно изменить: sql_log = "DELETE FROM python_log WHERE CONVERT(NVARCHAR, sc_path) like CONVERT(NVARCHAR, %s)" parms = (f"{path}%", ) # NOTE: ^ ^ con_log.execute(sql_log, parms)

Ответ 2



Удалить как хотелось не получилось. Сделал чуток по другому. Но изначальным вариантом было бы интересней. from sqlalchemy import create_engine path='elebedev' con_log = create_engine('mssql+pymssql://login:passw@server/base') sql_log = "DELETE FROM python_log WHERE CONVERT(NVARCHAR, sc_path) like %s" parms = (f"%{path}%", ) # NOTE: ^ ^ ^ con_log.execute(sql_log, parms)

воскресенье, 15 марта 2020 г.

Разбить строку результата на две в MS SQL

#sql #sql_server


Подскажите решение вот такой вот смарт-задачки: имеем результат выборки



В результате появляется строка, где в двух колонках и FixHours, и AddHours есть значения
не равные нулю:

FixHours = 0,5 AddHours = 1,5 CalculatedValue = 16.5

Как ее можно разделить на две, чтобы получить строки:

FixHours = 0,5 AddHours = 0 CalculatedValue = 16.5

FixHours = 0 AddHours = 1,5 CalculatedValue = 16.5

Была идея находить только эту строку, затем искусственно создавать вторую с помощью
Union. Но она провалилась, т.к. такие строки в таблице встречаются не один раз. В общем,
any ideas?
    


Ответы

Ответ 1



Если порядок не важен, то: WITH SourceData AS ( SELECT FixHours, AddHours, CalculatedValue FROM SomeTable --тут ваша выборка ) SELECT FixHours, 0, CalculatedValue FROM SourceData WHERE FixHours <> 0 UNION ALL SELECT 0, AddHours, CalculatedValue FROM SourceData WHERE AddHours <> 0 UNION ALL SELECT 0, 0, CalculatedValue FROM SourceData WHERE FixHours = 0 AND AddHours = 0 Если важен - то нужно добавлять rownumber в SourceData, и сортировать результат union по нему.

Ответ 2



Если есть желание без всяких юнионов, можно сделать join на таблицу, которая возвращает 2 записи и дальше уже по условиям выбирать, что показывать. Т.е. что-нибудь вроде такого варианта (не тестировал, но должно работать, за производительность такого решения также не ручаюсь): select case spt.number when 0 then t.FixHours else 0 end as FixHours, case spt.number when 1 then t.AddHours else 0 end as AddHours, t.CalculatedValue from table t join master..spt_values spt on spt.type='p' and spt.number in (0,1) and ((t.FixHour > 0 and spt.number = 0) or (t.AddHours > 0 and spt.number = 1) or (t.FixHour = 0 and t.AddHours = 0 and spt.number = 0))

Ответ 3



select fixhours,0,calculatevalue from a where a.fixhours!=0 and a.addhours!=0 union select 0 ,addhours, calculatevalue from a where a.fixhours!=0 and a.addhours!=0 Пример решения

Ответ 4



;WITH cte (FixHours, AddHours, CalculatedValue) AS ( -- тут должен быть запрос, возвращающий исходную таблицу SELECT 10, 0, 14 UNION ALL SELECT 0.5, 0, 14.5 UNION ALL SELECT 0.5, 1.5, 16.5 UNION ALL SELECT 0, 0.5, 17 UNION ALL SELECT 7, 0.75, 17.75 UNION ALL SELECT 0, 2, 19.75 ) SELECT FixHours, AddHours, CalculatedValue FROM cte WHERE FixHours = 0 OR AddHours = 0 UNION ALL SELECT FixHours, 0, CalculatedValue FROM cte WHERE FixHours <> 0 AND AddHours <> 0 UNION ALL SELECT 0, AddHours, CalculatedValue FROM cte WHERE FixHours <> 0 AND AddHours <> 0 ORDER BY CalculatedValue -- ну или какой там требуется быть порядок

SqlConnection не реагирует на password

#c_sharp #sql #sql_server #adonet


Имеется база данных на Sql Server 2012. 
В базе данных имеется пользователь TestUser, созданный таким образом:

CREATE LOGIN TestUser
    WITH PASSWORD = '123';
USE Банк;
GO
CREATE USER TestUser FOR LOGIN TestUser;
GO 


Я подключаюсь к базе через приложение на C#, используя SqlConnection.

String ConnectionString = "Data Source=Computer;Initial Catalog=Банк;Persist Security
Info=False;Integrated Security=SSPI;User ID=TestUser;Password=123;";
SqlConnection con = new SqlConnection();
con.ConnectionString = ConnectionString;
con.Open();


Проблема в том что даже если пароль неверный то соединение все равно будет установлено
и можно отправлять запросы. Подскажите как это исправить.
    


Ответы

Ответ 1



Попробуйте убрать "Integrated Security=SSPI". Кажется это логинит через системный логин и соосно игнорит юзера и пароль. То есть вы входите не как TestUser.

Как улучшить SQL запрос

#sql #sql_server


Мне нужно сделать выборку с таблиц со следующим условием:


  сделать запрос, который получает список всех продуктов, и цены на
  2013-03-01


Я написал вот такой скрипт:

select st.[ProductID], st.[ProductName], [Price].Price
from (
  select [Product].[ProductID], [ProductName], MAX(OnDate) as [OnDate]
  from [Product]
  inner join [dbo].[Price] on
    [Product].[ProductID] = [Price].[ProductID]
  where [OnDate] <= '2013-03-01'
  group by [Product].[ProductID], [ProductName]
) st
inner join [dbo].[Price] on
st.[ProductID] = [Price].[ProductID] and [Price].[OnDate] = st.[OnDate]
order by [ProductID]


И структура таблиц:

create table Product
(
    ProductID int not null identity(1,1) primary key,
    ProductName varchar(255),
    Description varchar(max),
    Color varchar(15)
)
create table Price
(
    ProductID int not null,
    OnDate datetime not null,
    Price int not null
)


Все работает отлично, но хотелось бы как то красивее все написать.
Какие есть идеи?
    


Ответы

Ответ 1



Проблема не столько в запросе, сколько в схеме базы. В таблице Price нет PK. Совсем. Единственный способ, которым SQL Server может выбрать что-то из таблицы без PK - это перечитать ее целиком. На большом объеме это будет тормозить вне зависимости от красоты запроса. Добавьте ключ - или составной (ProductID + OnDate, или какой-нибудь PriceID identity). После этого посмотрите план запроса и добавляйте индексы по необходимости. Я бы предсказал CREATE NONCLUSTERED INDEX [NonClusteredIndex-OnDate_Prod] ON [dbo].[Price] ( [OnDate] ASC, [ProductID] ASC ) что (на паре сотен строк) приведет к плану вида т.е. хоть вы и упомянули Price дважды, реально данные из нее будут вычитаны один раз - при чтении конкретной цены. Если очень хочется - можно переписать запрос с одним JOIN: ;WITH PricesBeforeDate as ( SELECT * from Price WHERE [OnDate] <= '2013-03-01' ), IndexedPrices as ( SELECT ROW_NUMBER() OVER(PARTITION BY [ProductID] ORDER BY OnDate DESC) AS Row, ProductID, Price FROM PricesBeforeDate ), LastPrices as ( SELECT * FROM IndexedPrices WHERE Row = 1 ) SELECT LastPrices.ProductID, ProductName, Price FROM LastPrices INNER JOIN Product on LastPrices.ProductID = Product.ProductID этот запрос дает чуть меньше чтений на тех данных, что я у себя навбивал, но всегда стоит сравнить на реальных значениях: видно что есть Sort, кушающий CPU. Это сортировка по ProductID и OnDate, вызванная тем, что я использовал PriceID в качестве ключа. Если использовать ключ по ProductID ASC, OnDate DESC, то сортировка исчезнет ALTER TABLE dbo.Price ADD CONSTRAINT PK_Price_1 PRIMARY KEY CLUSTERED ( ProductID, OnDate DESC ) план: Но в целом выбирать между разными вариантами запроса и разными индексами стоит на реальных данных. Включаете SET Statistics io on SET Statistics time on и смотрите output и план. Все остальное - гадание.

пятница, 13 марта 2020 г.

SQL, cursor, перебор, добавляется две записи вместо одной

#sql_server #циклы #cursor


Пытаюсь с помощью курсора перебрать исходные данные и для каждой записи сделать те
или иные изменения. Вот сам запрос:

DECLARE @ID bigint --id attachments
DECLARE @personID BIGINT
DECLARE @territoryServiceID BIGINT
DECLARE @isAtClosed BIT

DECLARE @currentServerDate DATETIME = '2016-01-01 01:10:00.000' --this change GETDATE()
DECLARE @BeginDate DATETIME SET @BeginDate = @currentServerDate
DECLARE @periodYear INT SET @periodYear = DATEPART(YEAR,@currentServerDate) - 1

DECLARE cur cursor LOCAL STATIC
FOR
SELECT at.id, at.personID, at.territoryServiceID, ts.isClosing
FROM Attachments at
INNER JOIN Person p ON p.id = at.personID AND p.parentID IS NULL
INNER JOIN TerritoryServices ts ON ts.id = at.territoryServiceID
LEFT JOIN Attachments at2 ON at2.personID = at.personID AND at2.parentID = at.id
AND at2.attachmentStatusID IN (2,11,12)
WHERE at.attachmentStatusID = 1 AND at.causeOfAttachID = 8 AND at.endDate IS NOT NULL
AND at2.id IS NULL
AND p.id IN (15300000019296419,15300000018501113,15300000014988209,414674754,420940229,409531785)


OPEN cur

FETCH NEXT FROM cur INTO @ID, @personID, @territoryServiceID, @isAtClosed
WHILE @@FETCH_STATUS = 0
BEGIN
DECLARE @personID_NVARCHAR NVARCHAR(MAX) SET @personID_NVARCHAR = CONVERT(NVARCHAR(MAX),@personID)
PRINT '1 ('+@personID_NVARCHAR+')'

IF (@isAtClosed = 1) -- if ter of CA is closing
    BEGIN
        -- Insert error into ErrorHandlingCampainOfAttach
        DECLARE @ErrorDescr NVARCHAR(MAX) SET @ErrorDescr = 'TerId: ' + CONVERT(NVARCHAR(MAX),@territoryServiceID)
        INSERT INTO [dbo].[ErrorHandlingCampainOfAttach] ([AttachmentsID],[personID],[territoryServiceID],[periodYear],[reasonError],[addDate],[description])
        VALUES (@ID, @personID, @territoryServiceID, @periodYear, 1, GETDATE(), @ErrorDescr)
    END
ELSE
    BEGIN
        DECLARE @terAt2ID BIGINT
        DECLARE @isAt2Close BIT = 0
        SELECT @isAt2Close = ts.isClosing, @terAt2ID = ts.id FROM Attachments at 
        INNER JOIN TerritoryServices ts ON ts.id = at.territoryServiceID
        WHERE at.personID = @personID AND at.attachmentStatusID = 2 AND at.endDate
IS NULL

        IF (@isAt2Close = 1) -- if ter of attach is closing
            BEGIN 
                -- Insert error into ErrorHandlingCampainOfAttach
                DECLARE @ErrorDescr2 NVARCHAR(MAX) SET @ErrorDescr2 = 'TerAttachId:
' + CONVERT(NVARCHAR(MAX),@terAt2ID)
                INSERT INTO [dbo].[ErrorHandlingCampainOfAttach] ([AttachmentsID],[personID],[territoryServiceID],[periodYear],[reasonError],[addDate],[description])
                VALUES (@ID, @personID, @territoryServiceID, @periodYear, 2, GETDATE(),
@ErrorDescr2)
            END
        ELSE
            BEGIN 
                BEGIN TRY
                BEGIN TRANSACTION TranName
                    -- Search active request
                    DECLARE @ID_zapros BIGINT 
                    SELECT @ID_zapros = id FROM Attachments WHERE personID = @personID
AND endDate IS NULL AND attachmentStatusID != 2 AND id != @ID
                    IF (@ID_zapros IS NOT NULL) 
                        BEGIN
                            -- Canseled request

                            -- Block #1
                            -- Create cancel for active request
                            INSERT INTO Attachments (personID,orgHealthCareID,personAddressesID,territoryServiceID,attachmentProfileID,doctorID,
                                causeOfAttachID,careAtHome,senderRequestID,senderSystemID,attachmentStatusID,beginDate,endDate,parentID,userID,registratorID,
                                actualAttachmentID,ConflictAttachment,Node,regDate,isMigrated,isDuplicate,oldPersonID,servApplicationID,Num)
                            SELECT at.personID,at.orgHealthCareID,at.personAddressesID,at.territoryServiceID,at.attachmentProfileID,
at.doctorID,
                                8,at.careAtHome,NULL,NULL, 11, @BeginDate, @BeginDate,
at.id, 
                                at.userID, at.registratorID, at.actualAttachmentID,
NULL,NULL,at.regDate,NULL,0,at.oldPersonID,NULL,at.Num
                            FROM Attachments at 
                            WHERE at.id = @ID_zapros

                            -- Set endDate for active request
                            UPDATE Attachments SET endDate = @BeginDate WHERE id
= @ID_zapros
                        END

                    --Search active attach
                    DECLARE @ID_prikrep BIGINT
                    SELECT @ID_prikrep = id FROM Attachments WHERE personID = @personID
AND endDate IS NULL AND attachmentStatusID = 2
                    IF (@ID_prikrep IS NOT NULL) 
                        BEGIN
                            -- Block #2
                            -- Insert detach
                            INSERT INTO Attachments (personID,orgHealthCareID,personAddressesID,territoryServiceID,attachmentProfileID,doctorID,
                                causeOfAttachID,careAtHome,senderRequestID,senderSystemID,attachmentStatusID,beginDate,endDate,parentID,userID,registratorID,
                                actualAttachmentID,ConflictAttachment,Node,regDate,isMigrated,isDuplicate,oldPersonID,servApplicationID,Num)
                            SELECT at.personID,at.orgHealthCareID,at.personAddressesID,at.territoryServiceID,at.attachmentProfileID,
at.doctorID,
                                8,at.careAtHome,NULL,NULL, 8, @BeginDate, @BeginDate,
at.id, 
                                at.userID, at.registratorID, at.actualAttachmentID,
NULL,NULL,at.regDate,NULL,0,at.oldPersonID,NULL,at.Num
                            FROM Attachments at 
                            WHERE at.id = @ID_prikrep

                            --Set endDate for active attach
                            UPDATE Attachments SET endDate = @BeginDate WHERE id
= @ID_prikrep
                        END

                    -- Attach CA
                    INSERT INTO Attachments (personID,orgHealthCareID,personAddressesID,territoryServiceID,attachmentProfileID,doctorID,
                        causeOfAttachID,careAtHome,senderRequestID,senderSystemID,attachmentStatusID,beginDate,endDate,parentID,userID,registratorID,
                        actualAttachmentID,ConflictAttachment,Node,regDate,isMigrated,isDuplicate,oldPersonID,servApplicationID,Num)
                    SELECT at.personID,at.orgHealthCareID,at.personAddressesID,at.territoryServiceID,at.attachmentProfileID,
at.doctorID,
                        8,at.careAtHome,NULL,NULL, 2, @BeginDate, NULL, at.id, 
                        at.userID, at.registratorID, at.actualAttachmentID, NULL,NULL,at.regDate,NULL,0,at.oldPersonID,NULL,at.Num
                    FROM Attachments at 
                    WHERE at.id = @ID

                COMMIT TRANSACTION TranName

                END TRY
                BEGIN CATCH
                    ROLLBACK TRANSACTION TranName

                    -- Insert error into ErrorHandlingCampainOfAttach
                    INSERT INTO [dbo].[ErrorHandlingCampainOfAttach] ([AttachmentsID],[personID],[territoryServiceID],[periodYear],[reasonError],[addDate],[description])
                    VALUES (@ID, @personID, @territoryServiceID, @periodYear, 3,
GETDATE(),ERROR_MESSAGE())
                END CATCH
            END
    END

FETCH NEXT FROM cur INTO @ID, @personID, @territoryServiceID, @isAtClosed
END
CLOSE cur
DEALLOCATE cur


Запрос для курсора возвращает 6 строк(6 выбраны для примера), то есть все ID уникальные,
ничего не задваивается. Далее в зависимости от определенных условий производятся те
или иные действия, прошу обратить внимание на два блока действий (в комментариях называется
Block#1 и Block#2), именно они ведут себя странно. 

После выполнения запроса на какой-нибудь (тут тоже важно, не всегда на одной и той-же
записи, бывает то на одной то на другой, а то и на двух) записи задваиваются строки
с данными, то есть вставляется две строки у которых все данные кроме автоинкрементного
идентификатора повторяются! Причем если откатить действия и попробовать только на этой
записи на одной (то есть цикл прогнать для одной записи) все завершается отлично! Вот
такие-вот злые электрические силы. Непонятно почему так происходит, подскажите кто-нибудь
что не так, или в каком направлении искать решение? 

P.S. триггеров на вставку данных на таблице Attachments нету. Строки всегда вставляются
после всех действия, то есть допустим первое условие выполняется, вставляется строка
1, затем по второму условию вставляется строка 2, затем строка 3, и в случае если происходит
задвоение строки, то она вставляется самой последней, то есть после строки 3 вставляется
строка 4 идентичная строке 1 (или строке 2 когда как)
    


Ответы

Ответ 1



С помощью Mike удалось найти решение данной проблемы! Тут я изложу все поподробнее вдруг кому-то поможет. Итак, начнем. Результат запроса для курсора: ... как видно никакого дублирования идентификаторов нет С подсказкой Mike(спасибо огромное), в одно из полей (Node) записал id записи в переборе, в результате получил следующее: В первой записи все отлично, не будем ее рассматривать. Вторая запись по списку id = 14308060, personID = 414674754. В результате произошло задвоение (Block #1), но в поле Node видим, что в конце записался идентификатор следующей по порядку записи!!! Ниже приведен результат следующей записи: ... тут все нормально Далее.. Пятая запись id = 148362023, personID = 15300000018501113. В результате прошло задвоение (Block #2), опять же в поле Node идентификатор следующей записи Ниже результат следующей записи в которой все ровно: Итак, все это навело на мысль, значения в переменных остаются прежними если в результате установки возвращается NULL, хотя в каждом цикле переменная объявляется заново. Теперь смотрим что происходит: 1. Выполняется обработка второй записи, так как у нее есть активный запрос то следующее выражение: DECLARE @ID_zapros BIGINT SELECT @ID_zapros = id FROM Attachments WHERE personID = @personID AND endDate IS NULL AND attachmentStatusID != 2 AND id != @ID записывает в переменную @ID_zapros идентификатор 150118746, далее выполняется все как нужно, добавляется ровно столько записей сколько нужно, последней записи, которая дублируется еще пока нет! Далее выполняется обработка третьей записи. У данной записи активного запроса нет, поэтому следующее выражение возвращает NULL SELECT @ID_zapros = id FROM Attachments WHERE personID = @personID AND endDate IS NULL AND attachmentStatusID != 2 AND id != @ID но! в переменную @ID_zapros записывается не NULL(как я предполагал), а остается предыдущее значение! Вот тут то и зарыта собака. И получается что при обработке третьей записи добавляется еще одна запись в с данными о предыдущей записи... с 5 и 6 записью все тоже самое, только уже на другом этапе... Я думал что, так как переменная объявляется внутри цикла, то при каждом объявлении в нее будет записываться NULL, также ошибался что при установки переменной, если результат возвращает NULL, то и в переменную запишется NULL, оказалось совсем не так... Решение довольно простое, обнулять переменную принудительно, я сделал так: DECLARE @ID_zapros BIGINT SELECT @ID_zapros = id FROM Attachments WHERE personID = @personID AND endDate IS NULL AND attachmentStatusID != 2 AND id != @ID IF (@ID_zapros IS NOT NULL) BEGIN --Отказываем запрос --Создаем отказ активному запросу INSERT INTO Attachments (personID,orgHealthCareID,personAddressesID,territoryServiceID,attachmentProfileID,doctorID, causeOfAttachID,careAtHome,senderRequestID,senderSystemID,attachmentStatusID,beginDate,endDate,parentID,userID,registratorID, actualAttachmentID,ConflictAttachment,Node,regDate,isMigrated,isDuplicate,oldPersonID,servApplicationID,Num) SELECT at.personID,at.orgHealthCareID,at.personAddressesID,at.territoryServiceID,at.attachmentProfileID, at.doctorID, 8,at.careAtHome,NULL,NULL, 11, @BeginDate, @BeginDate, at.id, at.userID, at.registratorID, at.actualAttachmentID, NULL,@nvar_ID,at.regDate,NULL,0,at.oldPersonID,NULL,at.Num FROM Attachments at WHERE at.id = @ID_zapros --Закрываем дату активному запросу UPDATE Attachments SET endDate = @BeginDate WHERE id = @ID_zapros SET @ID_zapros = NULL END Извиняюсь за довольно большое изложения, но я впервые здесь, может что-то делаю не так вы уж простите! Еще раз спасибо всем кто откликнулся! Надеюсь это кому-нибудь поможет не напороться на те же грабли)

Ответ 2



Код не самый очевидный, не имея данных сложновато понять, что происходит. Как вариант, для отладки вы можете попробовать добавить output inserted.* для всех блоков insert, что даст вам возможность посмотреть в каком порядке и какие именно данные были вставлены. Пример работы output блока: declare @attachments table (id int, status_id int) insert into @attachments (id, status_id) output 'block #1', inserted.* values (1, 2) insert into @attachments (id, status_id) output 'block #2', inserted.* select 3, 8 Возможно, здесь: SELECT @ID_zapros = id FROM Attachments WHERE ... либо здесь SELECT @ID_prikrep = id FROM Attachments WHERE ... по мере движения курсора выбирается не то, что ожидается.

TSQL, “простой некластеризованный индекс”

#sql #база_данных #sql_server


Есть несколько индексов такого вида:

CREATE INDEX IX_ProductVendor_VendorID ON Purchasing.ProductVendor (VendorID);


На MSDN здесь (в разделе "примеры", первый пример) сказано, что это "простой некластеризованный
индекс". Что это значит? Обычный первичный ключ? Может быть, глупый вопрос, но для
меня не очевидно, хотелось бы быть уверенным.
    


Ответы

Ответ 1



На Хабре есть аааааабалденная статья на тему индексов. Очень советую. Если вкратце, то: Индекс использует дерево для быстрого обращения к данным. У кластеризованного индекса в листьях дерева лежат сами строки данных. В силу природы дерева данные хранятся в уже отсортированном виде, поэтому кластеризованный индекс может быть только один. У некластеризованного индекса в листьях дерева лежат указатели на строки данных, т.е. для чтения данных необходима еще одна операция. Т.о. кластеризованных индексов у таблицы может быть несколько. Что это значит? Обычный первичный ключ? Понятие первичного ключа в общем-то не связано с индексом. В таблице может быть колонка, являющаяся первичным ключом, но без индекса. Однако на первичные ключи как правило создают кластеризованный индекс. В вашем вопросе VendorID это скорее просто foreign key -- на них обычно создают некластеризованные индексы.

Big data, оптимизация запросов

#sql_server #big_data


Есть следующая простая структура данных

Id; fk_Security_Id; DateTime; Price


Строка хранит данные по инструменту(активу), дату и время, цену(котировку). Строк
в БД на данный момент ~ 1 млрд. 200 млн. (10 инструментов с историей за прошлые 10 лет)

Задача - выборка данных по указанному fk_Security_Id и промежутку DateTime(например,
июль 2000г.) за адекватный промежуток времени (в идеале меньше минуты).

Сначала, я использовал знакомый мне MSSQL и навесил в лоб clustered index на эти
2 поля. В результате поиск по этим 2 полям занимает в районе 35 минут и сожранные 6.5Gb
RAM. Не совсем то, что конечно хотелось бы.
Какие варианты решения вижу пока я:


Не менять выбранную бд, а изменить саму структуру хранения
данных. Например разнести в разные таблицы данные по разным инструментам. В
этом случае конечно будут абсолютно идентичные таблицы с точки
зрения структуры, но можно будет выиграть некоторое время на поиске и
дальнейшее добавление новых инструментов не будет влиять на то самое время поиска.
И тогда вместо композитного кластерного индекса, индекс
будет состоять из одного поля - datetime. Также возможно здесь
имеет смысл вместо поля datetime в качестве индекса брать некий
timestamp или преобразованный Id. Но не уверен что это даст
существенный прирост в поиске, хотя стоит попробовать думаю.
Использовать какую-нибудь более легковесную бд, например postgres (дружит с необходимым
мне EF, что очень хотелось бы) + есть нативная поддержка Sphinx-а например.
Использовать какое-нибудь NoSql решение. С данными бд дел не имел, но допускаю,что
в моем случае данные укладываются в простую структуру key-value. Правда, наверное те
NoSql которые держат данные в RAM мне не подойдут потому что у меня просто столько
памяти нету. Хотя, если я не ошибась есть и достаточно шустрые дисковые NoSql , Aerospike
например. Но опять же поскольку я с ними не работал я не могу оценить насколько они
дадут выигрыш по времени по сравнению с обыными реляционными бд.


База не распределенная, ресурсы машины - 8 потоков и 8Gb RAM. Буду рад любому совету.
    


Ответы

Ответ 1



Пара мыслей (eсли вы всё же остановитесь на MSSQL). На мой взгляд big-data подразумевает щепетильное отношение к структурам хранения данных и типам хранимых данных. Сравните, к примеру, размеры различных типов данных для хранения дат и чисел: declare @dt datetime = getdate(), @dt2 datetime2(0) = getdate(), @sdt smalldatetime = getdate(), @m money = 1.0, @f float = 1.0, @dec_15_5 decimal(15,5) = 1.0, @r real = 1.0 select [datetime] = datalength(@dt), [datetime2(0)] = datalength(@dt2), [smalldatetime] = datalength(@sdt), [money] = datalength(@m), [float] = datalength(@f), [decimal(15,5)] = datalength(@dec_15_5), [real] = datalength(@r) datetime datetime2(0) smalldatetime money float decimal(15,5) real --------- ------------- -------------- ------ ------ -------------- ----- 8 6 4 8 8 5 4 Если тип столбца DateTime у вас datetime, рассмотрите возможность использования, например, типа smalldatetime (диапазон значений от 1900-01-01 до 2079-06-06 с точностью 1 минута). Если, тип стоблца Price, к примеру, float - рассмотрите возможность использования типов decimal (numeric) или real. Чем меньше размер строки данных, тем больше строк помещается в одну страницу памяти, соответственно легче оперировать ими в запросах. В таблицах с большим числом строк нелишним будет избегать NULL-able столбцов (это также сэкономит немного места). Правда следствием компактного хранения может быть некоторое неудобство в написании запросов, когда, например, при вычислении среднего для сохранения точности приходится делать кастинг в тип с большей точностью, а потом обратно. Да и сам кастинг несколько повысит стоимость запроса. Ваша идея разнести инструменты по таблицам имеет рациональное зерно. Нужно ли их держать в одной таблице, и в самом ли деле нужен Id в таблице, если, к примеру, на неё нет ссылок - решать вам. Однако если разнести данные по таблицам вида create table SomeInstrument ( DateTime smalldatetime not NULL primary key, Rate real not NULL ) то общий объём хранимых данных явно уменьшится, т.к. не будет столбцов Id и fk_Security_Id. Если всё же оставите всё в одной таблице, то fk_Security_Id (вместе с primary key таблицы, на которую он ссылается) имеет смысл перевести на тип tinyint, раз уж инструментов всего около десятка.

MSSQL. Создание группы с определенными правами

#sql_server #права #доступ


Необходимо дать права только на выполнение данной команды:

INSERT INTO table
SELECT *
FROM OPENROWSET(
    'MSDASQL',
    'Driver={Microsoft Access dBASE Driver (*.dbf, *.ndx, *.mdx)};DBQ=shara',
    'SELECT * FROM table2');


Т.е. что бы члены группы имели доступ только table, могли выполнять команду OPENROWSET
- не больше и не меньше.

Не подскажете как дать права правильно или где почитать? 

Заранее спасибо за Ваши ответы. 
    


Ответы

Ответ 1



Может быть вам подойдёт такой вариант. Заворачиваем команду в хранимую процедуру: create procedure [dbo].[ImportProc] with execute as 'UserName' -- тот у кого есть права на table и OPENROWSET as begin set nocount on; INSERT INTO [table] SELECT * FROM OPENROWSET(...); end далее создать роль, дать ей права на выполнение процедуры и добавить нужных пользователей в роль: USE [DBName] GO CREATE ROLE [data_import] GO GRANT EXECUTE ON [dbo].[ImportProc] TO [data_import] GO ALTER ROLE [data_import] ADD MEMBER [DataImporterUserName] GO Если нет, то для использования OPENROWSET нужно давать разрешение уровня сервера ADMINISTER BULK OPERATIONS соответствующему логину: USE [master] GO GRANT ADMINISTER BULK OPERATIONS TO [LoginName] GO Плюс в базе нужно отдельно разрешить вставку в таблицу: USE [DBName] GO GRANT INSERT ON [dbo].[table] TO [RoleName] GO

Странная работа оконных функций min max в ms sql 2012 при обработке null

#sql_server #функции


Странная работа оконных функций min, max в ms sql 2012 при обработке null–значений.
2 запроса

select max(q1) OVER(PARTITION BY q2 order by q1) from
(
    select null as q1, 1 as q2, 0 as q3
    union
    select 11, 1, 1
    union
    select null, 1, 1
) a


select max(q1) OVER(PARTITION BY q2) from
(
    select null as q1, 1 as q2, 0 as q3
    union
    select 11, 1, 1
    union
    select null, 1, 1
) a


Результат первого


NULL 
NULL
11


Второго 


11
11
11


Казалось бы причём тут в функциях min max order by...


  Microsoft SQL Server 2012 - 11.0.5058.0 (X64)
    May 14 2014 18:34:29 
    Copyright (c) Microsoft Corporation
    Enterprise Edition: Core-based
  Licensing (64-bit) on Windows NT 6.1  (Build 7601: Service Pack
  1)


и


  Microsoft SQL Server 2014 - 12.0.2000.8 (X64)
    Feb 20 2014 20:04:26
    Copyright (c) Microsoft Corporation
    Enterprise Edition (64-bit) on
  Windows NT 6.3  (Build 9600: )

    


Ответы

Ответ 1



Немного модифицируем ваш запрос: select max(q1) OVER(PARTITION BY q2 order by q3), min(q1) OVER(PARTITION BY q2 order by q3), sum(q3) OVER(PARTITION BY q2 order by q3), max(q1) OVER(PARTITION BY q2), sum(q3) OVER(PARTITION BY q2) from ( select null as q1, 1 as q2, 0 as q3 union select 12, 1, 1 union select 11, 1, 2 ) a Результат: NULL NULL 0 12 3 12 12 1 12 3 12 11 3 12 3 Видно, что у выражений без ORDER BY берется требуемое значение для всего окна, а для предложений с сортировкой - берется накопленное на данный момент значение в порядке сортировки. Теперь открываем документацию, раздел "Общие примечания": Если предложение ORDER BY не указано, то для рамки окна используется весь раздел. Это относится только к тем функциям, которым не требуется предложение ORDER BY. Если предложение ROWS или RANGE не указаны, а указано предложение ORDER BY, то в качестве значения по умолчанию для рамки окна используется RANGE UNBOUNDED PRECEDING AND CURRENT ROW Собственно в документации сказано именно то, что мы увидели в результате запроса. Если order by указан, то функции к которым это применимо, рассматривают окно он начала раздела заданного partition by до текущей строки.